diff --git a/odoo_project_stat/README.rst b/odoo_project_stat/README.rst new file mode 100644 index 00000000..f6f75de5 --- /dev/null +++ b/odoo_project_stat/README.rst @@ -0,0 +1,104 @@ +.. image:: https://odoo-community.org/readme-banner-image + :target: https://odoo-community.org/get-involved?utm_source=readme + :alt: Odoo Community Association + +================== +Odoo Project Stats +================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:baa0b975fb4f8145367be53b53084d70771ccdcf9846a39db1dc337a6d1d111e + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fmodule--composition--analysis-lightgray.png?logo=github + :target: https://github.com/OCA/module-composition-analysis/tree/19.0/odoo_project_stat + :alt: OCA/module-composition-analysis +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/module-composition-analysis-19-0/module-composition-analysis-19-0-odoo_project_stat + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/module-composition-analysis&target_branch=19.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module allows to generate and render some pie charts for your Odoo +projects. + +It consists of two pie charts on the project form view: + +- number of installed modules +- number of lines of code + +Both measures are split based on some criteria (configurable), by +default: + +- Odoo CE +- Odoo Enterprise +- OCA +- Community (not OCA) +- Specific code +- Misc. (everything not part of other criteria above) + +Each criteria having a rendering color used on the charts. + +|Project Pie Charts| + +.. |Project Pie Charts| image:: https://raw.githubusercontent.com/OCA/module-composition-analysis/19.0/odoo_project_stat/static/img/project_pie_charts.png + +**Table of contents** + +.. contents:: + :local: + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* Camptocamp + +Contributors +------------ + +- Camptocamp + + - Sébastien Alix + +Maintainers +----------- + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/module-composition-analysis `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/odoo_project_stat/__init__.py b/odoo_project_stat/__init__.py new file mode 100644 index 00000000..aee8895e --- /dev/null +++ b/odoo_project_stat/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import wizards diff --git a/odoo_project_stat/__manifest__.py b/odoo_project_stat/__manifest__.py new file mode 100644 index 00000000..e22de68b --- /dev/null +++ b/odoo_project_stat/__manifest__.py @@ -0,0 +1,24 @@ +# Copyright 2023 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) +{ + "name": "Odoo Project Stats", + "summary": "Get some stats about your Odoo Projects.", + "version": "19.0.1.0.0", + "category": "Tools", + "author": "Camptocamp, Odoo Community Association (OCA)", + "website": "https://github.com/OCA/module-composition-analysis", + "data": [ + "security/ir.model.access.csv", + "data/odoo_project_stat_config.xml", + "views/odoo_project.xml", + "views/odoo_project_stat_config.xml", + ], + "installable": True, + "depends": [ + "odoo_project", + # OCA/web + "web_widget_plotly_chart", + ], + "external_dependencies": {"python": ["plotly"]}, + "license": "AGPL-3", +} diff --git a/odoo_project_stat/data/odoo_project_stat_config.xml b/odoo_project_stat/data/odoo_project_stat_config.xml new file mode 100644 index 00000000..cf9d81af --- /dev/null +++ b/odoo_project_stat/data/odoo_project_stat_config.xml @@ -0,0 +1,75 @@ + + + + + + Odoo CE + #985184 + + + + + + Odoo Enterprise + #1ad3bb + + + + + + OCA + #a4b0f1 + + + + + + Community + #c3ccfc + + + + + + Specific + #ff680a + + + + + + Misc. + #f3f4f6 + + + diff --git a/odoo_project_stat/i18n/it.po b/odoo_project_stat/i18n/it.po new file mode 100644 index 00000000..0c68cfac --- /dev/null +++ b/odoo_project_stat/i18n/it.po @@ -0,0 +1,181 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * odoo_project_stat +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: it\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +#. module: odoo_project_stat +#: model:ir.model.fields,field_description:odoo_project_stat.field_odoo_project_stat__color +#: model:ir.model.fields,field_description:odoo_project_stat.field_odoo_project_stat_config__color +msgid "Color" +msgstr "" + +#. module: odoo_project_stat +#: model:ir.model.fields,field_description:odoo_project_stat.field_odoo_project_stat__create_uid +#: model:ir.model.fields,field_description:odoo_project_stat.field_odoo_project_stat_config__create_uid +msgid "Created by" +msgstr "" + +#. module: odoo_project_stat +#: model:ir.model.fields,field_description:odoo_project_stat.field_odoo_project_stat__create_date +#: model:ir.model.fields,field_description:odoo_project_stat.field_odoo_project_stat_config__create_date +msgid "Created on" +msgstr "" + +#. module: odoo_project_stat +#: model:ir.model.fields,field_description:odoo_project_stat.field_odoo_project_stat__date +msgid "Date" +msgstr "" + +#. module: odoo_project_stat +#: model:ir.model.fields,field_description:odoo_project_stat.field_odoo_project_stat__display_name +#: model:ir.model.fields,field_description:odoo_project_stat.field_odoo_project_stat_config__display_name +msgid "Display Name" +msgstr "" + +#. module: odoo_project_stat +#: model:ir.model.fields,field_description:odoo_project_stat.field_odoo_project_stat_config__domain +msgid "Domain" +msgstr "" + +#. module: odoo_project_stat +#: model:ir.model.fields,field_description:odoo_project_stat.field_odoo_project_stat__id +#: model:ir.model.fields,field_description:odoo_project_stat.field_odoo_project_stat_config__id +msgid "ID" +msgstr "" + +#. module: odoo_project_stat +#: model:ir.model,name:odoo_project_stat.model_odoo_project_import_modules +msgid "Import modules for an Odoo project" +msgstr "" + +#. module: odoo_project_stat +#: model:ir.model.fields,field_description:odoo_project_stat.field_odoo_project_stat____last_update +#: model:ir.model.fields,field_description:odoo_project_stat.field_odoo_project_stat_config____last_update +msgid "Last Modified on" +msgstr "" + +#. module: odoo_project_stat +#: model:ir.model.fields,field_description:odoo_project_stat.field_odoo_project_stat__write_uid +#: model:ir.model.fields,field_description:odoo_project_stat.field_odoo_project_stat_config__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: odoo_project_stat +#: model:ir.model.fields,field_description:odoo_project_stat.field_odoo_project_stat__write_date +#: model:ir.model.fields,field_description:odoo_project_stat.field_odoo_project_stat_config__write_date +msgid "Last Updated on" +msgstr "" + +#. module: odoo_project_stat +#: model:ir.model.fields,field_description:odoo_project_stat.field_odoo_project__chart_sloc +msgid "Lines of Code Chart" +msgstr "" + +#. module: odoo_project_stat +#: model:ir.model.fields,field_description:odoo_project_stat.field_odoo_project_stat__sloc +msgid "Lines of code" +msgstr "" + +#. module: odoo_project_stat +#: model:ir.model.fields,field_description:odoo_project_stat.field_odoo_project__chart_modules_count +msgid "Modules Chart" +msgstr "" + +#. module: odoo_project_stat +#: model:ir.model.fields,field_description:odoo_project_stat.field_odoo_project_stat__modules_count +msgid "Modules Count" +msgstr "" + +#. module: odoo_project_stat +#: model:ir.model,name:odoo_project_stat.model_odoo_project +msgid "Odoo Project" +msgstr "" + +#. module: odoo_project_stat +#: model:ir.model,name:odoo_project_stat.model_odoo_project_stat_config +msgid "Odoo Project Stat Config" +msgstr "" + +#. module: odoo_project_stat +#: model:ir.model,name:odoo_project_stat.model_odoo_project_stat +msgid "Odoo Project Stats" +msgstr "" + +#. module: odoo_project_stat +#: model:ir.model.constraint,message:odoo_project_stat.constraint_odoo_project_stat_config_residual_uniq +msgid "Only one configuration should exist for residual modules." +msgstr "" + +#. module: odoo_project_stat +#: model:ir.model.fields,field_description:odoo_project_stat.field_odoo_project_stat__odoo_project_id +msgid "Project" +msgstr "" + +#. module: odoo_project_stat +#: model:ir.model.fields,field_description:odoo_project_stat.field_odoo_project_stat__config_id +msgid "Project Stat Configuration" +msgstr "" + +#. module: odoo_project_stat +#: model:ir.actions.act_window,name:odoo_project_stat.odoo_project_stat_config_action +#: model:ir.ui.menu,name:odoo_project_stat.odoo_project_stat_config_menu +msgid "Project Stats" +msgstr "" + +#. module: odoo_project_stat +#: model_terms:ir.ui.view,arch_db:odoo_project_stat.odoo_project_view_form +msgid "Refresh" +msgstr "" + +#. module: odoo_project_stat +#: model:ir.model.fields,field_description:odoo_project_stat.field_odoo_project_stat_config__residual +msgid "Residual" +msgstr "" + +#. module: odoo_project_stat +#: model:ir.model.fields,field_description:odoo_project_stat.field_odoo_project_stat__sequence +#: model:ir.model.fields,field_description:odoo_project_stat.field_odoo_project_stat_config__sequence +msgid "Sequence" +msgstr "" + +#. module: odoo_project_stat +#: model:ir.model.fields,field_description:odoo_project_stat.field_odoo_project__stats_ids +#: model_terms:ir.ui.view,arch_db:odoo_project_stat.odoo_project_view_form +msgid "Stats" +msgstr "" + +#. module: odoo_project_stat +#: model:ir.model.constraint,message:odoo_project_stat.constraint_odoo_project_stat_odoo_project_config_date_uniq +msgid "This project stats record already exists." +msgstr "" + +#. module: odoo_project_stat +#: model:ir.model.fields,field_description:odoo_project_stat.field_odoo_project_stat__name +#: model:ir.model.fields,field_description:odoo_project_stat.field_odoo_project_stat_config__name +msgid "Title" +msgstr "" + +#. module: odoo_project_stat +#: model_terms:ir.ui.view,arch_db:odoo_project_stat.odoo_project_view_form +msgid "" +"Useful to get accurate figures if modules have been updated in their " +"respective repositories." +msgstr "" + +#. module: odoo_project_stat +#: model:ir.model.fields,help:odoo_project_stat.field_odoo_project_stat_config__residual +msgid "" +"Will catch all modules that doesn't match other rules (domain field will be " +"ignored if enabled)." +msgstr "" diff --git a/odoo_project_stat/i18n/odoo_project_stat.pot b/odoo_project_stat/i18n/odoo_project_stat.pot new file mode 100644 index 00000000..f80f7442 --- /dev/null +++ b/odoo_project_stat/i18n/odoo_project_stat.pot @@ -0,0 +1,180 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * odoo_project_stat +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: odoo_project_stat +#: model:ir.model.fields,field_description:odoo_project_stat.field_odoo_project_stat__color +#: model:ir.model.fields,field_description:odoo_project_stat.field_odoo_project_stat_config__color +msgid "Color" +msgstr "" + +#. module: odoo_project_stat +#: model:ir.model.fields,field_description:odoo_project_stat.field_odoo_project_stat__create_uid +#: model:ir.model.fields,field_description:odoo_project_stat.field_odoo_project_stat_config__create_uid +msgid "Created by" +msgstr "" + +#. module: odoo_project_stat +#: model:ir.model.fields,field_description:odoo_project_stat.field_odoo_project_stat__create_date +#: model:ir.model.fields,field_description:odoo_project_stat.field_odoo_project_stat_config__create_date +msgid "Created on" +msgstr "" + +#. module: odoo_project_stat +#: model:ir.model.fields,field_description:odoo_project_stat.field_odoo_project_stat__date +msgid "Date" +msgstr "" + +#. module: odoo_project_stat +#: model:ir.model.fields,field_description:odoo_project_stat.field_odoo_project_stat__display_name +#: model:ir.model.fields,field_description:odoo_project_stat.field_odoo_project_stat_config__display_name +msgid "Display Name" +msgstr "" + +#. module: odoo_project_stat +#: model:ir.model.fields,field_description:odoo_project_stat.field_odoo_project_stat_config__domain +msgid "Domain" +msgstr "" + +#. module: odoo_project_stat +#: model:ir.model.fields,field_description:odoo_project_stat.field_odoo_project_stat__id +#: model:ir.model.fields,field_description:odoo_project_stat.field_odoo_project_stat_config__id +msgid "ID" +msgstr "" + +#. module: odoo_project_stat +#: model:ir.model,name:odoo_project_stat.model_odoo_project_import_modules +msgid "Import modules for an Odoo project" +msgstr "" + +#. module: odoo_project_stat +#: model:ir.model.fields,field_description:odoo_project_stat.field_odoo_project_stat____last_update +#: model:ir.model.fields,field_description:odoo_project_stat.field_odoo_project_stat_config____last_update +msgid "Last Modified on" +msgstr "" + +#. module: odoo_project_stat +#: model:ir.model.fields,field_description:odoo_project_stat.field_odoo_project_stat__write_uid +#: model:ir.model.fields,field_description:odoo_project_stat.field_odoo_project_stat_config__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: odoo_project_stat +#: model:ir.model.fields,field_description:odoo_project_stat.field_odoo_project_stat__write_date +#: model:ir.model.fields,field_description:odoo_project_stat.field_odoo_project_stat_config__write_date +msgid "Last Updated on" +msgstr "" + +#. module: odoo_project_stat +#: model:ir.model.fields,field_description:odoo_project_stat.field_odoo_project__chart_sloc +msgid "Lines of Code Chart" +msgstr "" + +#. module: odoo_project_stat +#: model:ir.model.fields,field_description:odoo_project_stat.field_odoo_project_stat__sloc +msgid "Lines of code" +msgstr "" + +#. module: odoo_project_stat +#: model:ir.model.fields,field_description:odoo_project_stat.field_odoo_project__chart_modules_count +msgid "Modules Chart" +msgstr "" + +#. module: odoo_project_stat +#: model:ir.model.fields,field_description:odoo_project_stat.field_odoo_project_stat__modules_count +msgid "Modules Count" +msgstr "" + +#. module: odoo_project_stat +#: model:ir.model,name:odoo_project_stat.model_odoo_project +msgid "Odoo Project" +msgstr "" + +#. module: odoo_project_stat +#: model:ir.model,name:odoo_project_stat.model_odoo_project_stat_config +msgid "Odoo Project Stat Config" +msgstr "" + +#. module: odoo_project_stat +#: model:ir.model,name:odoo_project_stat.model_odoo_project_stat +msgid "Odoo Project Stats" +msgstr "" + +#. module: odoo_project_stat +#: model:ir.model.constraint,message:odoo_project_stat.constraint_odoo_project_stat_config_residual_uniq +msgid "Only one configuration should exist for residual modules." +msgstr "" + +#. module: odoo_project_stat +#: model:ir.model.fields,field_description:odoo_project_stat.field_odoo_project_stat__odoo_project_id +msgid "Project" +msgstr "" + +#. module: odoo_project_stat +#: model:ir.model.fields,field_description:odoo_project_stat.field_odoo_project_stat__config_id +msgid "Project Stat Configuration" +msgstr "" + +#. module: odoo_project_stat +#: model:ir.actions.act_window,name:odoo_project_stat.odoo_project_stat_config_action +#: model:ir.ui.menu,name:odoo_project_stat.odoo_project_stat_config_menu +msgid "Project Stats" +msgstr "" + +#. module: odoo_project_stat +#: model_terms:ir.ui.view,arch_db:odoo_project_stat.odoo_project_view_form +msgid "Refresh" +msgstr "" + +#. module: odoo_project_stat +#: model:ir.model.fields,field_description:odoo_project_stat.field_odoo_project_stat_config__residual +msgid "Residual" +msgstr "" + +#. module: odoo_project_stat +#: model:ir.model.fields,field_description:odoo_project_stat.field_odoo_project_stat__sequence +#: model:ir.model.fields,field_description:odoo_project_stat.field_odoo_project_stat_config__sequence +msgid "Sequence" +msgstr "" + +#. module: odoo_project_stat +#: model:ir.model.fields,field_description:odoo_project_stat.field_odoo_project__stats_ids +#: model_terms:ir.ui.view,arch_db:odoo_project_stat.odoo_project_view_form +msgid "Stats" +msgstr "" + +#. module: odoo_project_stat +#: model:ir.model.constraint,message:odoo_project_stat.constraint_odoo_project_stat_odoo_project_config_date_uniq +msgid "This project stats record already exists." +msgstr "" + +#. module: odoo_project_stat +#: model:ir.model.fields,field_description:odoo_project_stat.field_odoo_project_stat__name +#: model:ir.model.fields,field_description:odoo_project_stat.field_odoo_project_stat_config__name +msgid "Title" +msgstr "" + +#. module: odoo_project_stat +#: model_terms:ir.ui.view,arch_db:odoo_project_stat.odoo_project_view_form +msgid "" +"Useful to get accurate figures if modules have been updated in their " +"respective repositories." +msgstr "" + +#. module: odoo_project_stat +#: model:ir.model.fields,help:odoo_project_stat.field_odoo_project_stat_config__residual +msgid "" +"Will catch all modules that doesn't match other rules (domain field will be " +"ignored if enabled)." +msgstr "" diff --git a/odoo_project_stat/models/__init__.py b/odoo_project_stat/models/__init__.py new file mode 100644 index 00000000..3a61e47d --- /dev/null +++ b/odoo_project_stat/models/__init__.py @@ -0,0 +1,3 @@ +from . import odoo_project_stat_config +from . import odoo_project_stat +from . import odoo_project diff --git a/odoo_project_stat/models/odoo_project.py b/odoo_project_stat/models/odoo_project.py new file mode 100644 index 00000000..eabc762c --- /dev/null +++ b/odoo_project_stat/models/odoo_project.py @@ -0,0 +1,109 @@ +# Copyright 2024 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +import plotly.graph_objects as go +from plotly.offline import plot + +from odoo import fields, models + + +class OdooProject(models.Model): + _inherit = "odoo.project" + + stats_ids = fields.One2many( + comodel_name="odoo.project.stat", + inverse_name="odoo_project_id", + ) + chart_modules_count = fields.Text( + string="Modules Chart", + compute="_compute_charts", + ) + chart_sloc = fields.Text( + string="Lines of Code Chart", + compute="_compute_charts", + ) + + def _get_last_stats(self): + self.ensure_one() + last_stat = self.env["odoo.project.stat"].search( + [("odoo_project_id", "=", self.id)], + order="date DESC", + limit=1, + ) + if not last_stat: + return self.env["odoo.project.stat"].browse() + return self.env["odoo.project.stat"].search( + [("odoo_project_id", "=", self.id), ("date", "=", last_stat.date)], + order="sequence", + ) + + def _compute_charts(self): + for rec in self: + rec.chart_modules_count = rec.chart_sloc = False + stats = rec._get_last_stats() + labels = stats.mapped("name") + colors = stats.mapped("color") + count_values = stats.mapped("modules_count") + sloc_values = stats.mapped("sloc") + # Modules Count Pie Chart + if count_values: + count_chart = go.Figure( + data=[ + go.Pie( + labels=labels, + values=count_values, + ) + ] + ) + self._chart_update_layout(count_chart, colors=colors, title="Modules") + rec.chart_modules_count = plot( + count_chart, + include_plotlyjs=False, + output_type="div", + config={"displaylogo": False}, + ) + # SLOC Pie Chart + if sloc_values: + sloc_chart = go.Figure( + data=[ + go.Pie( + labels=labels, + values=sloc_values, + ) + ] + ) + self._chart_update_layout( + sloc_chart, colors=colors, title="Lines of Code" + ) + rec.chart_sloc = plot( + sloc_chart, + include_plotlyjs=False, + output_type="div", + config={"displaylogo": False}, + ) + + def _chart_update_layout(self, chart, colors=None, title=None): + chart.update_traces( + textinfo="percent+label", + textposition="inside", + hole=0.35, + sort=False, + direction="clockwise", + rotation=180, + ) + if colors: + chart.update_traces(marker=dict(colors=colors)) + if title: + chart.update_layout(title_text=title, title_x=0.5, title_y=0.5) + chart.update_layout( + margin=dict(l=0, r=0, t=20, b=20), + showlegend=False, + autosize=False, + width=450, + height=450, + ) + + def action_generate_stats(self): + for rec in self: + self.env["odoo.project.stat"].sudo()._generate_stats(rec.id) + return True diff --git a/odoo_project_stat/models/odoo_project_stat.py b/odoo_project_stat/models/odoo_project_stat.py new file mode 100644 index 00000000..aa575f27 --- /dev/null +++ b/odoo_project_stat/models/odoo_project_stat.py @@ -0,0 +1,118 @@ +# Copyright 2024 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +from odoo import fields, models +from odoo.fields import Domain +from odoo.tools.safe_eval import safe_eval + + +class OdooProjectStat(models.Model): + _name = "odoo.project.stat" + _description = "Odoo Project Stats" + _rec_name = "name" + _order = "odoo_project_id, date, sequence, name" + + odoo_project_id = fields.Many2one( + comodel_name="odoo.project", + ondelete="cascade", + string="Project", + index=True, + required=True, + readonly=True, + ) + config_id = fields.Many2one( + comodel_name="odoo.project.stat.config", + ondelete="restrict", + string="Project Stat Configuration", + required=True, + readonly=True, + ) + date = fields.Date(required=True, index=True) + sequence = fields.Integer(related="config_id.sequence", store=True) + name = fields.Char(related="config_id.name", store=True) + color = fields.Char(related="config_id.color") + modules_count = fields.Integer(readonly=True) + sloc = fields.Integer(string="Lines of code", readonly=True) + + _odoo_project_config_date_uniq = models.Constraint( + "UNIQUE (odoo_project_id, config_id, date)", + "This project stats record already exists.", + ) + + def _get_stats(self, odoo_project, date=None, config=None, limit=None): + domain = [("odoo_project_id", "=", odoo_project.id)] + if date: + domain.append(("date", "=", date)) + if config: + domain.append(("config_id", "=", config.id)) + return self.search(domain, limit=limit) + + def _generate_stats(self, odoo_project_id): + """Generate the stats for a given `odoo_project_id`.""" + odoo_project = self.env["odoo.project"].browse(odoo_project_id).exists() + odoo_project.ensure_one() + modules = odoo_project.project_module_ids + total_count = len(modules) + total_sloc = ( + sum(modules.mapped("sloc_python")) + + sum(modules.mapped("sloc_xml")) + + sum(modules.mapped("sloc_js")) + + sum(modules.mapped("sloc_css")) + ) + # Clean up stats of today if any + today = fields.Date.today() + existing_stats = self._get_stats(odoo_project, date=today) + existing_stats.sudo().unlink() + # Create or update existing stat record + # All stat configs are wanted here (small table) + configs = self.env["odoo.project.stat.config"].search( # pylint: disable=no-search-all + [] + ) + for config in configs: + stat = self._get_stats(odoo_project, date=today, config=config, limit=1) + values = self._generate_stat_values(odoo_project, config, total_count) + if stat: + stat.sudo().write(values) + else: + self.sudo().create(values) + stats = self._get_stats(odoo_project, date=today) + stat_residual = stats.filtered(lambda o: o.config_id.residual) + if stat_residual: + other_stats = stats - stat_residual + stat_residual.sudo().write( + { + "modules_count": ( + total_count - sum(other_stats.mapped("modules_count")) + ), + "sloc": (total_sloc - sum(other_stats.mapped("sloc"))), + } + ) + return True + + def _generate_stat_values(self, odoo_project, config, total_count): + # Counter + if config.residual: + modules_count = 0 + sloc = 0 + else: + domain = Domain.AND( + [ + safe_eval(config.domain), + [("odoo_project_id", "=", odoo_project.id)], + ] + ) + modules = self.env["odoo.project.module"].search(domain) + modules_count = len(modules) + sloc = ( + sum(modules.mapped("sloc_python")) + + sum(modules.mapped("sloc_xml")) + + sum(modules.mapped("sloc_js")) + + sum(modules.mapped("sloc_css")) + ) + return { + "odoo_project_id": odoo_project.id, + "config_id": config.id, + "date": fields.Date.today(), + "modules_count": modules_count, + "sloc": sloc, + } diff --git a/odoo_project_stat/models/odoo_project_stat_config.py b/odoo_project_stat/models/odoo_project_stat_config.py new file mode 100644 index 00000000..e93a2b3e --- /dev/null +++ b/odoo_project_stat/models/odoo_project_stat_config.py @@ -0,0 +1,26 @@ +# Copyright 2024 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +from odoo import fields, models + + +class OdooProjectStatConfig(models.Model): + _name = "odoo.project.stat.config" + _description = "Odoo Project Stat Config" + _order = "sequence, name" + + sequence = fields.Integer(default=10) + name = fields.Char(string="Title", required=True) + color = fields.Char(required=True) + residual = fields.Boolean( + help=( + "Will catch all modules that doesn't match other rules " + "(domain field will be ignored if enabled)." + ) + ) + domain = fields.Char() + + _residual_uniq = models.Constraint( + "UNIQUE (residual)", + "Only one configuration should exist for residual modules.", + ) diff --git a/odoo_project_stat/pyproject.toml b/odoo_project_stat/pyproject.toml new file mode 100644 index 00000000..4231d0cc --- /dev/null +++ b/odoo_project_stat/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/odoo_project_stat/readme/CONTRIBUTORS.md b/odoo_project_stat/readme/CONTRIBUTORS.md new file mode 100644 index 00000000..17752927 --- /dev/null +++ b/odoo_project_stat/readme/CONTRIBUTORS.md @@ -0,0 +1,2 @@ +- Camptocamp + - Sébastien Alix \ diff --git a/odoo_project_stat/readme/DESCRIPTION.md b/odoo_project_stat/readme/DESCRIPTION.md new file mode 100644 index 00000000..392e2940 --- /dev/null +++ b/odoo_project_stat/readme/DESCRIPTION.md @@ -0,0 +1,19 @@ +This module allows to generate and render some pie charts for your Odoo projects. + +It consists of two pie charts on the project form view: + +- number of installed modules +- number of lines of code + + Both measures are split based on some criteria (configurable), by default: + - Odoo CE + - Odoo Enterprise + - OCA + - Community (not OCA) + - Specific code + - Misc. (everything not part of other criteria above) + + Each criteria having a rendering color used on the charts. + + ![Project Pie Charts](./static/img/project_pie_charts.png) + diff --git a/odoo_project_stat/security/ir.model.access.csv b/odoo_project_stat/security/ir.model.access.csv new file mode 100644 index 00000000..da109388 --- /dev/null +++ b/odoo_project_stat/security/ir.model.access.csv @@ -0,0 +1,4 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_odoo_project_stat_config_user,odoo_project_stat_config_user,model_odoo_project_stat_config,odoo_repository.group_odoo_repository_user,1,0,0,0 +access_odoo_project_stat_config_manager,odoo_project_stat_config_manager,model_odoo_project_stat_config,odoo_repository.group_odoo_repository_manager,1,1,1,1 +access_odoo_project_stat_user,odoo_project_stat_user,model_odoo_project_stat,odoo_repository.group_odoo_repository_user,1,0,0,0 diff --git a/odoo_project_stat/static/description/icon.png b/odoo_project_stat/static/description/icon.png new file mode 100644 index 00000000..1dcc49c2 Binary files /dev/null and b/odoo_project_stat/static/description/icon.png differ diff --git a/odoo_project_stat/static/description/index.html b/odoo_project_stat/static/description/index.html new file mode 100644 index 00000000..a5765c51 --- /dev/null +++ b/odoo_project_stat/static/description/index.html @@ -0,0 +1,450 @@ + + + + + +README.rst + + + +
+ + + +Odoo Community Association + +
+

Odoo Project Stats

+ +

Beta License: AGPL-3 OCA/module-composition-analysis Translate me on Weblate Try me on Runboat

+

This module allows to generate and render some pie charts for your Odoo +projects.

+

It consists of two pie charts on the project form view:

+
    +
  • number of installed modules
  • +
  • number of lines of code
  • +
+

Both measures are split based on some criteria (configurable), by +default:

+
    +
  • Odoo CE
  • +
  • Odoo Enterprise
  • +
  • OCA
  • +
  • Community (not OCA)
  • +
  • Specific code
  • +
  • Misc. (everything not part of other criteria above)
  • +
+

Each criteria having a rendering color used on the charts.

+

Project Pie Charts

+

Table of contents

+ +
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Camptocamp
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+ +Odoo Community Association + +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

This module is part of the OCA/module-composition-analysis project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+
+ + diff --git a/odoo_project_stat/static/img/project_pie_charts.png b/odoo_project_stat/static/img/project_pie_charts.png new file mode 100644 index 00000000..665b2a4a Binary files /dev/null and b/odoo_project_stat/static/img/project_pie_charts.png differ diff --git a/odoo_project_stat/views/odoo_project.xml b/odoo_project_stat/views/odoo_project.xml new file mode 100644 index 00000000..938932a5 --- /dev/null +++ b/odoo_project_stat/views/odoo_project.xml @@ -0,0 +1,44 @@ + + + + + odoo.project.form.inherit + odoo.project + + + + + + + + + + + + +