diff --git a/addons/hr_timesheet/models/__init__.py b/addons/hr_timesheet/models/__init__.py
index 880b2c587ff..8f9a07a3223 100644
--- a/addons/hr_timesheet/models/__init__.py
+++ b/addons/hr_timesheet/models/__init__.py
@@ -9,5 +9,6 @@ from . import res_company
from . import res_config_settings
from . import project_project
from . import project_task
+from . import project_update
from . import project_collaborator
from . import uom_uom
diff --git a/addons/hr_timesheet/models/project_project.py b/addons/hr_timesheet/models/project_project.py
index 3dd7703a71b..648d48e5384 100644
--- a/addons/hr_timesheet/models/project_project.py
+++ b/addons/hr_timesheet/models/project_project.py
@@ -2,7 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from collections import defaultdict
-from odoo import models, fields, api, _
+from odoo import models, fields, api, _, _lt
from odoo.exceptions import ValidationError, RedirectWarning
class Project(models.Model):
@@ -211,3 +211,75 @@ class Project(models.Model):
action = self.env['ir.actions.act_window']._for_xml_id('hr_timesheet.act_hr_timesheet_line_by_project')
action['display_name'] = _("%(name)s's Timesheets", name=self.name)
return action
+
+ # ----------------------------
+ # Project Updates
+ # ----------------------------
+
+ def _get_stat_buttons(self):
+ buttons = super(Project, self)._get_stat_buttons()
+ if not self.allow_timesheets or not self.env.user.has_group("hr_timesheet.group_hr_timesheet_user"):
+ return buttons
+
+ encode_uom = self.env.company.timesheet_encode_uom_id
+ uom_ratio = self.env.ref('uom.product_uom_hour').factor / encode_uom.factor
+
+ allocated = self.allocated_hours / uom_ratio
+ effective = self.total_timesheet_time / uom_ratio
+ color = ""
+ if allocated:
+ number = f"{round(effective)} / {round(allocated)} {encode_uom.name}"
+ success_rate = round(100 * effective / allocated)
+ if success_rate > 100:
+ number = _lt(
+ "%(effective)s / %(allocated)s %(uom_name)s",
+ effective=round(effective),
+ allocated=round(allocated),
+ uom_name=encode_uom.name,
+ )
+ color = "text-danger"
+ else:
+ number = _lt(
+ "%(effective)s / %(allocated)s %(uom_name)s (%(success_rate)s%%)",
+ effective=round(effective),
+ allocated=round(allocated),
+ uom_name=encode_uom.name,
+ success_rate=success_rate,
+ )
+ if success_rate >= 80:
+ color = "text-warning"
+ else:
+ color = "text-success"
+ else:
+ number = _lt(
+ "%(effective)s %(uom_name)s",
+ effective=round(effective),
+ uom_name=encode_uom.name,
+ )
+
+ buttons.append({
+ "icon": f"clock-o {color}",
+ "text": _lt("Timesheets"),
+ "number": number,
+ "action_type": "object",
+ "action": "action_project_timesheets",
+ "show": True,
+ "sequence": 2,
+ })
+ if allocated and success_rate > 100:
+ buttons.append({
+ "icon": f"warning {color}",
+ "text": _lt("Extra Time"),
+ "number": _lt(
+ "%(exceeding_hours)s %(uom_name)s (+%(exceeding_rate)s%%)",
+ exceeding_hours=round(effective - allocated),
+ uom_name=encode_uom.name,
+ exceeding_rate=round(100 * (effective - allocated) / allocated),
+ ),
+ "action_type": "object",
+ "action": "action_project_timesheets",
+ "show": True,
+ "sequence": 3,
+ })
+
+ return buttons
diff --git a/addons/hr_timesheet/models/project_update.py b/addons/hr_timesheet/models/project_update.py
new file mode 100644
index 00000000000..a4732fdf9c6
--- /dev/null
+++ b/addons/hr_timesheet/models/project_update.py
@@ -0,0 +1,39 @@
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import fields, models, api
+
+
+class ProjectUpdate(models.Model):
+ _inherit = "project.update"
+
+ display_timesheet_stats = fields.Boolean(compute="_compute_display_timesheet_stats")
+ allocated_time = fields.Integer("Allocated Time", readonly=True)
+ timesheet_time = fields.Integer("Timesheet Time", readonly=True)
+ timesheet_percentage = fields.Integer(compute="_compute_timesheet_percentage")
+ uom_id = fields.Many2one("uom.uom", "Unit Of Measure", readonly=True)
+
+ def _compute_timesheet_percentage(self):
+ for update in self:
+ update.timesheet_percentage = update.allocated_time and round(update.timesheet_time * 100 / update.allocated_time)
+
+ def _compute_display_timesheet_stats(self):
+ for update in self:
+ update.display_timesheet_stats = update.project_id.allow_timesheets
+
+ # ---------------------------------
+ # ORM Override
+ # ---------------------------------
+ @api.model_create_multi
+ def create(self, vals_list):
+ updates = super().create(vals_list)
+ encode_uom = self.env.company.timesheet_encode_uom_id
+ ratio = self.env.ref("uom.product_uom_hour").ratio / encode_uom.ratio
+ for update in updates:
+ project = update.project_id
+ project.sudo().last_update_id = update
+ update.write({
+ "uom_id": encode_uom,
+ "allocated_time": round(project.allocated_hours / ratio),
+ "timesheet_time": round(project.total_timesheet_time / ratio),
+ })
+ return updates
diff --git a/addons/hr_timesheet/views/project_update_views.xml b/addons/hr_timesheet/views/project_update_views.xml
index b90db9ddd3a..8300765427e 100644
--- a/addons/hr_timesheet/views/project_update_views.xml
+++ b/addons/hr_timesheet/views/project_update_views.xml
@@ -12,5 +12,18 @@
+