[ADD] project_dashboard_billable_hours display billable hours on project dashboard view
This commit is contained in:
1
project_dashboard_billable_hours/__init__.py
Normal file
1
project_dashboard_billable_hours/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from . import models
|
||||||
32
project_dashboard_billable_hours/__manifest__.py
Normal file
32
project_dashboard_billable_hours/__manifest__.py
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# Copyright 2026 Maud Buchwalter (Elabore)
|
||||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "project_dashboard_billable_hours",
|
||||||
|
"version": "18.0.1.0.0",
|
||||||
|
"author": "Elabore",
|
||||||
|
"website": "https://git.elabore.coop/elabore/project-tools",
|
||||||
|
"maintainer": "Maud Buchwalter",
|
||||||
|
"license": "AGPL-3",
|
||||||
|
"category": "Tools",
|
||||||
|
"summary": "Display billable hours (timesheets linked to a SO line) in the project dashboard panel",
|
||||||
|
# any module necessary for this one to work correctly
|
||||||
|
"depends": ["sale_timesheet"],
|
||||||
|
"qweb": [
|
||||||
|
# "static/src/xml/*.xml",
|
||||||
|
],
|
||||||
|
"external_dependencies": {
|
||||||
|
"python": [],
|
||||||
|
},
|
||||||
|
# always loaded
|
||||||
|
"data": [],
|
||||||
|
# only loaded in demonstration mode
|
||||||
|
"demo": [],
|
||||||
|
"js": [],
|
||||||
|
"css": [],
|
||||||
|
"installable": True,
|
||||||
|
# Install this module automatically if all dependency have been previously
|
||||||
|
# and independently installed. Used for synergetic or glue modules.
|
||||||
|
"auto_install": False,
|
||||||
|
"application": False,
|
||||||
|
}
|
||||||
57
project_dashboard_billable_hours/i18n/fr.po
Normal file
57
project_dashboard_billable_hours/i18n/fr.po
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
# Translation of Odoo Server.
|
||||||
|
# This file contains the translation of the following modules:
|
||||||
|
# * project_dashboard_billable_hours
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: Odoo Server 18.0\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2026-09-25 16:40+0000\n"
|
||||||
|
"PO-Revision-Date: 2026-09-25 16:40+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_dashboard_billable_hours
|
||||||
|
#. odoo-python
|
||||||
|
#: code:addons/project_dashboard_billable_hours/models/project_project.py:0
|
||||||
|
msgid "%(billable)s %(uom_name)s"
|
||||||
|
msgstr "%(billable)s %(uom_name)s"
|
||||||
|
|
||||||
|
#. module: project_dashboard_billable_hours
|
||||||
|
#. odoo-python
|
||||||
|
#: code:addons/project_dashboard_billable_hours/models/project_project.py:0
|
||||||
|
msgid "%(billable)s / %(effective)s %(uom_name)s"
|
||||||
|
msgstr "%(billable)s / %(effective)s %(uom_name)s"
|
||||||
|
|
||||||
|
#. module: project_dashboard_billable_hours
|
||||||
|
#. odoo-python
|
||||||
|
#: code:addons/project_dashboard_billable_hours/models/project_project.py:0
|
||||||
|
msgid "%(billable)s / %(effective)s %(uom_name)s (%(billable_rate)s%%)"
|
||||||
|
msgstr "%(billable)s / %(effective)s %(uom_name)s (%(billable_rate)s%%)"
|
||||||
|
|
||||||
|
#. module: project_dashboard_billable_hours
|
||||||
|
#. odoo-python
|
||||||
|
#: code:addons/project_dashboard_billable_hours/models/project_project.py:0
|
||||||
|
msgid "%(name)s's Billable Timesheets"
|
||||||
|
msgstr "%(name)s's Feuille de temps facturable"
|
||||||
|
|
||||||
|
#. module: project_dashboard_billable_hours
|
||||||
|
#. odoo-python
|
||||||
|
#: code:addons/project_dashboard_billable_hours/models/project_project.py:0
|
||||||
|
#: model:ir.model.fields,field_description:project_dashboard_billable_hours.field_project_project__billable_hours
|
||||||
|
msgid "Billable Time"
|
||||||
|
msgstr "Temps facturable"
|
||||||
|
|
||||||
|
#. module: project_dashboard_billable_hours
|
||||||
|
#: model:ir.model,name:project_dashboard_billable_hours.model_project_project
|
||||||
|
msgid "Project"
|
||||||
|
msgstr "Projet"
|
||||||
|
|
||||||
|
#. module: project_dashboard_billable_hours
|
||||||
|
#: model:ir.model.fields,field_description:project_dashboard_billable_hours.field_project_project__smart_search
|
||||||
|
msgid "Smart Search"
|
||||||
|
msgstr ""
|
||||||
1
project_dashboard_billable_hours/models/__init__.py
Normal file
1
project_dashboard_billable_hours/models/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from . import project_project
|
||||||
106
project_dashboard_billable_hours/models/project_project.py
Normal file
106
project_dashboard_billable_hours/models/project_project.py
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
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):
|
||||||
|
print("LALAALALALALALLA", self._ids)
|
||||||
|
print("LALAALALALALALLA", self.ids)
|
||||||
|
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
|
||||||
Reference in New Issue
Block a user