[IMP] hr_timesheet,timesheet_grid: add new pivot measures and generic improvements
A new report was created for timesheets, also introducing new measures regarding the (non-)billable time (percentage) and the timesheets revenues/costs. Some general improvements were furthermore introduced in the timesheet apps. See task-2783955 closes odoo/odoo#88072 Related: odoo/upgrade#3509 Related: odoo/enterprise#25922 Signed-off-by: Laurent Stukkens (ltu) <ltu@odoo.com>
This commit is contained in:
committed by
Laurent Stukkens (LTU)
parent
8c3307a6b5
commit
da1b2c0c3c
@@ -1 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import project_report
|
||||
from . import timesheets_analysis_report
|
||||
|
||||
@@ -1,12 +1,120 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<record id="timesheets_analysis_report_pivot_employee" model="ir.ui.view">
|
||||
<field name="name">timesheets.analysis.report.pivot</field>
|
||||
<field name="model">timesheets.analysis.report</field>
|
||||
<field name="arch" type="xml">
|
||||
<pivot string="Timesheets Analysis" sample="1" disable_linking="True">
|
||||
<field name="employee_id" type="row"/>
|
||||
<field name="date" interval="month" type="col"/>
|
||||
<field name="amount" string="Timesheet Costs"/>
|
||||
<field name="unit_amount" type="measure" widget="timesheet_uom"/>
|
||||
</pivot>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="timesheets_analysis_report_graph_employee" model="ir.ui.view">
|
||||
<field name="name">timesheets.analysis.report.graph</field>
|
||||
<field name="model">timesheets.analysis.report</field>
|
||||
<field name="arch" type="xml">
|
||||
<graph string="Timesheets" sample="1" js_class="hr_timesheet_graphview" disable_linking="True">
|
||||
<field name="employee_id" type="row"/>
|
||||
<field name="amount" string="Timesheet Costs"/>
|
||||
<field name="unit_amount" type="measure" widget="timesheet_uom"/>
|
||||
</graph>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="timesheets_analysis_report_pivot_project" model="ir.ui.view">
|
||||
<field name="name">timesheets.analysis.report.pivot</field>
|
||||
<field name="model">timesheets.analysis.report</field>
|
||||
<field name="arch" type="xml">
|
||||
<pivot string="Timesheets Analysis" sample="1" disable_linking="True">
|
||||
<field name="project_id" type="row"/>
|
||||
<field name="date" interval="month" type="col"/>
|
||||
<field name="amount" string="Timesheet Costs"/>
|
||||
<field name="unit_amount" type="measure" widget="timesheet_uom"/>
|
||||
</pivot>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="timesheets_analysis_report_graph_project" model="ir.ui.view">
|
||||
<field name="name">timesheets.analysis.report.graph</field>
|
||||
<field name="model">timesheets.analysis.report</field>
|
||||
<field name="arch" type="xml">
|
||||
<graph string="Timesheets" sample="1" js_class="hr_timesheet_graphview" disable_linking="True">
|
||||
<field name="project_id" type="row"/>
|
||||
<field name="amount" string="Timesheet Costs"/>
|
||||
<field name="unit_amount" type="measure" widget="timesheet_uom"/>
|
||||
</graph>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="timesheets_analysis_report_pivot_task" model="ir.ui.view">
|
||||
<field name="name">timesheets.analysis.report.pivot</field>
|
||||
<field name="model">timesheets.analysis.report</field>
|
||||
<field name="arch" type="xml">
|
||||
<pivot string="Timesheets Analysis" sample="1" disable_linking="True">
|
||||
<field name="amount" string="Timesheet Costs"/>
|
||||
<field name="project_id" type="row"/>
|
||||
<field name="task_id" type="row"/>
|
||||
<field name="date" interval="month" type="col"/>
|
||||
<field name="unit_amount" type="measure" widget="timesheet_uom"/>
|
||||
</pivot>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="timesheets_analysis_report_graph_task" model="ir.ui.view">
|
||||
<field name="name">timesheets.analysis.report.graph</field>
|
||||
<field name="model">timesheets.analysis.report</field>
|
||||
<field name="arch" type="xml">
|
||||
<graph string="Timesheets" sample="1" js_class="hr_timesheet_graphview" disable_linking="True">
|
||||
<field name="project_id" type="row"/>
|
||||
<field name="task_id" type="row"/>
|
||||
<field name="amount" string="Timesheet Costs"/>
|
||||
<field name="unit_amount" type="measure" widget="timesheet_uom"/>
|
||||
</graph>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_timesheet_report_search" model="ir.ui.view">
|
||||
<field name="name">timesheets.analysis.report.search</field>
|
||||
<field name="model">timesheets.analysis.report</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Timesheet">
|
||||
<field name="date"/>
|
||||
<field name="employee_id"/>
|
||||
<field name="project_id"/>
|
||||
<field name="ancestor_task_id" groups="project.group_subtask_project"/>
|
||||
<field name="task_id"/>
|
||||
<field name="name"/>
|
||||
<field name="department_id"/>
|
||||
<field name="manager_id"/>
|
||||
<filter name="mine" string="My Timesheets" domain="[('user_id', '=', uid)]"/>
|
||||
<separator/>
|
||||
<filter name="month" string="Date" date="date"/>
|
||||
<group expand="0" string="Group By">
|
||||
<filter string="Project" name="groupby_project" domain="[]" context="{'group_by': 'project_id'}"/>
|
||||
<filter string="Ancestor Task" name="groupby_parent_task" domain="[]" context="{'group_by': 'ancestor_task_id'}" groups="project.group_subtask_project"/>
|
||||
<filter string="Task" name="groupby_task" domain="[]" context="{'group_by': 'task_id'}"/>
|
||||
<filter string="Date" name="groupby_date" domain="[]" context="{'group_by': 'date'}" help="Timesheet by Date"/>
|
||||
<filter string="Department" name="groupby_department" domain="[]" context="{'group_by': 'department_id'}"/>
|
||||
<filter string="Manager" name="groupby_manager" domain="[]" context="{'group_by': 'manager_id'}"/>
|
||||
<filter string="Employee" name="groupby_employee" domain="[]" context="{'group_by': 'employee_id'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Group by employee -->
|
||||
<record id="act_hr_timesheet_report" model="ir.actions.act_window">
|
||||
<field name="name">Timesheets by Employee</field>
|
||||
<field name="res_model">account.analytic.line</field>
|
||||
<field name="res_model">timesheets.analysis.report</field>
|
||||
<field name="domain">[('project_id', '!=', False)]</field>
|
||||
<field name="context">{'grid_range': 'week'}</field>
|
||||
<field name="search_view_id" ref="hr_timesheet_line_search"/>
|
||||
<field name="context">{}</field>
|
||||
<field name="search_view_id" ref="hr_timesheet_report_search"/>
|
||||
<field name="view_mode">pivot,graph</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_empty_folder">
|
||||
No data yet!
|
||||
@@ -20,66 +128,25 @@
|
||||
<record model="ir.actions.act_window.view" id="act_hr_timesheet_report_pivot">
|
||||
<field name="sequence" eval="5"/>
|
||||
<field name="view_mode">pivot</field>
|
||||
<field name="view_id" ref="hr_timesheet.view_hr_timesheet_line_pivot"/>
|
||||
<field name="view_id" ref="hr_timesheet.timesheets_analysis_report_pivot_employee"/>
|
||||
<field name="act_window_id" ref="act_hr_timesheet_report"/>
|
||||
</record>
|
||||
|
||||
<record id="timesheet_action_view_report_by_employee_graph" model="ir.actions.act_window.view">
|
||||
<field name="sequence" eval="6"/>
|
||||
<field name="view_mode">graph</field>
|
||||
<field name="view_id" ref="hr_timesheet.view_hr_timesheet_line_graph_all"/>
|
||||
<field name="view_id" ref="hr_timesheet.timesheets_analysis_report_graph_employee"/>
|
||||
<field name="act_window_id" ref="act_hr_timesheet_report"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.actions.act_window.view" id="act_hr_timesheet_report_tree">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view_mode">tree</field>
|
||||
<field name="view_id" ref="hr_timesheet.hr_timesheet_line_tree"/>
|
||||
<field name="act_window_id" ref="act_hr_timesheet_report"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.actions.act_window.view" id="act_hr_timesheet_report_form">
|
||||
<field name="sequence" eval="15"/>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="view_id" ref="hr_timesheet.hr_timesheet_line_form"/>
|
||||
<field name="act_window_id" ref="act_hr_timesheet_report"/>
|
||||
</record>
|
||||
|
||||
<record id="act_hr_timesheet_report_kanban" model="ir.actions.act_window.view">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view_mode">kanban</field>
|
||||
<field name="view_id" ref="hr_timesheet.view_kanban_account_analytic_line"/>
|
||||
<field name="act_window_id" ref="act_hr_timesheet_report"/>
|
||||
</record>
|
||||
|
||||
<record id="view_hr_timesheet_line_pivot_groupby_project" model="ir.ui.view">
|
||||
<field name="name">account.analytic.line.pivot</field>
|
||||
<field name="model">account.analytic.line</field>
|
||||
<field name="inherit_id" ref="hr_timesheet.view_hr_timesheet_line_pivot"/>
|
||||
<field name="mode">primary</field>
|
||||
<field name="arch" type="xml">
|
||||
<field name="employee_id" position="replace">
|
||||
<field name="project_id" type="row"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_hr_timesheet_line_graph_groupby_project" model="ir.ui.view">
|
||||
<field name="name">account.analytic.line.graph</field>
|
||||
<field name="model">account.analytic.line</field>
|
||||
<field name="inherit_id" ref="hr_timesheet.view_hr_timesheet_line_graph"/>
|
||||
<field name="mode">primary</field>
|
||||
<field name="arch" type="xml">
|
||||
<field name="task_id" position="replace"/>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Group by project-->
|
||||
<record id="timesheet_action_report_by_project" model="ir.actions.act_window">
|
||||
<field name="name">Timesheets by Project</field>
|
||||
<field name="res_model">account.analytic.line</field>
|
||||
<field name="res_model">timesheets.analysis.report</field>
|
||||
<field name="domain">[('project_id', '!=', False)]</field>
|
||||
<field name="context">{'grid_range': 'week'}</field>
|
||||
<field name="search_view_id" ref="hr_timesheet_line_search"/>
|
||||
<field name="context">{}</field>
|
||||
<field name="search_view_id" ref="hr_timesheet_report_search"/>
|
||||
<field name="view_mode">pivot,graph</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_empty_folder">
|
||||
No data yet!
|
||||
@@ -93,68 +160,25 @@
|
||||
<record id="timesheet_action_view_report_by_project_pivot" model="ir.actions.act_window.view">
|
||||
<field name="sequence" eval="5"/>
|
||||
<field name="view_mode">pivot</field>
|
||||
<field name="view_id" ref="hr_timesheet.view_hr_timesheet_line_pivot_groupby_project"/>
|
||||
<field name="view_id" ref="hr_timesheet.timesheets_analysis_report_pivot_project"/>
|
||||
<field name="act_window_id" ref="timesheet_action_report_by_project"/>
|
||||
</record>
|
||||
|
||||
<record id="timesheet_action_view_report_by_project_graph" model="ir.actions.act_window.view">
|
||||
<field name="sequence" eval="6"/>
|
||||
<field name="view_mode">graph</field>
|
||||
<field name="view_id" ref="hr_timesheet.view_hr_timesheet_line_graph_groupby_project"/>
|
||||
<field name="view_id" ref="hr_timesheet.timesheets_analysis_report_graph_project"/>
|
||||
<field name="act_window_id" ref="timesheet_action_report_by_project"/>
|
||||
</record>
|
||||
|
||||
<record id="timesheet_action_view_report_by_project_tree" model="ir.actions.act_window.view">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view_mode">tree</field>
|
||||
<field name="view_id" ref="hr_timesheet.hr_timesheet_line_tree"/>
|
||||
<field name="act_window_id" ref="timesheet_action_report_by_project"/>
|
||||
</record>
|
||||
|
||||
<record id="timesheet_action_view_report_by_project_form" model="ir.actions.act_window.view">
|
||||
<field name="sequence" eval="15"/>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="view_id" ref="hr_timesheet.hr_timesheet_line_form"/>
|
||||
<field name="act_window_id" ref="timesheet_action_report_by_project"/>
|
||||
</record>
|
||||
|
||||
<record id="timesheet_action_view_report_by_project_kanban" model="ir.actions.act_window.view">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view_mode">kanban</field>
|
||||
<field name="view_id" ref="hr_timesheet.view_kanban_account_analytic_line"/>
|
||||
<field name="act_window_id" ref="timesheet_action_report_by_project"/>
|
||||
</record>
|
||||
|
||||
<record id="view_hr_timesheet_line_pivot_groupby_task" model="ir.ui.view">
|
||||
<field name="name">account.analytic.line.pivot</field>
|
||||
<field name="model">account.analytic.line</field>
|
||||
<field name="inherit_id" ref="view_hr_timesheet_line_pivot_groupby_project"/>
|
||||
<field name="mode">primary</field>
|
||||
<field name="arch" type="xml">
|
||||
<field name="date" position="after">
|
||||
<field name="task_id" type="row"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_hr_timesheet_line_graph_groupby_task" model="ir.ui.view">
|
||||
<field name="name">account.analytic.line.graph</field>
|
||||
<field name="model">account.analytic.line</field>
|
||||
<field name="inherit_id" ref="hr_timesheet.view_hr_timesheet_line_graph"/>
|
||||
<field name="mode">primary</field>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='project_id']" position="after">
|
||||
<field name="task_id" position="move"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Group by task -->
|
||||
<record id="timesheet_action_report_by_task" model="ir.actions.act_window">
|
||||
<field name="name">Timesheets by Task</field>
|
||||
<field name="res_model">account.analytic.line</field>
|
||||
<field name="res_model">timesheets.analysis.report</field>
|
||||
<field name="domain">[('project_id', '!=', False)]</field>
|
||||
<field name="context">{'grid_range': 'week'}</field>
|
||||
<field name="search_view_id" ref="hr_timesheet_line_search"/>
|
||||
<field name="context">{}</field>
|
||||
<field name="search_view_id" ref="hr_timesheet_report_search"/>
|
||||
<field name="view_mode">pivot,graph</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_empty_folder">
|
||||
No data yet!
|
||||
@@ -168,35 +192,14 @@
|
||||
<record id="timesheet_action_view_report_by_task_pivot" model="ir.actions.act_window.view">
|
||||
<field name="sequence" eval="5"/>
|
||||
<field name="view_mode">pivot</field>
|
||||
<field name="view_id" ref="hr_timesheet.view_hr_timesheet_line_pivot_groupby_task"/>
|
||||
<field name="view_id" ref="hr_timesheet.timesheets_analysis_report_pivot_task"/>
|
||||
<field name="act_window_id" ref="timesheet_action_report_by_task"/>
|
||||
</record>
|
||||
|
||||
<record id="timesheet_action_view_report_by_task_graph" model="ir.actions.act_window.view">
|
||||
<field name="sequence" eval="6"/>
|
||||
<field name="view_mode">graph</field>
|
||||
<field name="view_id" ref="hr_timesheet.view_hr_timesheet_line_graph_groupby_task"/>
|
||||
<field name="act_window_id" ref="timesheet_action_report_by_task"/>
|
||||
</record>
|
||||
|
||||
<record id="timesheet_action_view_report_by_task_tree" model="ir.actions.act_window.view">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view_mode">tree</field>
|
||||
<field name="view_id" ref="hr_timesheet.hr_timesheet_line_tree"/>
|
||||
<field name="act_window_id" ref="timesheet_action_report_by_task"/>
|
||||
</record>
|
||||
|
||||
<record id="timesheet_action_view_report_by_task_form" model="ir.actions.act_window.view">
|
||||
<field name="sequence" eval="15"/>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="view_id" ref="hr_timesheet.hr_timesheet_line_form"/>
|
||||
<field name="act_window_id" ref="timesheet_action_report_by_task"/>
|
||||
</record>
|
||||
|
||||
<record id="timesheet_action_view_report_by_task_kanban" model="ir.actions.act_window.view">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view_mode">kanban</field>
|
||||
<field name="view_id" ref="hr_timesheet.view_kanban_account_analytic_line"/>
|
||||
<field name="view_id" ref="hr_timesheet.timesheets_analysis_report_graph_task"/>
|
||||
<field name="act_window_id" ref="timesheet_action_report_by_task"/>
|
||||
</record>
|
||||
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
from psycopg2 import sql
|
||||
|
||||
from odoo import api, tools, fields, models
|
||||
|
||||
|
||||
class TimesheetsAnalysisReport(models.Model):
|
||||
_name = "timesheets.analysis.report"
|
||||
_description = "Timesheets Analysis Report"
|
||||
_auto = False
|
||||
|
||||
name = fields.Char("Description", readonly=True)
|
||||
user_id = fields.Many2one("res.users", string="User", readonly=True)
|
||||
project_id = fields.Many2one("project.project", string="Project", readonly=True)
|
||||
task_id = fields.Many2one("project.task", string="Task", readonly=True)
|
||||
ancestor_task_id = fields.Many2one("project.task", string="Ancestor Task", readonly=True)
|
||||
employee_id = fields.Many2one("hr.employee", string="Employee", readonly=True)
|
||||
manager_id = fields.Many2one("hr.employee", "Manager", readonly=True)
|
||||
company_id = fields.Many2one("res.company", string="Company", readonly=True)
|
||||
department_id = fields.Many2one("hr.department", string="Department", readonly=True)
|
||||
currency_id = fields.Many2one('res.currency', string="Currency", readonly=True)
|
||||
date = fields.Date("Date", readonly=True)
|
||||
amount = fields.Monetary("Amount", readonly=True)
|
||||
unit_amount = fields.Float("Hours Spent", readonly=True)
|
||||
|
||||
@property
|
||||
def _table_query(self):
|
||||
return "%s %s %s" % (self._select(), self._from(), self._where())
|
||||
|
||||
@api.model
|
||||
def _select(self):
|
||||
return """
|
||||
SELECT
|
||||
A.id AS id,
|
||||
A.name AS name,
|
||||
A.user_id AS user_id,
|
||||
A.project_id AS project_id,
|
||||
A.task_id AS task_id,
|
||||
A.ancestor_task_id AS ancestor_task_id,
|
||||
A.employee_id AS employee_id,
|
||||
A.manager_id AS manager_id,
|
||||
A.company_id AS company_id,
|
||||
A.department_id AS department_id,
|
||||
A.currency_id AS currency_id,
|
||||
A.date AS date,
|
||||
A.amount AS amount,
|
||||
A.unit_amount AS unit_amount
|
||||
"""
|
||||
|
||||
@api.model
|
||||
def _from(self):
|
||||
return "FROM account_analytic_line A"
|
||||
|
||||
@api.model
|
||||
def _where(self):
|
||||
return "WHERE A.project_id IS NOT NULL"
|
||||
|
||||
@api.model
|
||||
def _get_view(self, view_id=None, view_type='form', **options):
|
||||
result = super()._get_view(view_id, view_type, **options)
|
||||
if view_type in ["pivot", "graph"] and self.env.company.timesheet_encode_uom_id == self.env.ref("uom.product_uom_day"):
|
||||
result["arch"] = self.env["account.analytic.line"]._apply_time_label(result["arch"], related_model=self._name)
|
||||
return result
|
||||
|
||||
def init(self):
|
||||
tools.drop_view_if_exists(self.env.cr, self._table)
|
||||
self.env.cr.execute(
|
||||
sql.SQL("CREATE or REPLACE VIEW {} as ({})").format(
|
||||
sql.Identifier(self._table),
|
||||
sql.SQL(self._table_query)
|
||||
)
|
||||
)
|
||||
@@ -86,5 +86,10 @@
|
||||
<field name="implied_ids" eval="[(4, ref('hr_timesheet.group_hr_timesheet_approver'))]"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.rule" id="timesheets_analysis_report_comp_rule">
|
||||
<field name="name">Timesheets Analysis Report multi-company</field>
|
||||
<field name="model_id" ref="model_timesheets_analysis_report"/>
|
||||
<field name="domain_force">[('company_id', 'in', company_ids)]</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
|
||||
@@ -4,3 +4,5 @@ access_account_analytic_user,analytic.account.analytic.timesheet.user,analytic.m
|
||||
access_uom_uom_hr_timesheet,uom.uom.timesheet.user,uom.model_uom_uom,hr_timesheet.group_hr_timesheet_user,1,0,0,0
|
||||
access_project_project,project.project.timesheet.user,model_project_project,hr_timesheet.group_hr_timesheet_user,1,0,0,0
|
||||
access_project_task,project.task.timesheet.user,model_project_task,hr_timesheet.group_hr_timesheet_user,1,1,0,0
|
||||
access_timesheets_analysis_report_manager,timesheets.analysis.report,model_timesheets_analysis_report,hr_timesheet.group_timesheet_manager,1,1,1,1
|
||||
access_timesheets_analysis_report_user,timesheets.analysis.report,model_timesheets_analysis_report,hr_timesheet.group_hr_timesheet_user,1,0,1,0
|
||||
|
||||
|
@@ -3,6 +3,7 @@
|
||||
from . import controllers
|
||||
from . import models
|
||||
from . import wizard
|
||||
from . import report
|
||||
|
||||
from odoo import api, SUPERUSER_ID
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ have real delivered quantities in sales orders.
|
||||
'depends': ['sale_project', 'hr_timesheet'],
|
||||
'data': [
|
||||
'data/sale_service_data.xml',
|
||||
'report/timesheets_analysis_views.xml',
|
||||
'security/ir.model.access.csv',
|
||||
'security/sale_timesheet_security.xml',
|
||||
'views/account_invoice_views.xml',
|
||||
|
||||
@@ -6,6 +6,17 @@ from odoo.exceptions import UserError
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.osv import expression
|
||||
|
||||
TIMESHEET_INVOICE_TYPES = [
|
||||
('billable_time', 'Billed on Timesheets'),
|
||||
('billable_fixed', 'Billed at a Fixed price'),
|
||||
('billable_milestones', 'Billed on Milestones'),
|
||||
('billable_manual', 'Billed Manually'),
|
||||
('non_billable', 'Non Billable Tasks'),
|
||||
('timesheet_revenues', 'Timesheet Revenues'),
|
||||
('service_revenues', 'Service Revenues'),
|
||||
('other_revenues', 'Other Revenues'),
|
||||
('other_costs', 'Other Costs'),
|
||||
]
|
||||
|
||||
class AccountAnalyticLine(models.Model):
|
||||
_inherit = 'account.analytic.line'
|
||||
@@ -14,16 +25,7 @@ class AccountAnalyticLine(models.Model):
|
||||
domain = super(AccountAnalyticLine, self)._default_sale_line_domain()
|
||||
return expression.OR([domain, [('qty_delivered_method', '=', 'timesheet')]])
|
||||
|
||||
timesheet_invoice_type = fields.Selection([
|
||||
('billable_time', 'Billed on Timesheets'),
|
||||
('billable_fixed', 'Billed at a Fixed price'),
|
||||
('billable_milestones', 'Billed on Milestones'),
|
||||
('billable_manual', 'Billed Manually'),
|
||||
('non_billable', 'Non Billable Tasks'),
|
||||
('timesheet_revenues', 'Timesheet Revenues'),
|
||||
('service_revenues', 'Service Revenues'),
|
||||
('other_revenues', 'Other Revenues'),
|
||||
('other_costs', 'Other Costs')], string="Billing Type",
|
||||
timesheet_invoice_type = fields.Selection(TIMESHEET_INVOICE_TYPES, string="Billable Type",
|
||||
compute='_compute_timesheet_invoice_type', compute_sudo=True, store=True, readonly=True)
|
||||
commercial_partner_id = fields.Many2one('res.partner', compute="_compute_commercial_partner")
|
||||
timesheet_invoice_id = fields.Many2one('account.move', string="Invoice", readonly=True, copy=False, help="Invoice created from the timesheet")
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import timesheets_analysis_report
|
||||
@@ -0,0 +1,41 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models, api
|
||||
|
||||
from odoo.addons.sale_timesheet.models.account import TIMESHEET_INVOICE_TYPES
|
||||
|
||||
|
||||
class TimesheetsAnalysisReport(models.Model):
|
||||
_inherit = "timesheets.analysis.report"
|
||||
|
||||
order_id = fields.Many2one("sale.order", string="Sales Order", readonly=True)
|
||||
so_line = fields.Many2one("sale.order.line", string="Sales Order Item", readonly=True)
|
||||
timesheet_invoice_type = fields.Selection(TIMESHEET_INVOICE_TYPES, string="Billable Type", readonly=True)
|
||||
timesheet_invoice_id = fields.Many2one("account.move", string="Invoice", readonly=True, help="Invoice created from the timesheet")
|
||||
timesheet_revenues = fields.Float("Timesheet Revenues", readonly=True, help="Number of hours spent multiplied by the unit price per hour/day.")
|
||||
margin = fields.Float("Margin", readonly=True, help="Timesheets revenues minus the costs")
|
||||
billable_time = fields.Float("Billable Hours", readonly=True, help="Number of hours/days linked to a SOL.")
|
||||
non_billable_time = fields.Float("Non-billable Hours", readonly=True, help="Number of hours/days not linked to a SOL.")
|
||||
|
||||
@property
|
||||
def _table_query(self):
|
||||
return """
|
||||
SELECT A.*,
|
||||
(timesheet_revenues + A.amount) AS margin,
|
||||
(A.unit_amount - billable_time) AS non_billable_time
|
||||
FROM (
|
||||
%s %s %s
|
||||
) A
|
||||
""" % (self._select(), self._from(), self._where())
|
||||
|
||||
@api.model
|
||||
def _select(self):
|
||||
return super()._select() + """,
|
||||
A.order_id AS order_id,
|
||||
A.so_line AS so_line,
|
||||
A.timesheet_invoice_type AS timesheet_invoice_type,
|
||||
A.timesheet_invoice_id AS timesheet_invoice_id,
|
||||
(-1 * A.unit_amount * A.amount) AS timesheet_revenues,
|
||||
CASE WHEN order_id IS NULL THEN 0 ELSE unit_amount END AS billable_time
|
||||
"""
|
||||
@@ -0,0 +1,111 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="timesheets_analysis_report_pivot_inherit" model="ir.ui.view">
|
||||
<field name="name">timesheets.analysis.report.pivot</field>
|
||||
<field name="model">timesheets.analysis.report</field>
|
||||
<field name="inherit_id" ref="hr_timesheet.timesheets_analysis_report_pivot_employee"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='unit_amount']" position="after">
|
||||
<field name="billable_time" widget="timesheet_uom"/>
|
||||
<field name="non_billable_time" widget="timesheet_uom"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="timesheets_analysis_report_graph_inherit" model="ir.ui.view">
|
||||
<field name="name">timesheets.analysis.report.graph</field>
|
||||
<field name="model">timesheets.analysis.report</field>
|
||||
<field name="inherit_id" ref="hr_timesheet.timesheets_analysis_report_pivot_employee"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='unit_amount']" position="after">
|
||||
<field name="billable_time" widget="timesheet_uom"/>
|
||||
<field name="non_billable_time" widget="timesheet_uom"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="timesheets_analysis_report_pivot_invoice_type" model="ir.ui.view">
|
||||
<field name="name">timesheets.analysis.report.pivot</field>
|
||||
<field name="model">timesheets.analysis.report</field>
|
||||
<field name="arch" type="xml">
|
||||
<pivot string="Timesheets Analysis" sample="1" disable_linking="True">
|
||||
<field name="date" interval="month" type="row"/>
|
||||
<field name="timesheet_invoice_type" type="col"/>
|
||||
<field name="amount" string="Timesheet Costs"/>
|
||||
<field name="unit_amount" type="measure" widget="timesheet_uom"/>
|
||||
</pivot>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="timesheets_analysis_report_graph_invoice_type" model="ir.ui.view">
|
||||
<field name="name">timesheets.analysis.report.graph</field>
|
||||
<field name="model">timesheets.analysis.report</field>
|
||||
<field name="arch" type="xml">
|
||||
<graph string="Timesheets" sample="1" js_class="hr_timesheet_graphview" disable_linking="True">
|
||||
<field name="amount" string="Timesheet Costs"/>
|
||||
<field name="unit_amount" type="measure" widget="timesheet_uom"/>
|
||||
<field name="timesheet_invoice_type" type="row"/>
|
||||
</graph>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_timesheet_report_search_sale_timesheet" model="ir.ui.view">
|
||||
<field name="name">timesheets.analysis.report.search</field>
|
||||
<field name="model">timesheets.analysis.report</field>
|
||||
<field name="inherit_id" ref="hr_timesheet.hr_timesheet_report_search"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='task_id']" position="after">
|
||||
<field name="order_id" string="Sales Order" groups="sales_team.group_sale_salesman"/>
|
||||
<field name="so_line" groups="sales_team.group_sale_salesman"/>
|
||||
</xpath>
|
||||
<xpath expr="//filter[@name='month']" position="before">
|
||||
<filter name="billable_time" string="Billed on Timesheets" domain="[('timesheet_invoice_type', '=', 'billable_time')]"/>
|
||||
<filter name="billable_fixed" string="Billed at a Fixed Price" domain="[('timesheet_invoice_type', '=', 'billable_fixed')]"/>
|
||||
<filter name="non_billable" string="Non Billable" domain="[('timesheet_invoice_type', '=', 'non_billable')]"/>
|
||||
<separator/>
|
||||
</xpath>
|
||||
<xpath expr="//filter[@name='groupby_employee']" position="after">
|
||||
<filter string="Sales Order" name="groupby_sale_order" domain="[]" context="{'group_by': 'order_id'}" groups="sales_team.group_sale_salesman"/>
|
||||
<filter string="Sales Order Item" name="groupby_sale_order_item" domain="[]" context="{'group_by': 'so_line'}" groups="sales_team.group_sale_salesman"/>
|
||||
<filter string="Invoice" name="groupby_invoice" domain="[]" context="{'group_by': 'timesheet_invoice_id'}"/>
|
||||
<filter string="Billable Type" name="groupby_timesheet_invoice_type" domain="[]" context="{'group_by': 'timesheet_invoice_type'}"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="timesheet_action_billing_report" model="ir.actions.act_window">
|
||||
<field name="name">Timesheets by Billing Type</field>
|
||||
<field name="res_model">timesheets.analysis.report</field>
|
||||
<field name="domain">[('project_id', '!=', False)]</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
No data yet!
|
||||
</p>
|
||||
<p>Review your timesheets by billing type and make sure your time is billable.</p>
|
||||
</field>
|
||||
<field name="search_view_id" ref="hr_timesheet.hr_timesheet_report_search"/>
|
||||
<field name="view_mode">pivot,graph</field>
|
||||
</record>
|
||||
|
||||
<record id="timesheet_action_view_report_by_billing_rate_pivot" model="ir.actions.act_window.view">
|
||||
<field name="sequence" eval="5"/>
|
||||
<field name="view_mode">pivot</field>
|
||||
<field name="view_id" ref="timesheets_analysis_report_pivot_invoice_type"/>
|
||||
<field name="act_window_id" ref="timesheet_action_billing_report"/>
|
||||
</record>
|
||||
|
||||
<record id="timesheet_action_view_report_by_billing_rate_graph" model="ir.actions.act_window.view">
|
||||
<field name="sequence" eval="6"/>
|
||||
<field name="view_mode">graph</field>
|
||||
<field name="view_id" ref="timesheets_analysis_report_graph_invoice_type"/>
|
||||
<field name="act_window_id" ref="timesheet_action_billing_report"/>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_timesheet_billing_analysis"
|
||||
parent="hr_timesheet.menu_timesheets_reports_timesheet"
|
||||
action="timesheet_action_billing_report"
|
||||
name="By Billing Type"
|
||||
sequence="40"/>
|
||||
|
||||
</odoo>
|
||||
@@ -160,82 +160,6 @@
|
||||
<field name="act_window_id" ref="timesheet_action_from_sales_order_item"/>
|
||||
</record>
|
||||
|
||||
<!--
|
||||
Reporting
|
||||
-->
|
||||
<record id="timesheet_action_billing_report" model="ir.actions.act_window">
|
||||
<field name="name">Timesheets by Billing Type</field>
|
||||
<field name="res_model">account.analytic.line</field>
|
||||
<field name="view_mode">tree,form,pivot,graph,kanban</field>
|
||||
<field name="domain">[('project_id', '!=', False)]</field>
|
||||
<field name="context">{
|
||||
'pivot_row_groupby': ['date:month'],
|
||||
}</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_empty_folder">
|
||||
No data yet!
|
||||
</p><p>
|
||||
Analyze the projects and tasks on which your employees spend their time.<br/>
|
||||
Evaluate which part is billable and what costs it represents.
|
||||
</p>
|
||||
</field>
|
||||
<field name="search_view_id" ref="hr_timesheet.hr_timesheet_line_search"/>
|
||||
</record>
|
||||
|
||||
<record id="view_hr_timesheet_line_graph_groupby_invoice_type" model="ir.ui.view">
|
||||
<field name="name">account.analytic.line.graph</field>
|
||||
<field name="model">account.analytic.line</field>
|
||||
<field name="inherit_id" ref="hr_timesheet.view_hr_timesheet_line_graph"/>
|
||||
<field name="mode">primary</field>
|
||||
<field name="arch" type="xml">
|
||||
<field name="task_id" position="replace"/>
|
||||
<field name="project_id" position="replace">
|
||||
<field name="timesheet_invoice_type"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="timesheet_action_view_report_by_billing_rate_form" model="ir.actions.act_window.view">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="view_id" ref="hr_timesheet.hr_timesheet_line_form"/>
|
||||
<field name="act_window_id" ref="timesheet_action_billing_report"/>
|
||||
</record>
|
||||
|
||||
<record id="timesheet_action_view_report_by_billing_rate_kanban" model="ir.actions.act_window.view">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view_mode">kanban</field>
|
||||
<field name="view_id" ref="hr_timesheet.view_kanban_account_analytic_line"/>
|
||||
<field name="act_window_id" ref="timesheet_action_billing_report"/>
|
||||
</record>
|
||||
|
||||
<record id="timesheet_action_view_report_by_billing_rate_pivot" model="ir.actions.act_window.view">
|
||||
<field name="sequence" eval="5"/>
|
||||
<field name="view_mode">pivot</field>
|
||||
<field name="view_id" ref="view_hr_timesheet_line_pivot_billing_rate"/>
|
||||
<field name="act_window_id" ref="timesheet_action_billing_report"/>
|
||||
</record>
|
||||
|
||||
<record id="timesheet_action_view_report_by_billing_rate_graph" model="ir.actions.act_window.view">
|
||||
<field name="sequence" eval="6"/>
|
||||
<field name="view_mode">graph</field>
|
||||
<field name="view_id" ref="sale_timesheet.view_hr_timesheet_line_graph_groupby_invoice_type"/>
|
||||
<field name="act_window_id" ref="timesheet_action_billing_report"/>
|
||||
</record>
|
||||
|
||||
<record id="timesheet_action_view_report_by_billing_rate_tree" model="ir.actions.act_window.view">
|
||||
<field name="sequence" eval="8"/>
|
||||
<field name="view_mode">tree</field>
|
||||
<field name="view_id" ref="hr_timesheet.hr_timesheet_line_tree"/>
|
||||
<field name="act_window_id" ref="timesheet_action_billing_report"/>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_timesheet_billing_analysis"
|
||||
parent="hr_timesheet.menu_timesheets_reports_timesheet"
|
||||
action="timesheet_action_billing_report"
|
||||
name="By Billing Type"
|
||||
sequence="40"/>
|
||||
|
||||
<!--
|
||||
Plan
|
||||
-->
|
||||
|
||||
Reference in New Issue
Block a user