from odoo import api, fields, models class Project(models.Model): _inherit = "project.project" billable_hours = fields.Float( string="Billable Time", compute="_compute_billable_hours", compute_sudo=True, groups="hr_timesheet.group_hr_timesheet_user", ) @api.depends('timesheet_ids.unit_amount', 'timesheet_ids.so_line') def _compute_billable_hours(self): if not any(self._ids): for project in self: filtered_timesheets = project.timesheet_ids.filtered( lambda t: t.so_line ) project.billable_hours = sum( filtered_timesheets.mapped("unit_amount") ) return timesheet_read_group = self.env["account.analytic.line"]._read_group( [ ("project_id", "in", self.ids), ("so_line", "!=", False), ], ["project_id"], ["unit_amount:sum"], ) timesheets_per_project = { project.id: unit_amount for project, unit_amount in timesheet_read_group } for project in self: project.billable_hours = timesheets_per_project.get(project.id, 0.0) def action_project_billable_timesheets(self): self.ensure_one() action = self.action_billable_time_button() # sale_timesheet action['domain'] = [('project_id', '=', self.id), ('so_line', '!=', False)] action['display_name'] = self.env._("%(name)s's Billable Timesheets", name=self.name) return action def _get_stat_buttons(self): buttons = super()._get_stat_buttons() if not (self.allow_billable and 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 billable = self.billable_hours / uom_ratio effective = self.total_timesheet_time / uom_ratio color = "" if billable: number = f"{round(billable)} / {round(effective)} {encode_uom.name}" billable_rate = round(100 * billable / effective) if billable_rate < 50: number = self.env._( "%(billable)s / %(effective)s %(uom_name)s", effective=round(effective), billable=round(billable), uom_name=encode_uom.name, ) color = "text-danger" else: number = self.env._( "%(billable)s / %(effective)s %(uom_name)s (%(billable_rate)s%%)", effective=round(effective), billable=round(billable), uom_name=encode_uom.name, billable_rate=billable_rate, ) if billable_rate < 80: color = "text-warning" else: color = "text-success" else: number = self.env._( "%(billable)s %(uom_name)s", billable=round(billable), uom_name=encode_uom.name, ) buttons.append({ "icon": f"money {color}", "text": self.env._("Billable Time"), "number": number, "action_type": "object", "action": "action_project_billable_timesheets", "show": True, "sequence": 3, }) return buttons