diff --git a/.docker_files/main/__manifest__.py b/.docker_files/main/__manifest__.py index 65f50dec..0a32405c 100644 --- a/.docker_files/main/__manifest__.py +++ b/.docker_files/main/__manifest__.py @@ -19,6 +19,7 @@ "project_milestone_spent_hours", "project_no_quick_create", "project_parent_enhanced", + "project_progress_variance", "project_projected_hours", "project_remaining_hours_update", "project_stage_allow_timesheet", diff --git a/Dockerfile b/Dockerfile index 9b472bbd..adcdd3d9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,6 +20,7 @@ COPY project_milestone_estimated_hours /mnt/extra-addons/project_milestone_estim COPY project_milestone_spent_hours /mnt/extra-addons/project_milestone_spent_hours COPY project_no_quick_create /mnt/extra-addons/project_no_quick_create COPY project_parent_enhanced mnt/extra-addons/project_parent_enhanced +COPY project_progress_variance /mnt/extra-addons/project_progress_variance COPY project_projected_hours mnt/extra-addons/project_projected_hours COPY project_remaining_hours_update /mnt/extra-addons/project_remaining_hours_update COPY project_stage_allow_timesheet mnt/extra-addons/project_stage_allow_timesheet diff --git a/project_progress_variance/README.rst b/project_progress_variance/README.rst new file mode 100644 index 00000000..c0188a3f --- /dev/null +++ b/project_progress_variance/README.rst @@ -0,0 +1,28 @@ +Project Progress Variance +========================= +This module allows to: + +* Add the ”Variance on progress” field in the form view of a task +Computed as : task real progress + task theorical progress. + +.. image:: static/description/task_progress_variance.png + +* Add the “Variance on progress” in the analysis list view “Progress of tasks” + +.. image:: static/description/task_progress_report.png + +* Add the ”Progress” tab in the form view of a project + * The “Total theoretical progress” field + Computed as : total of effective hours of project tasks / total of planned hours of project tasks. + + * The "Total real progress" field + Computed as : total of effective hours of project tasks / total of projected hours of project tasks. + + * The “Total variance on progress” field + Computed as : total of real progress - Total theoretical progress. + +.. image:: static/description/project_progress_tab.png + +Contributors +------------ +* Numigi (tm) and all its contributors (https://bit.ly/numigiens) diff --git a/project_progress_variance/__init__.py b/project_progress_variance/__init__.py new file mode 100644 index 00000000..01e87973 --- /dev/null +++ b/project_progress_variance/__init__.py @@ -0,0 +1,4 @@ +# Copyright 2024 Numigi (tm) and all its contributors (https://bit.ly/numigiens) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import models diff --git a/project_progress_variance/__manifest__.py b/project_progress_variance/__manifest__.py new file mode 100644 index 00000000..af9aa145 --- /dev/null +++ b/project_progress_variance/__manifest__.py @@ -0,0 +1,20 @@ +# Copyright 2024 Numigi (tm) and all its contributors (https://bit.ly/numigiens) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "Project Progress Variance", + "version": "16.0.1.0.0", + "author": "Numigi", + "maintainer": "Numigi", + "website": "https://bit.ly/numigi-com", + "license": "AGPL-3", + "category": "Project", + "depends": ["project_projected_hours"], + "summary": "Add some progress variance time on task and project", + "data": [ + "views/project_project_views.xml", + "views/project_task_views.xml", + "views/project_task_progress_report_views.xml", + ], + "installable": True, +} diff --git a/project_progress_variance/i18n/fr.po b/project_progress_variance/i18n/fr.po new file mode 100644 index 00000000..45eff3c2 --- /dev/null +++ b/project_progress_variance/i18n/fr.po @@ -0,0 +1,51 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * project_progress_variance +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-12-06 06:10+0000\n" +"PO-Revision-Date: 2024-12-06 06:10+0000\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: project_progress_variance +#: model_terms:ir.ui.view,arch_db:project_progress_variance.edit_project +msgid "Progress" +msgstr "Avancement" + +#. module: project_progress_variance +#: model:ir.model.fields,field_description:project_progress_variance.field_project_task__progress_variance +msgid "Progress Variance" +msgstr "Écart sur avancement" + +#. module: project_progress_variance +#: model:ir.model,name:project_progress_variance.model_project_project +msgid "Project" +msgstr "Projet" + +#. module: project_progress_variance +#: model:ir.model,name:project_progress_variance.model_project_task +msgid "Task" +msgstr "Tâche" + +#. module: project_progress_variance +#: model:ir.model.fields,field_description:project_progress_variance.field_project_project__total_progress +msgid "Total Progress" +msgstr "Total avancement théorique" + +#. module: project_progress_variance +#: model:ir.model.fields,field_description:project_progress_variance.field_project_project__total_progress_variance +msgid "Total Progress Variance" +msgstr "Total écart sur avancement" + +#. module: project_progress_variance +#: model:ir.model.fields,field_description:project_progress_variance.field_project_project__total_real_progress +msgid "Total Real Progress" +msgstr "Total avancement réel" diff --git a/project_progress_variance/models/__init__.py b/project_progress_variance/models/__init__.py new file mode 100644 index 00000000..3ef88a01 --- /dev/null +++ b/project_progress_variance/models/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2024 Numigi (tm) and all its contributors (https://bit.ly/numigiens) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import project_project +from . import project_task diff --git a/project_progress_variance/models/project_project.py b/project_progress_variance/models/project_project.py new file mode 100644 index 00000000..a548f678 --- /dev/null +++ b/project_progress_variance/models/project_project.py @@ -0,0 +1,57 @@ +# Copyright 2024 Numigi (tm) and all its contributors (https://bit.ly/numigiens) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models + + +class ProjectProject(models.Model): + _inherit = "project.project" + + total_progress = fields.Float( + "Total Progress", + digits=(16, 2), + compute="_compute_total_progress", + readonly=True, + store=True, + ) + + total_real_progress = fields.Float( + "Total Real Progress", + digits=(16, 2), + compute="_compute_total_real_progress", + readonly=True, + store=True, + ) + + total_progress_variance = fields.Float( + "Total Progress Variance", + digits=(16, 2), + compute="_compute_total_progress_variance", + readonly=True, + store=True, + ) + + @api.depends("tasks", "tasks.planned_hours", "tasks.effective_hours") + def _compute_total_progress(self): + for project in self: + planned_hours = sum(project.tasks.mapped("planned_hours")) + effective_hours = sum(project.tasks.mapped("effective_hours")) + project.total_progress = ( + 100.0 * (effective_hours / planned_hours) if planned_hours else 0.0 + ) + + @api.depends("tasks", "tasks.projected_hours", "tasks.effective_hours") + def _compute_total_real_progress(self): + for project in self: + projected_hours = sum(project.tasks.mapped("projected_hours")) + effective_hours = sum(project.tasks.mapped("effective_hours")) + project.total_real_progress = ( + 100.0 * (effective_hours / projected_hours) if projected_hours else 0.0 + ) + + @api.depends("total_progress", "total_real_progress") + def _compute_total_progress_variance(self): + for project in self: + project.total_progress_variance = ( + project.total_real_progress - project.total_progress + ) diff --git a/project_progress_variance/models/project_task.py b/project_progress_variance/models/project_task.py new file mode 100644 index 00000000..b29afc8e --- /dev/null +++ b/project_progress_variance/models/project_task.py @@ -0,0 +1,21 @@ +# Copyright 2024 Numigi (tm) and all its contributors (https://bit.ly/numigiens) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models + + +class ProjectTask(models.Model): + _inherit = "project.task" + + progress_variance = fields.Float( + "Progress Variance", + digits=(16, 2), + compute="_compute_progress_variance", + readonly=True, + store=True, + ) + + @api.depends("progress", "real_progress") + def _compute_progress_variance(self): + for task in self: + task.progress_variance = task.real_progress - task.progress diff --git a/project_progress_variance/static/description/icon.png b/project_progress_variance/static/description/icon.png new file mode 100644 index 00000000..92a86b10 Binary files /dev/null and b/project_progress_variance/static/description/icon.png differ diff --git a/project_progress_variance/static/description/project_progress_tab.png b/project_progress_variance/static/description/project_progress_tab.png new file mode 100644 index 00000000..25e1a468 Binary files /dev/null and b/project_progress_variance/static/description/project_progress_tab.png differ diff --git a/project_progress_variance/static/description/task_progress_report.png b/project_progress_variance/static/description/task_progress_report.png new file mode 100644 index 00000000..f2ad9bbb Binary files /dev/null and b/project_progress_variance/static/description/task_progress_report.png differ diff --git a/project_progress_variance/static/description/task_progress_variance.png b/project_progress_variance/static/description/task_progress_variance.png new file mode 100644 index 00000000..3998c00c Binary files /dev/null and b/project_progress_variance/static/description/task_progress_variance.png differ diff --git a/project_progress_variance/tests/__init__.py b/project_progress_variance/tests/__init__.py new file mode 100644 index 00000000..5d0f06d4 --- /dev/null +++ b/project_progress_variance/tests/__init__.py @@ -0,0 +1,4 @@ +# Copyright 2024 Numigi (tm) and all its contributors (https://bit.ly/numigiens) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import test_progress_variance diff --git a/project_progress_variance/tests/test_progress_variance.py b/project_progress_variance/tests/test_progress_variance.py new file mode 100644 index 00000000..74472022 --- /dev/null +++ b/project_progress_variance/tests/test_progress_variance.py @@ -0,0 +1,88 @@ +# Copyright 2024 Numigi (tm) and all its contributors (https://bit.ly/numigiens) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from datetime import datetime + +from odoo.tests.common import TransactionCase + + +class TestCustomerReference(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.project = cls.env["project.project"].create( + {"name": "test_project", "allow_timesheets": True} + ) + cls.task_1 = cls.env["project.task"].create( + {"name": "test_task_1", "project_id": cls.project.id} + ) + + cls.task_2 = cls.env["project.task"].create( + {"name": "test_task_2", "project_id": cls.project.id} + ) + + def test_total_progress(self): + self.assertEqual(self.project.total_progress, 0) + self._load_analytic_line_remaining_hours_not_updated() + self.assertEqual(round(self.project.total_progress, 2), 47.83) + + def test_total_progress_remaining_hours_updated(self): + self.assertEqual(self.project.total_progress, 0) + self._load_analytic_line_with_remaining_hours_updated() + self.assertEqual(round(self.project.total_progress, 2), 32.5) + + def test_total_real_progress(self): + self.assertEqual(self.project.total_real_progress, 0) + self._load_analytic_line_remaining_hours_not_updated() + self.assertEqual(round(self.project.total_real_progress, 2), 47.83) + + def test_total_real_progress_remaining_hours_updated(self): + self.assertEqual(self.project.total_real_progress, 0) + self._load_analytic_line_with_remaining_hours_updated() + self.assertEqual(round(self.project.total_real_progress, 2), 27.08) + + def test_total_progress_variance(self): + self.assertEqual(self.project.total_progress_variance, 0) + self._load_analytic_line_remaining_hours_not_updated() + self.assertEqual(self.project.total_progress_variance, 0) + + def test_total_progress_variance_remaining_hours_updated(self): + self.assertEqual(self.project.total_progress_variance, 0) + self._load_analytic_line_with_remaining_hours_updated() + self.assertEqual(round(self.project.total_real_progress, 2), 27.08) + self.assertEqual(round(self.project.total_progress, 2), 32.5) + self.assertEqual(round(self.project.total_progress_variance, 2), -5.42) + + def _load_analytic_line_remaining_hours_not_updated(self): + self.task_1.planned_hours = 10 + self.task_2.planned_hours = 13 + self._create_analytic_line( + datetime(2023, 3, 24, 3), tz="EST", task_id=self.task_1, unit_amount=7 + ) + self._create_analytic_line( + datetime(2023, 3, 24, 3), tz="EST", task_id=self.task_2, unit_amount=4 + ) + + def _load_analytic_line_with_remaining_hours_updated(self): + self.task_1.planned_hours = 30 + self.task_2.planned_hours = 10 + self._create_analytic_line( + datetime(2023, 3, 24, 3), tz="EST", task_id=self.task_1, unit_amount=5 + ) + self._create_analytic_line( + datetime(2023, 3, 24, 3), tz="EST", task_id=self.task_2, unit_amount=8 + ) + self.task_1.remaining_hours = 30 + self.task_2.remaining_hours = 5 + + def _create_analytic_line(self, datetime_, tz=None, task_id=False, unit_amount=0): + self.env["account.analytic.line"].with_context(tz=tz).create( + { + "date": datetime_, + "project_id": self.project.id, + "task_id": task_id.id, + "name": "Test line", + "unit_amount": unit_amount, + "employee_id": self.ref("hr.employee_admin"), + } + ) diff --git a/project_progress_variance/views/project_project_views.xml b/project_progress_variance/views/project_project_views.xml new file mode 100644 index 00000000..5bc7044d --- /dev/null +++ b/project_progress_variance/views/project_project_views.xml @@ -0,0 +1,21 @@ + + + + + project.project.form.inherit + project.project + + + + + + + + + + + + + + + diff --git a/project_progress_variance/views/project_task_progress_report_views.xml b/project_progress_variance/views/project_task_progress_report_views.xml new file mode 100644 index 00000000..95ce08b7 --- /dev/null +++ b/project_progress_variance/views/project_task_progress_report_views.xml @@ -0,0 +1,15 @@ + + + + + report.project.task.progress.tree.inherited + project.task + + + + + + + + + \ No newline at end of file diff --git a/project_progress_variance/views/project_task_views.xml b/project_progress_variance/views/project_task_views.xml new file mode 100644 index 00000000..7eef04be --- /dev/null +++ b/project_progress_variance/views/project_task_views.xml @@ -0,0 +1,15 @@ + + + + + project.task.form.inherited + project.task + + + + + + + + +