diff --git a/purchase_dashboard_advanced/README.rst b/purchase_dashboard_advanced/README.rst new file mode 100644 index 000000000..402db0b9b --- /dev/null +++ b/purchase_dashboard_advanced/README.rst @@ -0,0 +1,44 @@ +.. 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 + +Advanced Purchase Dashboard +=========================== +A dashboard for keeping track and evaluate various aspects of spending on purchases. + +Configuration +============= +- No additional configuration needed + +Company +------- +* `Cybrosys Techno Solutions `__ + +License +------- +General Public License, Version 3 (LGPL v3). +(https://www.gnu.org/licenses/lgpl-3.0-standalone.html) + +Credits +------- +* Developer:(V16) Jumana Jabin MP , 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/purchase_dashboard_advanced/__init__.py b/purchase_dashboard_advanced/__init__.py new file mode 100644 index 000000000..97f4c673b --- /dev/null +++ b/purchase_dashboard_advanced/__init__.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2023-TODAY Cybrosys Technologies() +# Author: Jumana Jabin MP (odoo@cybrosys.com) +# +# 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 diff --git a/purchase_dashboard_advanced/__manifest__.py b/purchase_dashboard_advanced/__manifest__.py new file mode 100644 index 000000000..792bb2f60 --- /dev/null +++ b/purchase_dashboard_advanced/__manifest__.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2023-TODAY Cybrosys Technologies() +# Author: Jumana Jabin MP (odoo@cybrosys.com) +# +# 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': "Advanced Purchase Dashboard", + 'version': '16.0.1.0.0', + 'category': 'Purchases', + 'summary': 'The Purchase Dashboard provides an detailed overview of your ' + 'purchases in a single screen', + 'description': 'A dashboard for keeping track and evaluate various ' + 'aspects of spending on purchases', + 'author': 'Cybrosys Techno Solutions', + 'company': 'Cybrosys Techno Solutions', + 'maintainer': 'Cybrosys Techno Solutions', + 'website': "https://www.cybrosys.com", + 'depends': ['purchase'], + 'data': [ + 'views/purchase_dashboard_advanced_menus.xml' + ], + 'assets': { + 'web.assets_backend': [ + 'purchase_dashboard_advanced/static/src/xml/*.xml', + 'purchase_dashboard_advanced/static/src/js/*.js', + 'purchase_dashboard_advanced/static/src/css/style.css', + 'https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.8.0/Chart.bundle.js', + ], + }, + 'images': ['static/description/banner.jpg'], + 'license': "LGPL-3", + 'installable': True, + 'auto_install': False, + 'application': False, +} diff --git a/purchase_dashboard_advanced/doc/RELEASE_NOTES.md b/purchase_dashboard_advanced/doc/RELEASE_NOTES.md new file mode 100644 index 000000000..c7cc39ad0 --- /dev/null +++ b/purchase_dashboard_advanced/doc/RELEASE_NOTES.md @@ -0,0 +1,7 @@ +## Module + +#### 06.01.2024 +#### Version 16.0.1.0.0 +#### ADD + +- Initial commit for Advanced Purchase Dashboard diff --git a/purchase_dashboard_advanced/models/__init__.py b/purchase_dashboard_advanced/models/__init__.py new file mode 100644 index 000000000..d8cf9fd2b --- /dev/null +++ b/purchase_dashboard_advanced/models/__init__.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2023-TODAY Cybrosys Technologies() +# Author: Jumana Jabin MP (odoo@cybrosys.com) +# +# 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 purchase_order +from . import purchase_order_line diff --git a/purchase_dashboard_advanced/models/purchase_order.py b/purchase_dashboard_advanced/models/purchase_order.py new file mode 100644 index 000000000..01c07852b --- /dev/null +++ b/purchase_dashboard_advanced/models/purchase_order.py @@ -0,0 +1,539 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2023-TODAY Cybrosys Technologies() +# Author: Jumana Jabin MP (odoo@cybrosys.com) +# +# 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 . +# +############################################################################# +import calendar +from odoo import api, models + + +class PurchaseOrder(models.Model): + """Model representing purchase orders and related analytics.Inherits from + 'purchase.order' model.""" + _inherit = 'purchase.order' + + @api.model + def get_purchase_data(self): + """Returns data to the orm call to display the data in the tiles + :return: Dictionary with purchase data + :rtype: dict""" + orders = self.env['purchase.order'].search([('state', 'in', [ + 'purchase', 'done'])]) + priority_orders = self.env['purchase.order'].search([ + ('priority', '=', '1')]) + vendor = list(set(rec.partner_id for rec in orders)) + return { + 'purchase_orders': len(orders), + 'purchase_amount': sum(rec.amount_total for rec in orders), + 'priority_orders': len(priority_orders), + 'vendors': len(vendor) + } + + def get_yearly_data(self): + """Get yearly purchase data. + :return: Dictionary with yearly purchase data + :rtype: dict""" + company = self.env.company.id + query = """ + SELECT COUNT(*) as po_count, SUM(amount_total) as po_sum + FROM purchase_order + WHERE company_id = %s + AND state IN ('purchase', 'done') + AND date_order >= date_trunc('year', now()) + """ + self.env.cr.execute(query, (company,)) + data = self.env.cr.dictfetchall() + query_priority = """ + SELECT COUNT(*) as priority_count + FROM purchase_order po + WHERE company_id = %s + AND state IN ('purchase', 'done') + AND EXTRACT(YEAR from date_order) = EXTRACT(YEAR from now()) + AND priority = '1' + """ + self.env.cr.execute(query_priority, (company,)) + priority_orders = self.env.cr.dictfetchall() + query_vendors = """ + SELECT COUNT(DISTINCT partner_id) as vendor_count + FROM purchase_order + WHERE company_id = %s + AND state IN ('purchase', 'done') + AND date_order < date_trunc('year', now()) + """ + self.env.cr.execute(query_vendors, (company,)) + result = self.env.cr.dictfetchall() + if result: + previous_vendors = result[0]['vendor_count'] + else: + previous_vendors = 0 + query_current_vendors = """ + SELECT COUNT(DISTINCT partner_id) as vendor_count + FROM purchase_order + WHERE company_id = %s + AND state IN ('purchase', 'done') + AND date_order BETWEEN date_trunc('year', now()) AND now() + """ + self.env.cr.execute(query_current_vendors, (company,)) + result = self.env.cr.dictfetchall() + if result: + current_vendors = result[0]['vendor_count'] + else: + current_vendors = 0 + new_vendors = current_vendors - previous_vendors + yearly = { + 'purchase_orders': data[0]['po_count'], + 'purchase_amount': data[0]['po_sum'] or 0, + 'priority_orders': priority_orders[0]['priority_count'], + 'vendors': new_vendors or 0, + } + return yearly + + def get_monthly_data(self): + """Get monthly purchase data. + :return: Dictionary with monthly purchase data + :rtype: dict""" + company = self.env.company.id + query = """ + SELECT COUNT(*), SUM(amount_total) + FROM purchase_order po + WHERE company_id = %s + AND state IN ('purchase', 'done') + AND EXTRACT(YEAR from date_order) = EXTRACT(YEAR from + CURRENT_DATE) + AND EXTRACT(MONTH from date_order) = EXTRACT(MONTH from + CURRENT_DATE) + """ + self.env.cr.execute(query, (company,)) + data = self.env.cr.dictfetchall() + query = """ + SELECT COUNT(*) + FROM purchase_order po + WHERE company_id = %s + AND EXTRACT(YEAR from date_order) = EXTRACT(YEAR from + CURRENT_DATE) + AND EXTRACT(MONTH from date_order) = EXTRACT(MONTH from + CURRENT_DATE) + AND priority = '1' + """ + self.env.cr.execute(query, (company,)) + priority_orders = self.env.cr.dictfetchall() + query = """ + SELECT DISTINCT partner_id + FROM purchase_order po + WHERE company_id = %s + AND state IN ('purchase','done') + AND EXTRACT(month from date_order) < EXTRACT(month FROM + CURRENT_DATE) + """ + self.env.cr.execute(query, (company,)) + previous_vendors = self.env.cr.dictfetchall() + previous = [] + if len(previous_vendors) > 1: + previous = [rec['partner_id'] for rec in previous_vendors] + else: + previous.append(rec for rec in previous_vendors) + query = """ + SELECT DISTINCT partner_id + FROM purchase_order po + WHERE company_id = %s + AND state IN ('purchase','done') + AND EXTRACT(YEAR from date_order) = EXTRACT(YEAR FROM + CURRENT_DATE) + AND EXTRACT(month from date_order) = EXTRACT(month FROM + CURRENT_DATE) + """ + self.env.cr.execute(query, (company,)) + vendors = self.env.cr.dictfetchall() + new_vendors = [] + if vendors: + if len(vendors) > 1: + for rec in vendors: + if rec['partner_id'] not in previous: + new_vendors.append(rec['partner_id']) + else: + if vendors not in previous: + new_vendors.append(vendors[0]['partner_id']) + monthly = { + 'purchase_orders': data[0]['count'], + 'purchase_amount': data[0]['sum'], + 'priority_orders': priority_orders[0]['count'], + 'vendors': len(new_vendors), + 'vendor_id': new_vendors, + } + return monthly + + def get_weekly_data(self): + """Get weekly purchase data. + :return: Dictionary with weekly purchase data + :rtype: dict""" + company = self.env.company.id + query = """ + SELECT COUNT(*), SUM(amount_total), COUNT(CASE WHEN priority = '1' + THEN 1 ELSE NULL END) + FROM purchase_order + WHERE company_id = %s + AND state IN ('purchase', 'done') + AND EXTRACT(YEAR from date_order) = EXTRACT(YEAR from + CURRENT_DATE) + AND EXTRACT(WEEK from date_order) = EXTRACT(WEEK from + CURRENT_DATE) + """ + self.env.cr.execute(query, [company]) + data = self.env.cr.fetchone() + query = """ + SELECT DISTINCT partner_id + FROM purchase_order + WHERE company_id = %s + AND state IN ('purchase', 'done') + AND EXTRACT(WEEK from date_order) < EXTRACT(WEEK FROM + CURRENT_DATE) + """ + self.env.cr.execute(query, [company]) + previous_vendors = self.env.cr.dictfetchall() + previous = [rec['partner_id'] for rec in previous_vendors] + query = """ + SELECT DISTINCT partner_id + FROM purchase_order + WHERE company_id = %s + AND state IN ('purchase', 'done') + AND EXTRACT(YEAR from date_order) = EXTRACT(YEAR FROM + CURRENT_DATE) + AND EXTRACT(WEEK from date_order) = EXTRACT(WEEK FROM + CURRENT_DATE) + """ + self.env.cr.execute(query, [company]) + vendors = self.env.cr.dictfetchall() + new_vendors = [rec['partner_id'] for rec in vendors if rec[ + 'partner_id'] not in previous] + weekly = { + 'purchase_orders': data[0], + 'purchase_amount': data[1], + 'priority_orders': data[2], + 'vendors': len(new_vendors) + } + return weekly + + def get_today_data(self): + """Get purchase data for the current day. + :return: Dictionary with purchase data for the current day + :rtype: dict""" + company = self.env.company.id + query = """ + SELECT + COUNT(*) AS purchase_orders, + SUM(amount_total) AS purchase_amount, + COUNT(*) FILTER (WHERE priority = '1') AS priority_orders + FROM purchase_order + WHERE + company_id = %s + AND state IN ('purchase', 'done') + AND date_order::date = CURRENT_DATE + """ + self.env.cr.execute(query, (company,)) + today_data = self.env.cr.dictfetchall()[0] + query = """ + SELECT DISTINCT partner_id + FROM purchase_order + WHERE + company_id = %s + AND state IN ('purchase', 'done') + AND date_order::date = CURRENT_DATE + AND NOT EXISTS ( + SELECT 1 + FROM purchase_order + WHERE + company_id = %s + AND state IN ('purchase', 'done') + AND date_order::date < CURRENT_DATE + AND partner_id = purchase_order.partner_id)""" + self.env.cr.execute(query, (company, company)) + new_vendors = [r['partner_id'] for r in self.env.cr.dictfetchall()] + return { + 'purchase_orders': today_data['purchase_orders'], + 'purchase_amount': today_data['purchase_amount'], + 'priority_orders': today_data['priority_orders'], + 'vendors': len(new_vendors), + 'vendor_id': new_vendors, + } + + @api.model + def get_select_mode_data(self, args): + """Get data based on the selected filters + :param args: Selected filter + :type args: str + :return: Data based on the selected filter + :rtype: dict or False""" + data = { + 'this_year': self.get_yearly_data, + 'this_month': self.get_monthly_data, + 'this_week': self.get_weekly_data, + 'today': self.get_today_data, + }.get(args) + return data() if data else False + + def execute_query(self, query, args): + """Returns quantity/count regarding the top entities + :param query: SQL query to be executed + :type query: str + :param args: Query parameters + :type args: str + :return: Query results + :rtype: list""" + self._cr.execute(query) + results = self._cr.dictfetchall() + final = [] + if args == 'top_product': + final = [[record.get('total_quantity') for record in results], + [record.get('product_name') for record in results]] + elif args == 'top_vendor': + final = [[record.get('count') for record in results], + [record.get('name') for record in results]] + elif args == 'top_rep': + final = [[record.get('count') for record in results], + [record.get('name') for record in results]] + return final + + @api.model + def get_top_chart_data(self, args): + """Get top chart data based on the selected filter. + :param args: Selected filter + :type args: str + :return: Top chart data + :rtype: list""" + query = '' + company_id = self.env.company.id + if args == 'top_product': + query = f''' + SELECT DISTINCT(product_template.name) as product_name, + SUM(product_qty) as total_quantity + FROM purchase_order_line + INNER JOIN product_product ON + product_product.id=purchase_order_line.product_id + INNER JOIN product_template ON + product_product.product_tmpl_id = product_template.id + WHERE purchase_order_line.company_id = {company_id} + GROUP BY product_template.id + ORDER BY total_quantity DESC + LIMIT 10 + ''' + elif args == 'top_vendor': + query = f''' + SELECT partner.name, COUNT(po.id) as count + FROM purchase_order po + JOIN res_partner partner ON po.partner_id = partner.id + WHERE po.company_id = {company_id} + GROUP BY partner.name + ORDER BY count DESC + LIMIT 10 + ''' + elif args == 'top_rep': + query = f''' + SELECT partner.name, COUNT(po.id) as count + FROM purchase_order po + JOIN res_users users ON po.user_id = users.id + JOIN res_partner partner ON users.partner_id = partner.id + WHERE po.company_id = {company_id} + GROUP BY partner.name + ORDER BY count DESC + LIMIT 10 + ''' + final = self.execute_query(query, args) + return final + + @api.model + def get_orders_by_month(self): + """Get purchase orders grouped by month. + :return: Purchase orders by month + :rtype: dict""" + query = f"""select count(*), EXTRACT(month from date_order) as dates + from purchase_order po + where company_id = {self.env.company.id} and state = 'purchase' + group by dates""" + self.env.cr.execute(query, (self.env.company.id,)) + cr = self.env.cr.dictfetchall() + month = [] + for rec in cr: + month.append(int(rec['dates'])) + rec.update({ + 'count': rec['count'], + 'dates': calendar.month_name[int(rec['dates'])], + 'month': int(rec['dates']) + }) + for rec in range(1, 13): + if rec not in month: + cr.append({ + 'count': 0, + 'dates': calendar.month_name[rec], + 'month': rec + }) + cr = sorted(cr, key=lambda i: i['month']) + return { + 'count': [rec['count'] for rec in cr], + 'dates': [rec['dates'] for rec in cr] + } + + @api.model + def purchase_vendors(self): + """Get a list of purchase vendors. + :return: List of purchase vendors in the format + [{'id': vendor_id, 'name': vendor_name}] + :rtype: list""" + company_id = self.env.company.id + query = """ + SELECT partner.id, partner.name + FROM purchase_order po + INNER JOIN res_partner partner ON po.partner_id = partner.id + WHERE po.company_id = %s + GROUP BY partner.id + """ + self._cr.execute(query, (company_id,)) + return self._cr.dictfetchall() + + @api.model + def purchase_vendor_details(self, args): + """Get purchase details for a specific vendor. + :param args: Vendor ID + :type args: int + :return: Purchase details for the specific vendor + :rtype: dict""" + company_id = self.env.company.id + partner = int(args) if args else 1 + query = """ + SELECT count(po.id),SUM(po.amount_total), EXTRACT(MONTH from + po.date_order) as dates + FROM purchase_order po + JOIN res_partner ON res_partner.id = po.partner_id + WHERE po.company_id = %s and po.partner_id = %s + GROUP BY dates + """ + self._cr.execute(query, (company_id, partner)) + partner_orders = self._cr.dictfetchall() + query_draft = """ + SELECT count(po.id),SUM(po.amount_total), EXTRACT(MONTH from + po.date_order) as dates + FROM purchase_order po + JOIN res_partner ON res_partner.id = po.partner_id + WHERE po.state in ('draft', 'sent') and po.company_id = %s and + po.partner_id = %s + GROUP BY dates""" + self._cr.execute(query_draft, (company_id, partner)) + draft_orders = self._cr.dictfetchall() + approve_qry = """ + SELECT count(po.id),SUM(po.amount_total), EXTRACT(MONTH from + po.date_order) as dates + FROM purchase_order po + JOIN res_partner ON res_partner.id = po.partner_id + WHERE po.state = 'to approve' and po.company_id = %s and + po.partner_id = %s + GROUP BY dates""" + self._cr.execute(approve_qry, (company_id, partner)) + approve_orders = self._cr.dictfetchall() + cancel_qry = """ + SELECT count(po.id),SUM(po.amount_total), EXTRACT(MONTH from + po.date_order) as dates + FROM purchase_order po + JOIN res_partner ON res_partner.id = po.partner_id + WHERE po.state = 'cancel' and po.company_id = %s and po.partner_id + = %s + GROUP BY dates""" + self._cr.execute(cancel_qry, (company_id, partner)) + cancel_orders = self._cr.dictfetchall() + all_orders = { + 'partner_orders': partner_orders, 'draft_orders': draft_orders, + 'approve_orders': approve_orders, 'cancel_orders': cancel_orders} + for order_type, order_list in all_orders.items(): + order_months = [] + for rec in order_list: + order_months.append(int(rec.get('dates'))) + for rec in range(1, 13): + if rec not in order_months: + vals = {'sum': 0.0, 'dates': rec, 'count': 0} + order_list.append(vals) + all_orders[order_type] = sorted( + order_list, key=lambda order: order['dates']) + value = { + 'purchase_amount': [record.get('sum') for record in + partner_orders], + 'po_count': [record.get('count') for record in partner_orders], + 'draft_amount': [record.get('sum') for record in draft_orders], + 'draft_count': [record.get('count') for record in draft_orders], + 'approve_amount': [record.get('sum') for record in approve_orders], + 'approve_count': [record.get('count') for record in + approve_orders], + 'cancel_amount': [record.get('sum') for record in cancel_orders], + 'cancel_count': [record.get('count') for record in cancel_orders], + 'dates': [record.get('dates') for record in partner_orders], + } + return value + + @api.model + def get_pending_purchase_data(self): + """Get pending purchase orders data. + :return: Data of pending purchase orders + :rtype: dict""" + company = self.env.company.id + query = """ + SELECT po.name, po.id, rp.name as partner_name, po.date_planned, + po.amount_total, po.state + FROM purchase_order po + JOIN purchase_order_line pol ON pol.order_id = po.id + JOIN res_partner rp ON rp.id = po.partner_id + WHERE po.date_planned < CURRENT_DATE AND pol.qty_received < + pol.product_qty AND po.company_id = %s + GROUP BY po.id, rp.id + """ + self._cr.execute(query, (company,)) + orders = self._cr.dictfetchall() + value = { + 'order': [rec['name'] for rec in orders], + 'vendor': [rec['partner_name'] for rec in orders], + 'amount': [rec['amount_total'] for rec in orders], + 'date': [rec['date_planned'] for rec in orders], + 'state': [rec['state'] for rec in orders], + 'data': [list(val for val in rec.values()) for rec in orders] + } + return value + + @api.model + def get_upcoming_purchase_data(self): + """Get upcoming purchase orders data. + :return: Data of upcoming purchase orders + :rtype: dict""" + company = self.env.company.id + query = """ + SELECT po.name, po.id, rp.name as partner_name, po.date_planned, + po.amount_total, po.state + FROM purchase_order po + JOIN purchase_order_line pol ON pol.order_id = po.id + JOIN res_partner rp ON rp.id = po.partner_id + WHERE po.date_planned > CURRENT_DATE AND pol.qty_received < + pol.product_qty AND po.company_id = %s + GROUP BY po.id, rp.id + """ + self._cr.execute(query, (company,)) + orders = self._cr.dictfetchall() + value = { + 'order': [rec['name'] for rec in orders], + 'vendor': [rec['partner_name'] for rec in orders], + 'amount': [rec['amount_total'] for rec in orders], + 'date': [rec['date_planned'] for rec in orders], + 'state': [rec['state'] for rec in orders], + 'data': [list(val for val in rec.values()) for rec in orders], + } + return value diff --git a/purchase_dashboard_advanced/models/purchase_order_line.py b/purchase_dashboard_advanced/models/purchase_order_line.py new file mode 100644 index 000000000..c2a47f456 --- /dev/null +++ b/purchase_dashboard_advanced/models/purchase_order_line.py @@ -0,0 +1,92 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2023-TODAY Cybrosys Technologies() +# Author: Jumana Jabin MP (odoo@cybrosys.com) +# +# 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 api, models + + +class PurchaseOrderLine(models.Model): + """Model representing purchase order lines and related analytics. + Inherits from 'purchase.order.line' model.""" + _inherit = 'purchase.order.line' + + @api.model + def product_categ_analysis(self): + """Get product category analysis data. + :return: Product category analysis data + :rtype: dict""" + company_id = self.env.user.company_id.id + quantity_query = """ + SELECT product_template.name, SUM(pl.product_qty) as total_quantity + FROM purchase_order_line pl + JOIN product_product ON pl.product_id = product_product.id + JOIN product_template ON product_product.product_tmpl_id = + product_template.id + WHERE pl.company_id = %s + GROUP BY product_template.name + """ + self._cr.execute(quantity_query, (company_id,)) + products_quantity = self._cr.fetchall() + name, quantity_done = zip(*products_quantity) + categories = self.get_categories() + value = {'name': name, 'count': quantity_done} + return {'values': value, 'category_id': categories} + + def get_categories(self): + """Get product categories. + :return: Product categories + :rtype: list""" + category_query = """ + SELECT pc.id, pc.name + FROM product_category pc + JOIN product_template pt ON pt.categ_id = pc.id + JOIN product_product pp ON pp.product_tmpl_id = pt.id + JOIN purchase_order_line pl ON pl.product_id = pp.id + WHERE pl.company_id = %s + GROUP BY pc.id, pc.name + """ + self._cr.execute(category_query, (self.env.user.company_id.id,)) + return self._cr.fetchall() + + @api.model + def product_categ_data(self, args): + """Get product category data. + :param args: Category ID + :type args: int + :return: Product category data + :rtype: dict""" + category_id = int(args or 1) + company_id = self.env.company.id + query = """ + SELECT product_template.name, SUM(pl.product_qty) + FROM purchase_order_line pl + INNER JOIN product_product ON pl.product_id = product_product.id + INNER JOIN product_template ON product_product.product_tmpl_id = + product_template.id + WHERE pl.company_id = %s AND product_template.categ_id = %s + GROUP BY product_template.name + """ + self._cr.execute(query, (company_id, category_id)) + product_move = self._cr.dictfetchall() + value = { + 'name': [record.get('name') for record in product_move], + 'count': [record.get('sum') for record in product_move], + } + return value diff --git a/purchase_dashboard_advanced/static/description/assets/icons/check.png b/purchase_dashboard_advanced/static/description/assets/icons/check.png new file mode 100644 index 000000000..c8e85f51d Binary files /dev/null and b/purchase_dashboard_advanced/static/description/assets/icons/check.png differ diff --git a/purchase_dashboard_advanced/static/description/assets/icons/chevron.png b/purchase_dashboard_advanced/static/description/assets/icons/chevron.png new file mode 100644 index 000000000..2089293d6 Binary files /dev/null and b/purchase_dashboard_advanced/static/description/assets/icons/chevron.png differ diff --git a/purchase_dashboard_advanced/static/description/assets/icons/cogs.png b/purchase_dashboard_advanced/static/description/assets/icons/cogs.png new file mode 100644 index 000000000..95d0bad62 Binary files /dev/null and b/purchase_dashboard_advanced/static/description/assets/icons/cogs.png differ diff --git a/purchase_dashboard_advanced/static/description/assets/icons/consultation.png b/purchase_dashboard_advanced/static/description/assets/icons/consultation.png new file mode 100644 index 000000000..8319d4baa Binary files /dev/null and b/purchase_dashboard_advanced/static/description/assets/icons/consultation.png differ diff --git a/purchase_dashboard_advanced/static/description/assets/icons/ecom-black.png b/purchase_dashboard_advanced/static/description/assets/icons/ecom-black.png new file mode 100644 index 000000000..a9385ff13 Binary files /dev/null and b/purchase_dashboard_advanced/static/description/assets/icons/ecom-black.png differ diff --git a/purchase_dashboard_advanced/static/description/assets/icons/education-black.png b/purchase_dashboard_advanced/static/description/assets/icons/education-black.png new file mode 100644 index 000000000..3eb09b27b Binary files /dev/null and b/purchase_dashboard_advanced/static/description/assets/icons/education-black.png differ diff --git a/purchase_dashboard_advanced/static/description/assets/icons/hotel-black.png b/purchase_dashboard_advanced/static/description/assets/icons/hotel-black.png new file mode 100644 index 000000000..130f613be Binary files /dev/null and b/purchase_dashboard_advanced/static/description/assets/icons/hotel-black.png differ diff --git a/purchase_dashboard_advanced/static/description/assets/icons/license.png b/purchase_dashboard_advanced/static/description/assets/icons/license.png new file mode 100644 index 000000000..a5869797e Binary files /dev/null and b/purchase_dashboard_advanced/static/description/assets/icons/license.png differ diff --git a/purchase_dashboard_advanced/static/description/assets/icons/lifebuoy.png b/purchase_dashboard_advanced/static/description/assets/icons/lifebuoy.png new file mode 100644 index 000000000..658d56ccc Binary files /dev/null and b/purchase_dashboard_advanced/static/description/assets/icons/lifebuoy.png differ diff --git a/purchase_dashboard_advanced/static/description/assets/icons/manufacturing-black.png b/purchase_dashboard_advanced/static/description/assets/icons/manufacturing-black.png new file mode 100644 index 000000000..697eb0e9f Binary files /dev/null and b/purchase_dashboard_advanced/static/description/assets/icons/manufacturing-black.png differ diff --git a/purchase_dashboard_advanced/static/description/assets/icons/pos-black.png b/purchase_dashboard_advanced/static/description/assets/icons/pos-black.png new file mode 100644 index 000000000..97c0f90c1 Binary files /dev/null and b/purchase_dashboard_advanced/static/description/assets/icons/pos-black.png differ diff --git a/purchase_dashboard_advanced/static/description/assets/icons/puzzle.png b/purchase_dashboard_advanced/static/description/assets/icons/puzzle.png new file mode 100644 index 000000000..65cf854e7 Binary files /dev/null and b/purchase_dashboard_advanced/static/description/assets/icons/puzzle.png differ diff --git a/purchase_dashboard_advanced/static/description/assets/icons/restaurant-black.png b/purchase_dashboard_advanced/static/description/assets/icons/restaurant-black.png new file mode 100644 index 000000000..4a35eb939 Binary files /dev/null and b/purchase_dashboard_advanced/static/description/assets/icons/restaurant-black.png differ diff --git a/purchase_dashboard_advanced/static/description/assets/icons/service-black.png b/purchase_dashboard_advanced/static/description/assets/icons/service-black.png new file mode 100644 index 000000000..301ab51cb Binary files /dev/null and b/purchase_dashboard_advanced/static/description/assets/icons/service-black.png differ diff --git a/purchase_dashboard_advanced/static/description/assets/icons/trading-black.png b/purchase_dashboard_advanced/static/description/assets/icons/trading-black.png new file mode 100644 index 000000000..9398ba2f1 Binary files /dev/null and b/purchase_dashboard_advanced/static/description/assets/icons/trading-black.png differ diff --git a/purchase_dashboard_advanced/static/description/assets/icons/training.png b/purchase_dashboard_advanced/static/description/assets/icons/training.png new file mode 100644 index 000000000..884ca024d Binary files /dev/null and b/purchase_dashboard_advanced/static/description/assets/icons/training.png differ diff --git a/purchase_dashboard_advanced/static/description/assets/icons/update.png b/purchase_dashboard_advanced/static/description/assets/icons/update.png new file mode 100644 index 000000000..ecbc5a01a Binary files /dev/null and b/purchase_dashboard_advanced/static/description/assets/icons/update.png differ diff --git a/purchase_dashboard_advanced/static/description/assets/icons/user.png b/purchase_dashboard_advanced/static/description/assets/icons/user.png new file mode 100644 index 000000000..6ffb23d9f Binary files /dev/null and b/purchase_dashboard_advanced/static/description/assets/icons/user.png differ diff --git a/purchase_dashboard_advanced/static/description/assets/icons/wrench.png b/purchase_dashboard_advanced/static/description/assets/icons/wrench.png new file mode 100644 index 000000000..6c04dea0f Binary files /dev/null and b/purchase_dashboard_advanced/static/description/assets/icons/wrench.png differ diff --git a/purchase_dashboard_advanced/static/description/assets/misc/categories.png b/purchase_dashboard_advanced/static/description/assets/misc/categories.png new file mode 100644 index 000000000..bedf1e0b1 Binary files /dev/null and b/purchase_dashboard_advanced/static/description/assets/misc/categories.png differ diff --git a/purchase_dashboard_advanced/static/description/assets/misc/check-box.png b/purchase_dashboard_advanced/static/description/assets/misc/check-box.png new file mode 100644 index 000000000..42caf24b9 Binary files /dev/null and b/purchase_dashboard_advanced/static/description/assets/misc/check-box.png differ diff --git a/purchase_dashboard_advanced/static/description/assets/misc/compass.png b/purchase_dashboard_advanced/static/description/assets/misc/compass.png new file mode 100644 index 000000000..d5fed8faa Binary files /dev/null and b/purchase_dashboard_advanced/static/description/assets/misc/compass.png differ diff --git a/purchase_dashboard_advanced/static/description/assets/misc/corporate.png b/purchase_dashboard_advanced/static/description/assets/misc/corporate.png new file mode 100644 index 000000000..2eb13edbf Binary files /dev/null and b/purchase_dashboard_advanced/static/description/assets/misc/corporate.png differ diff --git a/purchase_dashboard_advanced/static/description/assets/misc/customer-support.png b/purchase_dashboard_advanced/static/description/assets/misc/customer-support.png new file mode 100644 index 000000000..79efc72ed Binary files /dev/null and b/purchase_dashboard_advanced/static/description/assets/misc/customer-support.png differ diff --git a/purchase_dashboard_advanced/static/description/assets/misc/cybrosys-logo.png b/purchase_dashboard_advanced/static/description/assets/misc/cybrosys-logo.png new file mode 100644 index 000000000..cc3cc0ccf Binary files /dev/null and b/purchase_dashboard_advanced/static/description/assets/misc/cybrosys-logo.png differ diff --git a/purchase_dashboard_advanced/static/description/assets/misc/features.png b/purchase_dashboard_advanced/static/description/assets/misc/features.png new file mode 100644 index 000000000..b41769f77 Binary files /dev/null and b/purchase_dashboard_advanced/static/description/assets/misc/features.png differ diff --git a/purchase_dashboard_advanced/static/description/assets/misc/logo.png b/purchase_dashboard_advanced/static/description/assets/misc/logo.png new file mode 100644 index 000000000..478462d3e Binary files /dev/null and b/purchase_dashboard_advanced/static/description/assets/misc/logo.png differ diff --git a/purchase_dashboard_advanced/static/description/assets/misc/pictures.png b/purchase_dashboard_advanced/static/description/assets/misc/pictures.png new file mode 100644 index 000000000..56d255fe9 Binary files /dev/null and b/purchase_dashboard_advanced/static/description/assets/misc/pictures.png differ diff --git a/purchase_dashboard_advanced/static/description/assets/misc/pie-chart.png b/purchase_dashboard_advanced/static/description/assets/misc/pie-chart.png new file mode 100644 index 000000000..426e05244 Binary files /dev/null and b/purchase_dashboard_advanced/static/description/assets/misc/pie-chart.png differ diff --git a/purchase_dashboard_advanced/static/description/assets/misc/right-arrow.png b/purchase_dashboard_advanced/static/description/assets/misc/right-arrow.png new file mode 100644 index 000000000..730984a06 Binary files /dev/null and b/purchase_dashboard_advanced/static/description/assets/misc/right-arrow.png differ diff --git a/purchase_dashboard_advanced/static/description/assets/misc/star.png b/purchase_dashboard_advanced/static/description/assets/misc/star.png new file mode 100644 index 000000000..2eb9ab29f Binary files /dev/null and b/purchase_dashboard_advanced/static/description/assets/misc/star.png differ diff --git a/purchase_dashboard_advanced/static/description/assets/misc/support.png b/purchase_dashboard_advanced/static/description/assets/misc/support.png new file mode 100644 index 000000000..4f18b8b82 Binary files /dev/null and b/purchase_dashboard_advanced/static/description/assets/misc/support.png differ diff --git a/purchase_dashboard_advanced/static/description/assets/misc/whatsapp.png b/purchase_dashboard_advanced/static/description/assets/misc/whatsapp.png new file mode 100644 index 000000000..d513a5356 Binary files /dev/null and b/purchase_dashboard_advanced/static/description/assets/misc/whatsapp.png differ diff --git a/purchase_dashboard_advanced/static/description/assets/modules/11.png b/purchase_dashboard_advanced/static/description/assets/modules/11.png new file mode 100644 index 000000000..f3c986fc1 Binary files /dev/null and b/purchase_dashboard_advanced/static/description/assets/modules/11.png differ diff --git a/purchase_dashboard_advanced/static/description/assets/modules/12.png b/purchase_dashboard_advanced/static/description/assets/modules/12.png new file mode 100644 index 000000000..25ed3e0b6 Binary files /dev/null and b/purchase_dashboard_advanced/static/description/assets/modules/12.png differ diff --git a/purchase_dashboard_advanced/static/description/assets/modules/13.png b/purchase_dashboard_advanced/static/description/assets/modules/13.png new file mode 100644 index 000000000..17c14989e Binary files /dev/null and b/purchase_dashboard_advanced/static/description/assets/modules/13.png differ diff --git a/purchase_dashboard_advanced/static/description/assets/modules/14.png b/purchase_dashboard_advanced/static/description/assets/modules/14.png new file mode 100644 index 000000000..5c56f0bcd Binary files /dev/null and b/purchase_dashboard_advanced/static/description/assets/modules/14.png differ diff --git a/purchase_dashboard_advanced/static/description/assets/modules/15.png b/purchase_dashboard_advanced/static/description/assets/modules/15.png new file mode 100644 index 000000000..6c8c8adc3 Binary files /dev/null and b/purchase_dashboard_advanced/static/description/assets/modules/15.png differ diff --git a/purchase_dashboard_advanced/static/description/assets/modules/16.png b/purchase_dashboard_advanced/static/description/assets/modules/16.png new file mode 100644 index 000000000..8eb34c3c5 Binary files /dev/null and b/purchase_dashboard_advanced/static/description/assets/modules/16.png differ diff --git a/purchase_dashboard_advanced/static/description/assets/screenshots/1.png b/purchase_dashboard_advanced/static/description/assets/screenshots/1.png new file mode 100644 index 000000000..6b98dfd89 Binary files /dev/null and b/purchase_dashboard_advanced/static/description/assets/screenshots/1.png differ diff --git a/purchase_dashboard_advanced/static/description/assets/screenshots/2.png b/purchase_dashboard_advanced/static/description/assets/screenshots/2.png new file mode 100644 index 000000000..dbaafed0d Binary files /dev/null and b/purchase_dashboard_advanced/static/description/assets/screenshots/2.png differ diff --git a/purchase_dashboard_advanced/static/description/assets/screenshots/3.png b/purchase_dashboard_advanced/static/description/assets/screenshots/3.png new file mode 100644 index 000000000..82fcc2ed7 Binary files /dev/null and b/purchase_dashboard_advanced/static/description/assets/screenshots/3.png differ diff --git a/purchase_dashboard_advanced/static/description/assets/screenshots/4.png b/purchase_dashboard_advanced/static/description/assets/screenshots/4.png new file mode 100644 index 000000000..d47061f31 Binary files /dev/null and b/purchase_dashboard_advanced/static/description/assets/screenshots/4.png differ diff --git a/purchase_dashboard_advanced/static/description/assets/screenshots/5.png b/purchase_dashboard_advanced/static/description/assets/screenshots/5.png new file mode 100644 index 000000000..df87f3b78 Binary files /dev/null and b/purchase_dashboard_advanced/static/description/assets/screenshots/5.png differ diff --git a/purchase_dashboard_advanced/static/description/assets/screenshots/6.png b/purchase_dashboard_advanced/static/description/assets/screenshots/6.png new file mode 100644 index 000000000..ab3a037fc Binary files /dev/null and b/purchase_dashboard_advanced/static/description/assets/screenshots/6.png differ diff --git a/purchase_dashboard_advanced/static/description/assets/screenshots/7.png b/purchase_dashboard_advanced/static/description/assets/screenshots/7.png new file mode 100644 index 000000000..2f31d86f7 Binary files /dev/null and b/purchase_dashboard_advanced/static/description/assets/screenshots/7.png differ diff --git a/purchase_dashboard_advanced/static/description/assets/screenshots/8.png b/purchase_dashboard_advanced/static/description/assets/screenshots/8.png new file mode 100644 index 000000000..0ffe9abfc Binary files /dev/null and b/purchase_dashboard_advanced/static/description/assets/screenshots/8.png differ diff --git a/purchase_dashboard_advanced/static/description/assets/screenshots/9.png b/purchase_dashboard_advanced/static/description/assets/screenshots/9.png new file mode 100644 index 000000000..6b5dad4cb Binary files /dev/null and b/purchase_dashboard_advanced/static/description/assets/screenshots/9.png differ diff --git a/purchase_dashboard_advanced/static/description/assets/screenshots/hero.gif b/purchase_dashboard_advanced/static/description/assets/screenshots/hero.gif new file mode 100644 index 000000000..a02782268 Binary files /dev/null and b/purchase_dashboard_advanced/static/description/assets/screenshots/hero.gif differ diff --git a/purchase_dashboard_advanced/static/description/banner.jpg b/purchase_dashboard_advanced/static/description/banner.jpg new file mode 100644 index 000000000..c0c596512 Binary files /dev/null and b/purchase_dashboard_advanced/static/description/banner.jpg differ diff --git a/purchase_dashboard_advanced/static/description/icon.png b/purchase_dashboard_advanced/static/description/icon.png new file mode 100644 index 000000000..68c87e3a7 Binary files /dev/null and b/purchase_dashboard_advanced/static/description/icon.png differ diff --git a/purchase_dashboard_advanced/static/description/index.html b/purchase_dashboard_advanced/static/description/index.html new file mode 100644 index 000000000..48b4bda17 --- /dev/null +++ b/purchase_dashboard_advanced/static/description/index.html @@ -0,0 +1,728 @@ +
+ +
+ +
+
+ Community +
+
+ Enterprise +
+
+ Odoo.Sh +
+
+
+ + + +

+ Advanced Purchase Dashboard +

+

+ A Dashboard For Keeping Track and Evaluate Various Aspects of Spending + on Purchases. +

+ + + +
+ + +
+
+ +
+

+ Explore This + Module

+
+ + + + +
+
+ +
+

+ Overview +

+
+
+
+ A dashboard for keeping track and evaluate various aspects of spending + on purchases , We can track Monthly , yearly and vendor details , top + products , priority products and upcoming details of purchase. +
+
+ + + +
+
+ +
+

+ Features +

+
+
+
+
+ + Detailed View for Purchases + +
+
+ + Responsive Views +
+
+ + Different Types of Graphs + +
+
+ + Different Types of Tables for Analysing the Records +
+ +
+
+ + + + +
+
+

+ Screenshots +

+
+
+

+ Dashboard View

+ + + +
+
+

+ Dashboard Tiles

+

+ The Tiles display a list of the purchase orders,Total amount, + Priority orders and Vendors based on today,This Week, This Month + and This Year +

+ +
+
+

+ Top Charts

+

+ This is the pie graph for showing the top products, vendors, + purchase representatives +

+ +
+ +
+

+ Purchases By Month

+

+ This bar graph shows the purchases made in each month. +

+ +
+ +
+

+ Purchase Vendor Analysis

+

+ This is a line graph containing multiple attributes on it. This + will show the purchases and its states based on the vendors. +

+ +
+
+

+ Product Category Analysis

+

+ This is a line graph used to plot the product category analysis. It + will demonstrate the quantity of products based on the category + selected. +

+ +
+ +
+

+ Pending Arrivals

+

+ There are pending purchase orders with partial deliveries, where + some products have been delivered, but the rest are still awaiting + delivery. +

+ +
+
+

+ Upcoming Arrivals

+

+ This is the table of contents describing the purchase order that is + yet to arrive. +

+ +
+ + + + +
+
+ +
+

+ Related + Products +

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

+ Our Services +

+
+ +
+
+
+
+ +
+
+ Odoo + Customization
+
+ +
+
+ +
+
+ Odoo + Implementation
+
+ +
+
+ +
+
+ Odoo + Support
+
+ + +
+
+ +
+
+ Hire + Odoo + Developer
+
+ +
+
+ +
+
+ Odoo + Integration
+
+ +
+
+ +
+
+ Odoo + Migration
+
+ + +
+
+ +
+
+ Odoo + Consultancy
+
+ +
+
+ +
+
+ Odoo + Implementation
+
+ +
+
+ +
+
+ Odoo + Licensing Consultancy
+
+
+ +
+ + + + + +
+
+ +
+

+ Our + Industries +

+
+ +
+
+
+
+ +
+ Trading +
+

+ Easily procure + and + sell your products

+
+
+ +
+
+ +
+ POS +
+

+ Easy + configuration + and convivial experience

+
+
+ +
+
+ +
+ Education +
+

+ A platform for + educational management

+
+
+ +
+
+ +
+ Manufacturing +
+

+ Plan, track and + schedule your operations

+
+
+ +
+
+ +
+ E-commerce & Website +
+

+ Mobile + friendly, + awe-inspiring product pages

+
+
+ +
+
+ +
+ Service Management +
+

+ Keep track of + services and invoice

+
+
+ +
+
+ +
+ Restaurant +
+

+ Run your bar or + restaurant methodically

+
+
+ +
+
+ +
+ Hotel Management +
+

+ An + all-inclusive + hotel management application

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

+ Support +

+
+
+
+
+
+
+ +
+
+

Need Help?

+

Got questions or need + help? Get in touch.

+ +

+ odoo@cybrosys.com

+
+
+
+
+
+
+
+ +
+
+

WhatsApp

+

Say hi to us on + WhatsApp!

+ +

+ +91 86068 + 27707

+
+
+
+
+
+
+
+ +
+
+
+ +
diff --git a/purchase_dashboard_advanced/static/src/css/style.css b/purchase_dashboard_advanced/static/src/css/style.css new file mode 100644 index 000000000..4654394a0 --- /dev/null +++ b/purchase_dashboard_advanced/static/src/css/style.css @@ -0,0 +1,333 @@ +.oh_dashboards{ + padding-top :15px; + background-color: #f8faff !important; +} + +.oh-card h4 { + font-size: 1.1rem; +} + +/* Widget One +---------------------------*/ +.stat-content { + display: inline-block; + width: 66%; +} +.stat-icon{ + display: inline-block; +} + +.stat-widget-one .stat-icon { + vertical-align: top; + margin: auto; + width: 100%; + color: #01c490; +} +.oh-card { + + padding-top: 0px; + padding: 0px; + margin-bottom: 1.5rem; + border-radius: 0px; + box-shadow: none; + background: none; + transition: transform 0.2s ease, box-shadow 0.2s ease; + will-change: transform, box-shadow; + +} +.oh-card:hover { + + transform: translateY(-2px) translateZ(0) !important; + box-shadow: 0 10px 10px 0 rgba(62, 57, 107, 0.12), 0 0 0 transparent !important; + +} +.oh-data { + + margin-top: 4.5%; + +} + +.oh-data .oh-card { + + transition: transform 0.2s ease, box-shadow 0.2s ease; + will-change: transform, box-shadow; + box-shadow: 0 10px 40px 0 rgba(62,57,107,0.07), 0 2px 9px 0 rgba(62,57,107,0.06); + +} +.stat-widget-one .stat-icon i { + font-size: 30px; + font-weight: 900; + display: inline-block; + color: #01c490;} + +.stat-widget-one .stat-text { + font-size: 14px; + color: #868e96; + font-weight: bold; +} +.stat-widget-one .stat-digit { + font-size: 24px; + color: #02448b; } + +.stat-count { + font-size: 20px; + text-align: center; + color: #00438b;} + +.stat-title { + font-size: 17px; + text-align: center; + color: #00438b; } + + .oh-data .stat-icon { + + width: 30%; + height: 85px; + text-align: center; + background: #ff8762; + color: #fff; + width: 32%; + padding-top: 2%; + font-size: xxx-large; + +} +.stat-widget-one .stat-text { + font-size: 14px; + color: #ff8762; + margin-top: 2.3rem; + margin-left: 1rem; +} + +.stat-widget-one .stat-digit { + font-size: 26px; + color:#993232; + margin-left: 1rem; + margin-top: -1px; + font-family: initial +} + +.stat-widget-one .stat-icon i { + + font-size: 25px; + font-weight: 900; + display: inline-block; + color: #fff; + +} +.stat-widget-one { + + background-color: white; + text-align: left; + +} +.stat-widget-one { + width: 100%; +} +.oh-data .stat-icon { + + width: 30%; + height: 85px; + text-align: center; + padding-top: 2%; + +} + +h4 .stat-count { + font-size: 17px; + text-align: center; + color: #000 !important; + margin-top: 0px; + width: 100%; + float: left; + margin: 0; +} + +.stat-head { + text-align: left !important; + font-weight: 300; + font-size: 15px; + margin-bottom: 25px; + margin-left: 24px; + width: 100%; +} +.oh-card-body { + display: flex; + justify-content: space-between; + align-items: center; +} + +.o_action_manager{ + overflow-y: scroll !important; + max-width:100%; + } + +.stat_count{ + margin-top: -89px; + margin-left: 35px; + font-size: 33px; +} + + +.stat-head { + text-align: left !important; + font-weight: 300; + font-size: 18px; + margin-bottom: 25px; + margin-left: 24px; + width: 100%; + margin-top: 57px; + color: black; +} +#product_categ_selection{ + color:black; + background-color : white; + +} +.top_chart{ + height: fit-content; +} +.row.main-section { + margin-right: 0px; !important; +} + +.chart-container { + border-radius: 0.3rem; + padding: 1rem; + margin: 1rem auto; +} + +.chart-container.card-shadow { + height: 100%; +} + +.half_chart.chart-container.card-shadow { + height: 49%; +} +#container { + height: 400px; +} +/*vendor analysis*/ + + +#vendor_selection { + transform: translateX(223%) translateY(-198%); + background: white; + width:30%; + color:black + +} + +/*category analysis*/ + +/*table*/ +.graph_details_table{ +position: absolute; + top: 45px; + right: 15px; + + background-color: white; + border-collapse: collapse; + border: 1px solid #ddd; + +} + +.graph_details_table th{ +background-color:#67b7dc; +color: white; + width: 250px; + height: 30px; +} + +.graph_details_table td{ +border: 1px solid #ddd; +height: 20px; +} + +.graph_details_table tr:nth-child(even){background-color: #f2f2f2;} + +.graph_details_table tr:hover {background-color: #ddd;} + +.chart-container h2 { + font-weight: 700; + font-size: 1.690rem; +} + +.hr_notification { + background: #f6f7fa; + transition: transform 0.2s ease, box-shadow 0.2s ease; + will-change: transform, box-shadow; + box-shadow: 0 10px 40px 0 rgba(62,57,107,0.07), 0 2px 9px 0 rgba(62,57,107,0.06); + height: 316px; + overflow-y: auto; + margin-bottom: 15px; +} +.hr_notification .media { + border-bottom: 1px solid #e6e6e6; + padding-bottom: 6px; + margin-bottom: 10px; +} +hr_notification .text-color.display-6 { + margin: 0px 0 3px; + color: #2d2d2d; +} +.hr_notification p { + margin: 0 0 1px; + color: #666; + font-size: 10px; +} +.chart_head { + font-size: 17px; + text-align: center; + padding: 12px 0; + color: #fff; + font-weight: 300; + background: #5ebade; + margin-bottom: 9px; +} +.hr_notification_head { + font-size: 17px; + text-align: center; + padding: 12px 0; + color: #fff; + font-weight: 300; + background: #5ebade; + margin-bottom: 9px; +} +@media (max-width: 767.98px) { + .oh-card{ + width: 100% !important; + } + .dashboard_main_section .form-control{ + width: 85% !important; + margin: 15px auto; + } + .sale_details{ + position: relative; + top: 0px !important; + right: 0px !important; + } + .selling_product_graph_view{ + margin-top: 5rem; + } + .hr_notification{ + min-width: 90% !important; + } +} +.hr_notification{ + min-width: 425.683px; + min-height: 316px; + max-width: 100%; +} +.media-body{ + + background: #466b8d; + float: left; + margin: 0; + width: 100% + +} +.category{ + width: 30%; + transform: translateX(222%) translateY(-154%); + background: white; +} diff --git a/purchase_dashboard_advanced/static/src/js/dashboard.js b/purchase_dashboard_advanced/static/src/js/dashboard.js new file mode 100644 index 000000000..3b63a3d88 --- /dev/null +++ b/purchase_dashboard_advanced/static/src/js/dashboard.js @@ -0,0 +1,360 @@ +/* @odoo-module*/ +import { registry } from '@web/core/registry'; +import { Component , useRef , useState, onWillStart ,onMounted} from '@odoo/owl' +import { useService } from "@web/core/utils/hooks"; +import { PurchaseTiles } from './purchaseTile' +import { session } from "@web/session"; + var core = require('web.core'); + var _t = core._t; + /** + * Class representing the purchase dashboard component. + */ +class purchaseDashboard extends Component { + setup() { + /** + * State variable for purchase-related data. + * @type {Object} + */ + this.purchase = useState({}) + this.action = useService('action') + this.selectedVendor = useState({ selected: null }) + this.root = useRef('root'); + this.orm = useService('orm') + onWillStart(async ()=> { + this.purchase.monthlyData = await this.orm.call('purchase.order','get_orders_by_month',[]) + this.purchase.orders = await this.orm.call('purchase.order','get_monthly_data',['this_month']) + this.purchase.pending = await this.orm.call('purchase.order','get_pending_purchase_data',[]) + this.purchase.upcoming = await this.orm.call('purchase.order','get_upcoming_purchase_data',[]) + this.purchase.topChart = await this.orm.call('purchase.order','get_top_chart_data',['top_product']) + this.purchase.vendors = await this.orm.call('purchase.order','purchase_vendors',[]) + this.purchase.categoryAnalysis = await this.orm.call('purchase.order.line','product_categ_analysis',[]) + this.selectedVendor.selected = this.purchase.vendors[0].id + await this.getRenderVendorAnalysisData() + }) + onMounted(() => { + this.renderProductAnalysis(); + this.renderVendorAnalysis(); + this.renderByMonthPurchase(); + this.renderTopProduct(); + }); + console.warn(this) + } + /** + * Handler for selecting a mode. + * @param {Object} event - The event object. + */ + async handleOnchangeSelect(event){ + const option = event.target.value; + this.purchase.orders = await this.orm.call('purchase.order','get_select_mode_data',[option]) + } + /** + * Handler for changing the product category. + * @param {Object} event - The event object. + */ + async handleOnChangeProductCategory(event){ + const category_id =parseInt(event.target.value); + const { count, name } = await this.orm.call('purchase.order.line','product_categ_data',[category_id]) + this.purchase.categoryAnalysis.values.name = name; + this.purchase.categoryAnalysis.count = count; + this.renderProductAnalysis() + } + /** + * Render the product analysis chart. + */ + renderProductAnalysis(){ + const ctx = this.root.el.querySelector("#product_categ_purchases") + const label =this.purchase.categoryAnalysis.values.name.map((obj) => obj.en_US) + var count = this.purchase.categoryAnalysis.count; + new Chart(ctx, { + type: 'line', + data: { + labels: label, + datasets: [{ + label: 'Quantity Done', + data: count, + backgroundColor: '#003f5c', + borderColor: '#003f5c', + barPercentage: 0.5, + barThickness: 6, + maxBarThickness: 8, + minBarLength: 0, + borderWidth: 1, + type: 'line', + fill: false + }] + }, + options: { + scales: { + y: { + beginAtZero: true + }, + }, + responsive: true, + maintainAspectRatio: false, + } + }); + }; + /** + * Fetch and render vendor analysis data. + */ + async getRenderVendorAnalysisData(){ + this.purchase.renderVendorAnalysisData = await this.orm.call('purchase.order','purchase_vendor_details',[this.selectedVendor.selected]) + } + /** + * Handler for changing the vendor analysis. + * @param {Object} event - The event object. + */ + async handleOnChangeVendorAnalysis(event){ + this.selectedVendor.selected = parseInt(event.target.value) + await this.getRenderVendorAnalysisData() + this.renderVendorAnalysis() + } + /** + * Render the vendor analysis chart. + */ + renderVendorAnalysis(){ + const ctx = this.root.el.querySelector("#purchase_vendors") + let name = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] + let sum = this.purchase.renderVendorAnalysisData.purchase_amount + let po_count = this.purchase.renderVendorAnalysisData.po_count + let draft_total = this.purchase.renderVendorAnalysisData.draft_amount + let draft_count = this.purchase.renderVendorAnalysisData.draft_count + let approve_amount = this.purchase.renderVendorAnalysisData.approve_amount + let approve_count = this.purchase.renderVendorAnalysisData.approve_count + let cancel_amount = this.purchase.renderVendorAnalysisData.cancel_amount + let cancel_count = this.purchase.renderVendorAnalysisData.cancel_count + let j = 0; + if (window.myChart_year != undefined) + window.myChart_year.destroy(); + window.myChart_year = new Chart(ctx, { + type: 'line', + data: { + labels: name, + datasets: [ + { + label: 'Purchase Order Total', + data: sum, + backgroundColor: '#0000ff', + borderColor: '#0000ff', + barPercentage: 0.5, + barThickness: 6, + maxBarThickness: 8, + minBarLength: 0, + borderWidth: 1, + type: 'line', + fill: false + }, + { + label: 'Draft Order Total', + data: draft_total, + backgroundColor: '#71d927', + borderColor: '#71d927', + barPercentage: 0.5, + barThickness: 6, + maxBarThickness: 8, + minBarLength: 0, + borderWidth: 1, + type: 'line', + fill: false + }, + { + label: 'To Approve', + data: approve_amount, + backgroundColor: '#ff0066', + borderColor: '#ff0066', + barPercentage: 0.5, + barThickness: 6, + maxBarThickness: 8, + minBarLength: 0, + borderWidth: 1, + type: 'line', + fill: false + }, + { + label: 'Cancelled Orders', + data: cancel_amount, + backgroundColor: '#ffff1a', + borderColor: '#ffff1a', + barPercentage: 0.5, + barThickness: 6, + maxBarThickness: 8, + minBarLength: 0, + borderWidth: 1, + type: 'line', + fill: false + }, + ] + }, + options: { + scales: { + y: { + beginAtZero: true + }, + }, + responsive: true, + maintainAspectRatio: false, + } + }); + } + /** + * Render the purchase data by month. + */ + renderByMonthPurchase(){ + const ctx = this.root.el.querySelector("#canvas") + var month = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] + var count = this.purchase.monthlyData.count; + new Chart(ctx, { + type: 'bar', + data: { + labels: month, + datasets: [{ + label: 'Count', + data: count, + backgroundColor: '#ac3973', + borderColor: '#ac3973', + barPercentage: 0.5, + barThickness: 6, + maxBarThickness: 8, + minBarLength: 0, + borderWidth: 1, + type: 'bar', + fill: false + }] + }, + options: { + scales: { + y: { + beginAtZero: true + }, + }, + responsive: true, + maintainAspectRatio: false, + } + }); + } + /** + * Handler for changing the top product. + * @param {Object} event - The event object. + */ + async handleOnChangeTopProduct(event){ + const selected_id = event.target.value; + this.purchase.topChart = await this.orm.call('purchase.order','get_top_chart_data',[selected_id]) + this.renderTopProduct() + } + /** + * Render the top product chart. + */ + renderTopProduct(){ + const ctx = this.root.el.querySelector(".top_pie_chart") + if (window.myCharts_top_priority != undefined ){ + window.myCharts_top_priority.destroy(); + window.myCharts_top_priority = new Chart(ctx, { + type: "doughnut", + data: data, + options: options + }); + } + const label = this.purchase.topChart[1].map((item) => item.en_US || item ); + const datas = this.purchase.topChart[0]; + var background_color = []; + this.purchase.topChart[0].forEach((div) => { + var randomColor = '#' + Math.floor(Math.random() * 16777215).toString(16); + background_color.push(randomColor) + }); + var randomColor= background_color + var data = { + labels : label, + datasets: [{ + label: "", + data: datas, + backgroundColor:randomColor, + borderColor:randomColor, + borderWidth: 1 + },] + }; + var options = { + responsive: true, + title: false, + legend: { + display: true, + position: "right", + labels: { + fontColor: "#333", + fontSize: 16 + } + }, + scales: { + yAxes: [{ + gridLines: { + color: "rgba(0, 0, 0, 0)", + display: false, + }, + ticks: { + min: 0, + display: false, + } + }] + } + }; + window.myCharts_top_priority = new Chart(ctx, { + type: "doughnut", + data: data, + options: options + }); + } + /** + * Navigate to the backend for purchase orders. + */ + goBackend(){ + this.action.doAction({ + type: 'ir.actions.act_window', + name: _t('Purchase Order'), + res_model: 'purchase.order', + view_mode: 'tree,form', + views: [[false, 'list'],[false, 'form']], + target: 'self', + domain: [['state','in', ['purchase', 'done']]], + }); + } + /** + * Handle priority orders. + */ + priorityOrders(){ + if(this.priority_orders) { + var options = { + on_reverse_breadcrumb: self.on_reverse_breadcrumb, + }; + } + this.action.doAction({ + type: 'ir.actions.act_window', + name: _t("Priority Order"), + res_model: 'purchase.order', + view_mode: 'tree,form', + views: [[false, 'list'],[false, 'form']], + target: 'self', + domain: [['priority','=', 1]], + }); + } + /** + * Navigate to the vendor backend. + */ + vendorBackend(){ + this.action.doAction({ + type: 'ir.actions.act_window', + name: _t('Vendor'), + res_model: 'res.partner', + view_mode: 'tree,form', + views: [[false, 'list'],[false, 'form']], + target: 'self', + domain: [['id', "in", this.purchase.orders.vendor_id]], + }); + } +} +/** + * Template for the purchase dashboard component. + */ + purchaseDashboard.template = 'PurchaseDashboard' + purchaseDashboard.components = { + PurchaseTiles + } +registry.category('actions').add('purchase_dashboard', purchaseDashboard); diff --git a/purchase_dashboard_advanced/static/src/js/purchaseTile.js b/purchase_dashboard_advanced/static/src/js/purchaseTile.js new file mode 100644 index 000000000..58e028a7c --- /dev/null +++ b/purchase_dashboard_advanced/static/src/js/purchaseTile.js @@ -0,0 +1,10 @@ +/* @odoo-module*/ +import { Component } from '@odoo/owl' +/** + * Class representing purchase tiles component. + */ +export class PurchaseTiles extends Component {} +/** + * Template for the purchase tiles component. + */ +PurchaseTiles.template = 'PurchaseTiles' diff --git a/purchase_dashboard_advanced/static/src/xml/dashboard.xml b/purchase_dashboard_advanced/static/src/xml/dashboard.xml new file mode 100644 index 000000000..7d585affd --- /dev/null +++ b/purchase_dashboard_advanced/static/src/xml/dashboard.xml @@ -0,0 +1,248 @@ + + + + + +
+
+
+
+
+
+

Purchase Dashboard

+
+ +
+
+ +
+
+ + + + + + +
+
+
+
+
+
+
+ Top Charts +
+
+ +
+
+ +
+
+
+ +
+
+
+
+
+ Purchases By Month +
+
+ +
+
+
+
+
+
+
+
+
+
+ Purchases Vendor Analysis +
+
+ +
+
+ +
+
+
+
+
+
+ Product Category Analysis +
+
+
+ +
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+ Pending Arrivals +
+
+
+
+
+ + + + + + + + + + + + + + + + + +
OrderVendorAmountDate PlannedState
+
+
+
+
+
+
+
+
+
+ Upcoming Arrivals +
+
+
+
+
+ + + + + + + + + + + + + + + + + +
OrderVendorAmountDate PlannedState
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/purchase_dashboard_advanced/static/src/xml/purchaseTile.xml b/purchase_dashboard_advanced/static/src/xml/purchaseTile.xml new file mode 100644 index 000000000..6b9f71500 --- /dev/null +++ b/purchase_dashboard_advanced/static/src/xml/purchaseTile.xml @@ -0,0 +1,25 @@ + + + + + +
+
+
+
+
+ +
+
+
+
+ +
+
+
+
+
+
+ + diff --git a/purchase_dashboard_advanced/views/purchase_dashboard_advanced_menus.xml b/purchase_dashboard_advanced/views/purchase_dashboard_advanced_menus.xml new file mode 100644 index 000000000..442123442 --- /dev/null +++ b/purchase_dashboard_advanced/views/purchase_dashboard_advanced_menus.xml @@ -0,0 +1,13 @@ + + + + + Purchase + purchase_dashboard + + + +