Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .docker_files/main/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
40 changes: 40 additions & 0 deletions project_timesheet_hours_only/README.rst
Original file line number Diff line number Diff line change
@@ -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.
4 changes: 4 additions & 0 deletions project_timesheet_hours_only/__init__.py
Original file line number Diff line number Diff line change
@@ -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

Check warning on line 4 in project_timesheet_hours_only/__init__.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

project_timesheet_hours_only/__init__.py#L4

'.models' imported but unused (F401)
18 changes: 18 additions & 0 deletions project_timesheet_hours_only/__manifest__.py
Original file line number Diff line number Diff line change
@@ -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).

{

Check notice on line 4 in project_timesheet_hours_only/__manifest__.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

project_timesheet_hours_only/__manifest__.py#L4

Statement seems to have no effect
"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,
}
4 changes: 4 additions & 0 deletions project_timesheet_hours_only/models/__init__.py
Original file line number Diff line number Diff line change
@@ -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

Check warning on line 4 in project_timesheet_hours_only/models/__init__.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

project_timesheet_hours_only/models/__init__.py#L4

'.project_project' imported but unused (F401)
21 changes: 21 additions & 0 deletions project_timesheet_hours_only/models/project_project.py
Original file line number Diff line number Diff line change
@@ -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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions project_timesheet_hours_only/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -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).
33 changes: 33 additions & 0 deletions project_timesheet_hours_only/tests/test_project_timesheet.py
Original file line number Diff line number Diff line change
@@ -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)
6 changes: 6 additions & 0 deletions project_timesheet_hours_only/views/project_project_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="hr_timesheet.act_hr_timesheet_line_by_project" model="ir.actions.act_window">
<field name="domain">[('project_id', '!=', False), ('task_id', '!=', False)]</field>
</record>
</odoo>
Loading