diff --git a/.docker_files/main/__manifest__.py b/.docker_files/main/__manifest__.py index abdf532a..0f9b484a 100644 --- a/.docker_files/main/__manifest__.py +++ b/.docker_files/main/__manifest__.py @@ -73,6 +73,7 @@ "project_time_control_wizard_group", "project_time_range", "project_timesheet_analytic_update", + "project_timesheet_hours_only", "project_type", "project_wip", "project_wip_batch_closing", diff --git a/Dockerfile b/Dockerfile index 9c38a1e6..ef90e7af 100644 --- a/Dockerfile +++ b/Dockerfile @@ -74,6 +74,7 @@ COPY project_time_budget /mnt/extra-addons/project_time_budget COPY project_time_control_wizard_group /mnt/extra-addons/project_time_control_wizard_group COPY project_time_range /mnt/extra-addons/project_time_range COPY project_timesheet_analytic_update /mnt/extra-addons/project_timesheet_analytic_update +COPY project_timesheet_hours_only /mnt/extra-addons/project_timesheet_hours_only COPY project_type /mnt/extra-addons/project_type COPY project_wip /mnt/extra-addons/project_wip COPY project_wip_batch_closing /mnt/extra-addons/project_wip_batch_closing diff --git a/project_task_analytic_lines/models/account_analytic_line.py b/project_task_analytic_lines/models/account_analytic_line.py index 817d145d..84b795a9 100644 --- a/project_task_analytic_lines/models/account_analytic_line.py +++ b/project_task_analytic_lines/models/account_analytic_line.py @@ -86,7 +86,7 @@ def write(self, vals): return True def _propagate_origin_task_to_timesheet_lines(self): - """Backward propagation of origin_task_id to task_id. + """Backward propagation of origin_task_id to task_id.w This allows the system to behave in a more transparent way when manually changing the value of origin_task_id diff --git a/project_timesheet_hours_only/README.rst b/project_timesheet_hours_only/README.rst new file mode 100644 index 00000000..45e989a1 --- /dev/null +++ b/project_timesheet_hours_only/README.rst @@ -0,0 +1,40 @@ +============================ +Project Timesheet Hours Only +============================ + +.. ![]https://img.shields.io/badge/licence-AGPL--3-blue.png) + :target: https://www.gnu.org/licenses/agpl.html + :alt: License: AGPL-3 + +Context +======= +On mixed projects involving both labor (timesheets) and material tracking, Odoo's standard project dashboard aggregates all analytic lines under the "Recorded Hours" smart button. This leads to inaccurate totals since material consumption quantities (often recorded with negative or distinct values) are mixed with actual work hours. + +Description +=========== +This module fixes the project's "Recorded Hours" smart button and its corresponding list view to exclusively display and calculate actual labor hours. It filters out material consumption lines by ensuring that only analytic lines linked to a specific task (``task_id != False``) are included. + +Usage +===== +1. Navigate to the **Project** app. +2. Open any project that contains both timesheets and material consumption lines. +3. The counter on the **Recorded Hours** smart button now strictly shows the total labor hours. +4. Clicking the smart button will only list actual timesheet entries, excluding material logs. + +.. note:: + Material consumption remains fully traceable and searchable via **Project > Reporting > Material** (filtering by project and grouping as needed). + +Installation +============ +No data migration or historical recalculation is strictly required, as the change applies dynamically to the compute method and window action domain. + +Credits +======= + +Authors +------- +* Numigi + +Maintainers +----------- +This module is maintained by Numigi. diff --git a/project_timesheet_hours_only/__init__.py b/project_timesheet_hours_only/__init__.py new file mode 100644 index 00000000..36ffad66 --- /dev/null +++ b/project_timesheet_hours_only/__init__.py @@ -0,0 +1,4 @@ +# © Numigi (tm) and all its contributors (https://numigi.com/r/home) +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +from . import models diff --git a/project_timesheet_hours_only/__manifest__.py b/project_timesheet_hours_only/__manifest__.py new file mode 100644 index 00000000..68f98c50 --- /dev/null +++ b/project_timesheet_hours_only/__manifest__.py @@ -0,0 +1,18 @@ +# © Numigi (tm) and all its contributors (https://numigi.com/r/home) +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +{ + "name": "Project Timesheet Hours Only", + "summary": "Filter project timesheet smart button to " + "exclude material consumption and show hours only", + "version": "1.0.0", + "category": "Project", + "author": "Numigi", + "website": "https://www.numigi.com", + "license": "AGPL-3", + "depends": ["hr_timesheet", "project"], + "data": [ + "views/project_project_views.xml", + ], + "installable": True, +} diff --git a/project_timesheet_hours_only/models/__init__.py b/project_timesheet_hours_only/models/__init__.py new file mode 100644 index 00000000..86e6be36 --- /dev/null +++ b/project_timesheet_hours_only/models/__init__.py @@ -0,0 +1,4 @@ +# © Numigi (tm) and all its contributors (https://numigi.com/r/home) +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +from . import project_project diff --git a/project_timesheet_hours_only/models/project_project.py b/project_timesheet_hours_only/models/project_project.py new file mode 100644 index 00000000..eaa4d60e --- /dev/null +++ b/project_timesheet_hours_only/models/project_project.py @@ -0,0 +1,21 @@ +# © Numigi (tm) and all its contributors (https://numigi.com/r/home) +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +from odoo import models + + +class ProjectProject(models.Model): + _inherit = "project.project" + + def _compute_total_timesheet_time(self): + for project in self: + project._compute_project_timesheet_time() + + def _compute_project_timesheet_time(self): + project_timesheets = self.timesheet_ids.filtered(lambda t: t.task_id) + total_time = self._calculate_timesheet_total(project_timesheets) + self.total_timesheet_time = int(round(total_time)) + + def _calculate_timesheet_total(self, timesheets): + total = sum(t.unit_amount * t.product_uom_id.factor_inv for t in timesheets) + return total * self.timesheet_encode_uom_id.factor diff --git a/project_timesheet_hours_only/static/description/icon.png b/project_timesheet_hours_only/static/description/icon.png new file mode 100644 index 00000000..92a86b10 Binary files /dev/null and b/project_timesheet_hours_only/static/description/icon.png differ diff --git a/project_timesheet_hours_only/tests/__init__.py b/project_timesheet_hours_only/tests/__init__.py new file mode 100644 index 00000000..c63ff811 --- /dev/null +++ b/project_timesheet_hours_only/tests/__init__.py @@ -0,0 +1,2 @@ +# © Numigi (tm) and all its contributors (https://numigi.com/r/home) +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). diff --git a/project_timesheet_hours_only/tests/test_project_timesheet.py b/project_timesheet_hours_only/tests/test_project_timesheet.py new file mode 100644 index 00000000..7b961db6 --- /dev/null +++ b/project_timesheet_hours_only/tests/test_project_timesheet.py @@ -0,0 +1,33 @@ +# © Numigi (tm) and all its contributors (https://numigi.com/r/home) +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + + +from odoo.tests import common + + +class TestProjectTimesheetHoursOnly(common.SavepointCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.project = cls.env["project.project"].create({"name": "Test Project"}) + cls.task = cls.env["project.task"].create({ + "name": "Test Task", + "project_id": cls.project.id, + }) + + def test_compute_timesheet_excludes_material(self): + self.env["account.analytic.line"].create({ + "project_id": self.project.id, + "name": "Material", + "unit_amount": 15.0, + }) + self.assertEqual(self.project.total_timesheet_time, 0) + + def test_compute_timesheet_includes_hours(self): + self.env["account.analytic.line"].create({ + "project_id": self.project.id, + "task_id": self.task.id, + "name": "Development", + "unit_amount": 10.0, + }) + self.assertEqual(self.project.total_timesheet_time, 10) diff --git a/project_timesheet_hours_only/views/project_project_views.xml b/project_timesheet_hours_only/views/project_project_views.xml new file mode 100644 index 00000000..f5ef3f85 --- /dev/null +++ b/project_timesheet_hours_only/views/project_project_views.xml @@ -0,0 +1,6 @@ + + + + [('project_id', '!=', False), ('task_id', '!=', False)] + + \ No newline at end of file