Sales Timesheet

sale_timesheet
REPOSITORY
REPOSITORYodoo/odoo
GIT
GIThttps://github.com/odoo/odoo.git
GIT FOLDER
GIT FOLDERhttps://github.com/odoo/odoo/tree/19.0/sale_timesheet
VERSION
VERSION 0.1.0
CATEGORY
CATEGORYSales/Sales
LICENSE
LICENSELGPL-3
APPLICATION
APPLICATIONNo
AUTO-INSTALLABLE
AUTO-INSTALLABLEYes
AUTHORS
AUTHORSOdoo S.A.
MAINTAINERS
MAINTAINERSOdoo S.A.
COMMITTERS
COMMITTERSRaphael Collet, Odoo Translation Bot, Stéphane Bidoul, Thibault Delavallée, Xavier Morel, Lucas Perais (lpe), Yannick Tivisse, Julien Castiaux, Victor Feyens, Andrea Grazioso (agr-odoo), Paul Morelle, Julien Mougenot, svs-odoo, Prakash Prajapati, Tiffany Chang (tic), Gorash, Mathieu Duckerts-Antoine, William Braeckman, william-andre, Audric Onockx (auon), Renaud Thiry, Vincent Larcin, alsh-odoo, Louis Wicket (wil), Victor Piryns (pivi), Pierre-Yves Dufays, Levi Siuzdak (sile), Antoine Boonen, Dylan Kiss (dyki), Xavier Bol (xbo), Brieuc-brd, rajh-odoo, Youssef Bashandy, bary, Maxime de Neuville, suth-odoo, kcv-odoo, Tanguy Quéguineur, nihp-odoo, aamo-odoo, thle-odoo, Mathieu Coutant, Patricia Daloze (pda), guce, Pierre Pulinckx (PIPU), Kawtar Drissi El Bouzaidi (kdeb), David Van Droogenbroeck (DROD), Mohammad Abdulmoneim (abdu), Michaël Mattiello, Pierre Pulinckx, Bastien PIERRE, K Theja (kthe), bona-odoo, Krzysztof Magusiak (krma), Mahdi Alijani (malj), Harrison Hutton, Maeva De Keyser (MDEK), nsirjacobs, mjvi-odoo, bhra-odoo, defl, mohannedah, Amr Elkhatieb, Maxime de Neuville (mane), damrOdoo, Merlin Guillaume, Abdelouahab Laaroussi (abla), Farah (ahmfa), ksin-odoo, chpa-odoo, Dhruvkumar Nagar
WEBSITE
WEBSITE
LAST TRACKING UPDATE
LAST TRACKING UPDATE2026-07-06 14:10:18
ODOO DEPENDENCIES
ODOO DEPENDENCIES odoo/odoo:
    - sale_project
    - sale_management
    - sale
    - sales_team
    - base
    - mail
    - base_setup
    - web
    - bus
    - web_tour
    - html_editor
    - account_payment
    - account
    - onboarding
    - product
    - uom
    - analytic
    - portal
    - http_routing
    - auth_signup
    - digest
    - resource
    - payment
    - utm
    - sale_service
    - project_account
    - project
    - rating
    - hr_timesheet
    - hr
    - phone_validation
    - resource_mail
    - hr_hourly_cost
PYTHON DEPENDENCIES
PYTHON DEPENDENCIES Not have
SYSTEM DEPENDENCIES
SYSTEM DEPENDENCIES Not have
DESCRIPTION
DESCRIPTION
Allows to sell timesheets in your sales order
=============================================

This module set the right product on all timesheet lines
according to the order/contract you work on. This allows to
have real delivered quantities in sales orders.

Code Analysis

Views touched (45)
XML IDNameModelTypeStatus
account_invoice_view_form_inherit_sale_timesheet account.invoice.form.inherit.timesheet account.move xpath Inherits account.view_move_form
hr_timesheet_line_form_inherit account.analytic.line.form.inherit account.analytic.line xpath Inherits hr_timesheet.hr_timesheet_line_form
hr_timesheet_line_tree_inherit account.analytic.line.list.inherit account.analytic.line xpath Inherits hr_timesheet.hr_timesheet_line_tree
hr_timesheet_report_search_sale_timesheet timesheets.analysis.report.search timesheets.analysis.report search Inherits sale_timesheet.timesheet_view_search
portal_invoice_page_inherit portal_invoice_page_inherit ir.ui.view qweb Inherits account.portal_invoice_page
portal_my_task_inherit portal_my_task_inherit ir.ui.view qweb Inherits project.portal_my_task
portal_my_timesheets_inherit portal_my_timesheets_inherit ir.ui.view qweb Inherits hr_timesheet.portal_my_timesheets
portal_timesheet_table_inherit portal_timesheet_table_inherit ir.ui.view qweb Inherits hr_timesheet.portal_timesheet_table
product_template_view_search_sale_timesheet product.template.search.timesheet product.template filter Inherits product.product_template_search_view
project_project_view_form project.project.form.inherit project.project xpath Inherits hr_timesheet.project_invoice_form
project_project_view_kanban_inherit_sale_timesheet project.project.kanban.inherit.sale.timesheet project.project xpath Inherits hr_timesheet.view_project_kanban_inherited
project_project_view_kanban_inherit_sale_timesheet_so_button project.project.kanban.inherit.sale.timesheet.so.button project.project xpath Inherits project.view_project_kanban
project_sharing_inherit_project_task_view_form project.task.form.inherit.timesheet project.task xpath Inherits hr_timesheet.project_sharing_inherit_project_task_view_form
project_task_view_form_inherit_sale_timesheet project.task.form.inherit.timesheet project.task xpath Inherits project.view_task_form2
project_task_view_search_inherit_sale_timesheet project.task.view.search.inherit project.task filter Inherits hr_timesheet.project_task_view_search
report_timesheet_account_move report_timesheet_account_move ir.ui.view qweb New
report_timesheet_sale_order report_timesheet_sale_order ir.ui.view qweb New
res_config_settings_view_form res.config.settings.view.form.inherit.sale.timesheet res.config.settings xpath Inherits hr_timesheet.res_config_settings_view_form
sale_advance_payment_inv_timesheet_view_form sale_timesheet.sale.advance.payment.inv.view.form sale.advance.payment.inv form Inherits sale.view_sale_advance_payment_inv
sale_order_portal_content_inherit sale_order_portal_content_inherit ir.ui.view qweb Inherits sale.sale_order_portal_template
timesheet_sale_page timesheet_sale_page ir.ui.view qweb New
timesheet_view_search account.analytic.line.search account.analytic.line xpath Inherits hr_timesheet.hr_timesheet_line_search
timesheete_analysis_report_form timesheets.analysis.report.form timesheets.analysis.report xpath Inherits hr_timesheet.timesheets_analysis_report_form
timesheets_analysis_report_graph_inherit timesheets.analysis.report.graph timesheets.analysis.report xpath Inherits hr_timesheet.timesheets_analysis_report_pivot_employee
timesheets_analysis_report_graph_invoice_type timesheets.analysis.report.graph timesheets.analysis.report graph New
timesheets_analysis_report_graph_project_inherit timesheets.analysis.report.graph.project timesheets.analysis.report xpath Inherits hr_timesheet.timesheets_analysis_report_graph_project
timesheets_analysis_report_graph_task_inherit timesheets.analysis.report.graph.task timesheets.analysis.report xpath Inherits hr_timesheet.timesheets_analysis_report_graph_task
timesheets_analysis_report_graph_timesheet_grid timesheets.analysis.report.graph timesheets.analysis.report xpath Inherits hr_timesheet.timesheets_analysis_report_graph_employee
timesheets_analysis_report_list_inherited timesheets.analysis.report.list.inherited timesheets.analysis.report xpath Inherits hr_timesheet.timesheets_analysis_report_list
timesheets_analysis_report_pivot_inherit timesheets.analysis.report.pivot timesheets.analysis.report xpath Inherits hr_timesheet.timesheets_analysis_report_pivot_employee
timesheets_analysis_report_pivot_invoice_type timesheets.analysis.report.pivot timesheets.analysis.report pivot New
timesheets_analysis_report_pivot_project_inherit timesheets.analysis.report.pivot.project timesheets.analysis.report xpath Inherits hr_timesheet.timesheets_analysis_report_pivot_project
timesheets_analysis_report_pivot_task_inherit timesheets.analysis.report.pivot.task timesheets.analysis.report xpath Inherits hr_timesheet.timesheets_analysis_report_pivot_task
view_calendar_account_analytic_line account.analytic.line.calendar account.analytic.line field Inherits hr_timesheet.view_calendar_account_analytic_line
view_calendar_account_analytic_line_multi_create account.analytic.line.calendar.multi_create account.analytic.line field Inherits hr_timesheet.view_calendar_account_analytic_line_multi_create
view_hr_timesheet_line_graph_employee_per_date account.analytic.line.graph.employee.per.date account.analytic.line graph New
view_hr_timesheet_line_graph_invoice_employee account.analytic.line.graph.invoice.employee account.analytic.line xpath Inherits view_hr_timesheet_line_graph_employee_per_date
view_hr_timesheet_line_pivot_billing_rate account.analytic.line.pivot.billing.rate account.analytic.line pivot New
view_hr_timesheet_line_pivot_inherited account.analytic.line.pivot account.analytic.line xpath Inherits hr_timesheet.view_hr_timesheet_line_pivot
view_order_form_inherit_sale_timesheet sale.order.form.sale.timesheet sale.order button Inherits sale_project.view_order_form_inherit_sale_project
view_product_timesheet_form product.template.timesheet.form product.template field Inherits sale.product_template_form_view
view_task_project_user_fsm_graph_base_inherited report.project.task.user.fsm.graph.base.inherited report.project.task.user field Inherits project.view_task_project_user_fsm_graph_base
view_task_project_user_fsm_pivot_base_inherited report.project.task.user.fsm.pivot.base.inherited report.project.task.user field Inherits project.view_task_project_user_fsm_pivot_base
view_task_project_user_pivot_inherited report.project.task.user.pivot.inherited report.project.task.user field Inherits project.view_task_project_user_pivot
view_task_tree2_inherited project.task.list.inherited project.task xpath Inherits hr_timesheet.view_task_tree2_inherited
Models touched (16)

New fields (8)
  • allow_billable Boolean
    related='project_id.allow_billable'
  • commercial_partner_id Many2one → res.partner
    compute='_compute_commercial_partner' args: 'res.partner'
  • is_so_line_edited Boolean
    args: 'Is Sales Order Item Manually Edited'
  • order_id Many2one
    index=True readonly=True related='so_line.order_id' store=True
  • sale_order_state Selection
    related='order_id.state'
  • so_line Many2one
    compute='_compute_so_line' domain=_domain_so_line falsy_value_label='Non-billable' help='Sales order item to which the time spent will be added in order to be invoiced to your customer. Remove the sales order item for the timesheet entry to be non-billable.' readonly=False store=True
  • timesheet_invoice_id Many2one → account.move
    copy=False help='Invoice created from the timesheet' index='btree_not_null' readonly=True string='Invoice' args: 'account.move'
  • timesheet_invoice_type Selection
    compute='_compute_timesheet_invoice_type' compute_sudo=True readonly=True store=True string='Billable Type' args: TIMESHEET_INVOICE_TYPES
Public methods (2)
  • action_invoice_from_timesheet(self)
  • action_sale_order_from_timesheet(self)

New fields (4)
  • timesheet_count Integer
    compute='_compute_timesheet_count' compute_sudo=True export_string_translation=False args: 'Number of timesheets'
  • timesheet_encode_uom_id Many2one → uom.uom
    export_string_translation=False related='company_id.timesheet_encode_uom_id' args: 'uom.uom'
  • timesheet_ids One2many → account.analytic.line
    copy=False export_string_translation=False readonly=True string='Timesheets' args: 'account.analytic.line', 'timesheet_invoice_id'
  • timesheet_total_duration Integer
    compute='_compute_timesheet_total_duration' compute_sudo=True help='Total recorded duration, expressed in the encoding UoM, and rounded to the unit' args: 'Timesheet Total Duration'
Public methods (2)
  • action_post(self)
  • action_view_timesheet(self)

New fields (0)

No new fields.

Public methods (1)
  • unlink(self)

New fields (0)

No new fields.

Public methods (1)
  • reverse_moves(self, is_modify=False)

New fields (0)

No new fields.

Public methods (1)
  • default_get(self, fields)
    @api.model

New fields (0)

No new fields.

Public methods (1)
  • write(self, vals)

New fields (5)
  • project_id Many2one
    domain="['|', ('company_id', '=', False), '&', ('company_id', '=?', company_id), ('company_id', '=', current_company_id), ('allow_billable', '=', True), ('pricing_type', '=', 'task_rate'), ('allow_timesheets', 'in', [service_policy == 'delivered_timesheet', True]), ('is_template', '=', False)]"
  • project_template_id Many2one
    domain="['|', ('company_id', '=', False), '&', ('company_id', '=?', company_id), ('company_id', '=', current_company_id), ('allow_billable', '=', True), ('allow_timesheets', 'in', [service_policy == 'delivered_timesheet', True]), ('is_template', '=', True)]"
  • service_type Selection
    ondelete={'timesheet': 'set manual'} selection_add=[('timesheet', 'Timesheets on project (one fare per SO/Project)')]
  • service_upsell_threshold Float
    default=1 help='Percentage of time delivered compared to the prepaid amount that must be reached for the upselling opportunity activity to be triggered.' args: 'Threshold'
  • service_upsell_threshold_ratio Char
    compute='_compute_service_upsell_threshold_ratio' export_string_translation=False
Public methods (1)
  • write(self, vals)

New fields (7)
  • allocated_hours Float
  • billing_type Selection
    compute='_compute_billing_type' default='not_billable' readonly=False required=True selection=[('not_billable', 'not billable'), ('manually', 'billed manually')] store=True
  • partner_id Many2one
    compute='_compute_partner_id' readonly=False store=True
  • pricing_type Selection
    compute='_compute_pricing_type' default='task_rate' help='The task rate is perfect if you would like to bill different services to different customers at different rates. The fixed rate is perfect if you bill a service at a fixed rate per hour or day worked regardless of the employee who performed it. The employee rate is preferable if your employees deliver the same service at a different rate. For instance, junior and senior consultants would deliver the same service (= consultancy), but at a different rate because of their level of seniority.' search='_search_pricing_type' string='Pricing' args: [('task_rate', 'Task rate'), ('fixed_rate', 'Project rate'), ('employee_rate', 'Employee rate')]
  • sale_line_employee_ids One2many → project.sale.line.employee.map
    copy=False export_string_translation=False help="Sales order item that will be selected by default on the timesheets of the corresponding employee. It bypasses the sales order item defined on the project and the task, and can be modified on each timesheet entry if necessary. In other words, it defines the rate at which an employee's time is billed based on their expertise, skills or experience, for instance.\nIf you would like to bill the same service at a different rate, you need to create two separate sales order items as each sales order item can only have a single unit price at a time.\nYou can also define the hourly company cost of your employees for their timesheets on this project specifically. It will bypass the timesheet cost set on the employee." args: 'project.sale.line.employee.map', 'project_id', 'Sale line/Employee map'
  • timesheet_product_id Many2one → product.product
    check_company=True compute='_compute_timesheet_product_id' default=_default_timesheet_product_id domain="[\n ('type', '=', 'service'),\n ('invoice_policy', '=', 'delivery'),\n ('service_type', '=', 'timesheet'),\n ]" help='Service that will be used by default when invoicing the time spent on a task. It can be modified on each task individually by selecting a specific sales order item.' readonly=False store=True string='Timesheet Product' args: 'product.product'
  • warning_employee_rate Boolean
    compute='_compute_warning_employee_rate' compute_sudo=True export_string_translation=False
Public methods (7)
  • action_billable_time_button(self)
  • action_profitability_items(self, section_name, domain=None, res_id=False)
  • action_project_timesheets(self)
  • action_view_timesheet(self)
  • default_get(self, fields)
    @api.model
    Pre-fill timesheet product as "Time" data product when creating new project allowing billable tasks by default.
  • get_panel_data(self)
  • write(self, vals)

New fields (13)
  • company_id Many2one → res.company
    export_string_translation=False related='project_id.company_id' string='Company' args: 'res.company'
  • cost Monetary
    compute='_compute_cost' currency_field='cost_currency_id' help="This cost overrides the employee's default employee hourly wage in employee's HR Settings" readonly=False store=True
  • cost_currency_id Many2one → res.currency
    export_string_translation=False readonly=True related='employee_id.currency_id' string='Cost Currency' args: 'res.currency'
  • currency_id Many2one → res.currency
    compute='_compute_currency_id' readonly=False store=True string='Currency' args: 'res.currency'
  • display_cost Monetary
    compute='_compute_display_cost' currency_field='cost_currency_id' groups='project.group_project_manager,hr.group_hr_user' inverse='_inverse_display_cost' string='Hourly Cost'
  • employee_id Many2one → hr.employee
    domain="[('id', 'not in', existing_employee_ids)]" required=True args: 'hr.employee', 'Employee'
  • existing_employee_ids Many2many → hr.employee
    compute='_compute_existing_employee_ids' compute_sudo=True export_string_translation=False args: 'hr.employee'
  • is_cost_changed Boolean
    compute='_compute_is_cost_changed' export_string_translation=False store=True args: 'Is Cost Manually Changed'
  • partner_id Many2one
    export_string_translation=False related='project_id.partner_id'
  • price_unit Float
    compute='_compute_price_unit' readonly=True store=True args: 'Unit Price'
  • project_id Many2one → project.project
    domain=[('is_template', '=', False)] index=True required=True args: 'project.project', 'Project'
  • sale_line_id Many2one → sale.order.line
    compute='_compute_sale_line_id' domain=<expr> readonly=False store=True args: 'sale.order.line', 'Sales Order Item'
  • sale_order_id Many2one
    export_string_translation=False related='project_id.sale_order_id'
Public methods (2)
  • create(self, vals_list)
    @api.model_create_multi
  • write(self, vals)

New fields (8)
  • has_multi_sol Boolean
    compute='_compute_has_multi_sol' compute_sudo=True
  • is_project_map_empty Boolean
    compute='_compute_is_project_map_empty' args: 'Is Project map empty'
  • last_sol_of_customer Many2one → sale.order.line
    compute='_compute_last_sol_of_customer' args: 'sale.order.line'
  • pricing_type Selection
    related='project_id.pricing_type'
  • remaining_hours_available Boolean
    related='sale_line_id.remaining_hours_available'
  • remaining_hours_so Float
    compute='_compute_remaining_hours_so' compute_sudo=True search='_search_remaining_hours_so' args: 'Time Remaining on SO'
  • sale_order_id Many2one
    domain="['|', '|', ('partner_id', '=', partner_id), ('partner_id.commercial_partner_id.id', 'parent_of', partner_id), ('partner_id', 'parent_of', partner_id)]"
  • timesheet_product_id Many2one
    related='project_id.timesheet_product_id'
Public methods (1)
  • TASK_PORTAL_READABLE_FIELDS(self)
    @property

New fields (1)
  • remaining_hours_so Float
    groups='hr_timesheet.group_hr_timesheet_user' readonly=True args: 'Time Remaining on SO'
Public methods (0)

No public methods.

New fields (1)
  • invoice_policy Boolean
    help='Timesheets taken when invoicing time spent' string='Invoice Policy'
Public methods (0)

No public methods.

New fields (3)
  • date_end_invoice_timesheet Date
    help='Only timesheets not yet invoiced (and validated, if applicable) from this period will be invoiced. If the period is not indicated, all timesheets not yet invoiced (and validated, if applicable) will be invoiced without distinction.' string='End Date'
  • date_start_invoice_timesheet Date
    help='Only timesheets not yet invoiced (and validated, if applicable) from this period will be invoiced. If the period is not indicated, all timesheets not yet invoiced (and validated, if applicable) will be invoiced without distinction.' string='Start Date'
  • invoicing_timesheet_enabled Boolean
    compute='_compute_invoicing_timesheet_enabled' export_string_translation=False store=True
Public methods (0)

No public methods.

New fields (4)
  • show_hours_recorded_button Boolean
    compute='_compute_show_hours_recorded_button' compute_sudo=True export_string_translation=False groups='hr_timesheet.group_hr_timesheet_user'
  • timesheet_count Float
    compute='_compute_timesheet_count' export_string_translation=False groups='hr_timesheet.group_hr_timesheet_user' string='Timesheet activities'
  • timesheet_encode_uom_id Many2one → uom.uom
    export_string_translation=False related='company_id.timesheet_encode_uom_id' args: 'uom.uom'
  • timesheet_total_duration Integer
    compute='_compute_timesheet_total_duration' compute_sudo=True export_string_translation=False groups='hr_timesheet.group_hr_timesheet_user' help='Total recorded duration, expressed in the encoding UoM, and rounded to the unit' args: 'Timesheet Total Duration'
Public methods (2)
  • action_view_timesheet(self)
  • create(self, vals_list)
    @api.model_create_multi

New fields (6)
  • analytic_line_ids One2many
    domain=[('project_id', '=', False)]
  • has_displayed_warning_upsell Boolean
    copy=False export_string_translation=False args: 'Has Displayed Warning Upsell'
  • qty_delivered_method Selection
    selection_add=[('timesheet', 'Timesheets')]
  • remaining_hours Float
    compute='_compute_remaining_hours' compute_sudo=True store=True args: 'Time Remaining on SO'
  • remaining_hours_available Boolean
    compute='_compute_remaining_hours_available' compute_sudo=True
  • timesheet_ids One2many → account.analytic.line
    domain=[('project_id', '!=', False)] export_string_translation=False string='Timesheets' args: 'account.analytic.line', 'so_line'
Public methods (0)

No public methods.

New fields (8)
  • billable_time Float
    help='Number of hours/days linked to a SOL.' readonly=True args: 'Billable Time'
  • margin Monetary
    currency_field='currency_id' help='Timesheets revenues minus the costs' readonly=True args: 'Margin'
  • non_billable_time Float
    help='Number of hours/days not linked to a SOL.' readonly=True args: 'Non-billable Time'
  • order_id Many2one → sale.order
    readonly=True string='Sales Order' args: 'sale.order'
  • so_line Many2one → sale.order.line
    readonly=True string='Sales Order Item' args: 'sale.order.line'
  • timesheet_invoice_id Many2one → account.move
    help='Invoice created from the timesheet' readonly=True string='Invoice' args: 'account.move'
  • timesheet_invoice_type Selection
    readonly=True string='Billable Type' args: TIMESHEET_INVOICE_TYPES
  • timesheet_revenues Monetary
    currency_field='currency_id' help='Number of hours spent multiplied by the unit price per hour/day.' readonly=True args: 'Timesheet Revenues'
Public methods (0)

No public methods.

REPOSITORY
REPOSITORYodoo/odoo
GIT
GIThttps://github.com/odoo/odoo.git
GIT FOLDER
GIT FOLDERhttps://github.com/odoo/odoo/tree/18.0/sale_timesheet
VERSION
VERSION 0.1.0
CATEGORY
CATEGORYHidden
LICENSE
LICENSELGPL-3
APPLICATION
APPLICATIONNo
AUTO-INSTALLABLE
AUTO-INSTALLABLEYes
AUTHORS
AUTHORS
MAINTAINERS
MAINTAINERS
COMMITTERS
COMMITTERSRaphael Collet, Odoo Translation Bot, Stéphane Bidoul, Martin Trigaux, Xavier Morel, Damien Bouvy, Yannick Tivisse, Odoo Online, Arnold Moyaux, Victor Feyens, Andrea Grazioso (agr-odoo), Adrien Dieudonné, Sébastien Theys, Prakash Prajapati, Tiffany Chang (tic), Rémy Voet (ryv), Gorash, Pierre Paridans, Arnaud Joset, William Braeckman, william-andre, Florian Damhaut, Audric Onockx (auon), Renaud Thiry, Vincent Larcin, Habib (ayh), damr, Thomas Lefebvre (thle), alsh-odoo, Louis Wicket (wil), Maximilien (malb), Victor Piryns (pivi), Gauthier Wala (gawa), Pieter Claeys (clpi), Claire Bretton (clbr), Levi Siuzdak (sile), Antoine Boonen, Dylan Kiss (dyki), Leloup Loïc (lole), Hugo Carlier (Huca), Xavier Bol (xbo), Sarah Bellefroid, Mattis Megevand (mmeg), omra-odoo, kdeb@odoo.com, Paul Stroobant, Youssef Bashandy, bary, Louis (loti), Maxime de Neuville, suth-odoo, jugj-odoo, Abderraouf Ghrissi (abgh), Bastien Fafchamps (bafa), kcv-odoo, Tanguy Quéguineur, Ritika Rathore, dise, Chrysanthe (chgo), Louis (wil), mash, Pierre Pulinckx (PIPU), Abdessamad Zouiten (abzo), st-yes, Kawtar Drissi El Bouzaidi (kdeb), David Van Droogenbroeck (DROD), Chris Duvivier, Pierre Pulinckx (pipu), Bastien PIERRE, K Theja (kthe), kpt-odoo, Bastien (bvdn), bona-odoo, Krzysztof Magusiak (krma), Mahdi Alijani (malj), rupa-odoo, Harrison Hutton, Maeva De Keyser (MDEK), nsirjacobs, mjvi-odoo, desh, Gurpreet Singh, bhra-odoo, defl, dise-odoo, Killian Frappart, shap@odoo.com, Amr Elkhatieb, saurabh, Farah (ahmfa), nsir-odoo, ksin-odoo
WEBSITE
WEBSITE
LAST TRACKING UPDATE
LAST TRACKING UPDATE2026-07-06 01:25:55
ODOO DEPENDENCIES
ODOO DEPENDENCIES odoo/odoo:
    - sale_project
    - sale_management
    - sale
    - sales_team
    - base
    - mail
    - base_setup
    - web
    - bus
    - web_tour
    - html_editor
    - account_payment
    - account
    - onboarding
    - product
    - uom
    - analytic
    - portal
    - web_editor
    - http_routing
    - auth_signup
    - digest
    - resource
    - payment
    - utm
    - sale_service
    - project_account
    - project
    - rating
    - hr_timesheet
    - hr
    - phone_validation
    - resource_mail
    - hr_hourly_cost
PYTHON DEPENDENCIES
PYTHON DEPENDENCIES Not have
SYSTEM DEPENDENCIES
SYSTEM DEPENDENCIES Not have
DESCRIPTION
DESCRIPTION
Allows to sell timesheets in your sales order
=============================================

This module set the right product on all timesheet lines
according to the order/contract you work on. This allows to
have real delivered quantities in sales orders.

Code Analysis

Views touched (46)
XML IDNameModelTypeStatus
account_invoice_view_form_inherit_sale_timesheet account.invoice.form.inherit.timesheet account.move xpath Inherits account.view_move_form
hr_timesheet_line_form_inherit account.analytic.line.form.inherit account.analytic.line xpath Inherits hr_timesheet.hr_timesheet_line_form
hr_timesheet_line_tree_inherit account.analytic.line.list.inherit account.analytic.line xpath Inherits hr_timesheet.hr_timesheet_line_tree
hr_timesheet_report_search_sale_timesheet timesheets.analysis.report.search timesheets.analysis.report search Inherits sale_timesheet.timesheet_view_search
portal_invoice_page_inherit portal_invoice_page_inherit ir.ui.view qweb Inherits account.portal_invoice_page
portal_my_task_inherit portal_my_task_inherit ir.ui.view qweb Inherits project.portal_my_task
portal_my_timesheets_inherit portal_my_timesheets_inherit ir.ui.view qweb Inherits hr_timesheet.portal_my_timesheets
portal_timesheet_table_inherit portal_timesheet_table_inherit ir.ui.view qweb Inherits hr_timesheet.portal_timesheet_table
product_template_view_search_sale_timesheet product.template.search.timesheet product.template filter Inherits product.product_template_search_view
project_create_invoice_view_form project.create.invoice.view.form project.create.invoice form New
project_project_view_form project.project.form.inherit project.project xpath Inherits hr_timesheet.project_invoice_form
project_project_view_kanban_inherit_sale_timesheet project.project.kanban.inherit.sale.timesheet project.project xpath Inherits hr_timesheet.view_project_kanban_inherited
project_project_view_kanban_inherit_sale_timesheet_so_button project.project.kanban.inherit.sale.timesheet.so.button project.project xpath Inherits project.view_project_kanban
project_sharing_inherit_project_task_view_form project.task.form.inherit.timesheet project.task xpath Inherits hr_timesheet.project_sharing_inherit_project_task_view_form
project_task_view_form_inherit_sale_timesheet project.task.form.inherit.timesheet project.task xpath Inherits project.view_task_form2
project_task_view_search_inherit_sale_timesheet project.task.view.search.inherit project.task filter Inherits hr_timesheet.project_task_view_search
project_update_default_description project_update_default_description ir.ui.view qweb Inherits project.project_update_default_description
report_timesheet_account_move report_timesheet_account_move ir.ui.view qweb New
report_timesheet_sale_order report_timesheet_sale_order ir.ui.view qweb New
res_config_settings_view_form res.config.settings.view.form.inherit.sale.timesheet res.config.settings xpath Inherits hr_timesheet.res_config_settings_view_form
sale_advance_payment_inv_timesheet_view_form sale_timesheet.sale.advance.payment.inv.view.form sale.advance.payment.inv xpath Inherits sale.view_sale_advance_payment_inv
sale_order_portal_content_inherit sale_order_portal_content_inherit ir.ui.view qweb Inherits sale.sale_order_portal_template
timesheet_sale_page timesheet_sale_page ir.ui.view qweb New
timesheet_view_search account.analytic.line.search account.analytic.line xpath Inherits hr_timesheet.hr_timesheet_line_search
timesheete_analysis_report_form timesheets.analysis.report.form timesheets.analysis.report xpath Inherits hr_timesheet.timesheets_analysis_report_form
timesheets_analysis_report_graph_inherit timesheets.analysis.report.graph timesheets.analysis.report xpath Inherits hr_timesheet.timesheets_analysis_report_pivot_employee
timesheets_analysis_report_graph_invoice_type timesheets.analysis.report.graph timesheets.analysis.report graph New
timesheets_analysis_report_graph_project_inherit timesheets.analysis.report.graph.project timesheets.analysis.report xpath Inherits hr_timesheet.timesheets_analysis_report_graph_project
timesheets_analysis_report_graph_task_inherit timesheets.analysis.report.graph.task timesheets.analysis.report xpath Inherits hr_timesheet.timesheets_analysis_report_graph_task
timesheets_analysis_report_graph_timesheet_grid timesheets.analysis.report.graph timesheets.analysis.report xpath Inherits hr_timesheet.timesheets_analysis_report_graph_employee
timesheets_analysis_report_list_inherited timesheets.analysis.report.list.inherited timesheets.analysis.report xpath Inherits hr_timesheet.timesheets_analysis_report_list
timesheets_analysis_report_pivot_inherit timesheets.analysis.report.pivot timesheets.analysis.report xpath Inherits hr_timesheet.timesheets_analysis_report_pivot_employee
timesheets_analysis_report_pivot_invoice_type timesheets.analysis.report.pivot timesheets.analysis.report pivot New
timesheets_analysis_report_pivot_project_inherit timesheets.analysis.report.pivot.project timesheets.analysis.report xpath Inherits hr_timesheet.timesheets_analysis_report_pivot_project
timesheets_analysis_report_pivot_task_inherit timesheets.analysis.report.pivot.task timesheets.analysis.report xpath Inherits hr_timesheet.timesheets_analysis_report_pivot_task
view_hr_timesheet_line_graph_employee_per_date account.analytic.line.graph.employee.per.date account.analytic.line graph New
view_hr_timesheet_line_graph_invoice_employee account.analytic.line.graph.invoice.employee account.analytic.line xpath Inherits view_hr_timesheet_line_graph_employee_per_date
view_hr_timesheet_line_pivot_billing_rate account.analytic.line.pivot.billing.rate account.analytic.line pivot New
view_hr_timesheet_line_pivot_inherited account.analytic.line.pivot account.analytic.line xpath Inherits hr_timesheet.view_hr_timesheet_line_pivot
view_order_form_inherit_sale_timesheet sale.order.form.sale.timesheet sale.order data Inherits sale_project.view_order_form_inherit_sale_project
view_product_timesheet_form product.template.timesheet.form product.template field Inherits sale.product_template_form_view
view_task_form2_inherit_sale_timesheet view.task.form2.inherit project.task xpath Inherits sale_project.view_sale_project_inherit_form
view_task_project_user_fsm_graph_base_inherited report.project.task.user.fsm.graph.base.inherited report.project.task.user field Inherits project.view_task_project_user_fsm_graph_base
view_task_project_user_fsm_pivot_base_inherited report.project.task.user.fsm.pivot.base.inherited report.project.task.user field Inherits project.view_task_project_user_fsm_pivot_base
view_task_project_user_pivot_inherited report.project.task.user.pivot.inherited report.project.task.user xpath Inherits project.view_task_project_user_pivot
view_task_tree2_inherited project.task.list.inherited project.task xpath Inherits hr_timesheet.view_task_tree2_inherited
Models touched (18)

New fields (8)
  • allow_billable Boolean
    related='project_id.allow_billable'
  • commercial_partner_id Many2one → res.partner
    compute='_compute_commercial_partner' args: 'res.partner'
  • is_so_line_edited Boolean
    args: 'Is Sales Order Item Manually Edited'
  • order_id Many2one
    index=True readonly=True related='so_line.order_id' store=True
  • sale_order_state Selection
    related='order_id.state'
  • so_line Many2one
    compute='_compute_so_line' domain=_domain_so_line help='Sales order item to which the time spent will be added in order to be invoiced to your customer. Remove the sales order item for the timesheet entry to be non-billable.' readonly=False store=True
  • timesheet_invoice_id Many2one → account.move
    copy=False help='Invoice created from the timesheet' index='btree_not_null' readonly=True string='Invoice' args: 'account.move'
  • timesheet_invoice_type Selection
    compute='_compute_timesheet_invoice_type' compute_sudo=True readonly=True store=True string='Billable Type' args: TIMESHEET_INVOICE_TYPES
Public methods (2)
  • action_invoice_from_timesheet(self)
  • action_sale_order_from_timesheet(self)

New fields (4)
  • timesheet_count Integer
    compute='_compute_timesheet_count' compute_sudo=True export_string_translation=False args: 'Number of timesheets'
  • timesheet_encode_uom_id Many2one → uom.uom
    export_string_translation=False related='company_id.timesheet_encode_uom_id' args: 'uom.uom'
  • timesheet_ids One2many → account.analytic.line
    copy=False export_string_translation=False readonly=True string='Timesheets' args: 'account.analytic.line', 'timesheet_invoice_id'
  • timesheet_total_duration Integer
    compute='_compute_timesheet_total_duration' compute_sudo=True help='Total recorded duration, expressed in the encoding UoM, and rounded to the unit' args: 'Timesheet Total Duration'
Public methods (2)
  • action_post(self)
  • action_view_timesheet(self)

New fields (0)

No new fields.

Public methods (1)
  • unlink(self)

New fields (0)

No new fields.

Public methods (1)
  • reverse_moves(self, is_modify=False)

New fields (0)

No new fields.

Public methods (1)
  • default_get(self, fields)
    @api.model

New fields (0)

No new fields.

Public methods (1)
  • write(self, vals)

New fields (5)
  • project_id Many2one
    domain="['|', ('company_id', '=', False), '&', ('company_id', '=?', company_id), ('company_id', '=', current_company_id), ('allow_billable', '=', True), ('pricing_type', '=', 'task_rate'), ('allow_timesheets', 'in', [service_policy == 'delivered_timesheet', True])]"
  • project_template_id Many2one
    domain="['|', ('company_id', '=', False), '&', ('company_id', '=?', company_id), ('company_id', '=', current_company_id), ('allow_billable', '=', True), ('allow_timesheets', 'in', [service_policy == 'delivered_timesheet', True])]"
  • service_type Selection
    ondelete={'timesheet': 'set manual'} selection_add=[('timesheet', 'Timesheets on project (one fare per SO/Project)')]
  • service_upsell_threshold Float
    default=1 help='Percentage of time delivered compared to the prepaid amount that must be reached for the upselling opportunity activity to be triggered.' args: 'Threshold'
  • service_upsell_threshold_ratio Char
    compute='_compute_service_upsell_threshold_ratio' export_string_translation=False
Public methods (1)
  • write(self, vals)

New fields (5)
  • _candidate_orders Many2many → sale.order
    compute='_compute_candidate_orders' export_string_translation=False args: 'sale.order'
  • amount_to_invoice Monetary
    compute='_compute_amount_to_invoice' currency_field='currency_id' help='Total amount to invoice on the sales order, including all items (services, storables, expenses, ...)' args: 'Amount to invoice'
  • currency_id Many2one
    export_string_translation=False readonly=True related='sale_order_id.currency_id'
  • project_id Many2one → project.project
    help='Project to make billable' required=True args: 'project.project', 'Project'
  • sale_order_id Many2one → sale.order
    domain="[('id', 'in', _candidate_orders)]" required=True string='Choose the Sales Order to invoice' args: 'sale.order'
Public methods (2)
  • action_create_invoice(self)
  • default_get(self, fields)
    @api.model

New fields (7)
  • allocated_hours Float
  • billing_type Selection
    compute='_compute_billing_type' default='not_billable' readonly=False required=True selection=[('not_billable', 'not billable'), ('manually', 'billed manually')] store=True
  • partner_id Many2one
    compute='_compute_partner_id' readonly=False store=True
  • pricing_type Selection
    compute='_compute_pricing_type' default='task_rate' help='The task rate is perfect if you would like to bill different services to different customers at different rates. The fixed rate is perfect if you bill a service at a fixed rate per hour or day worked regardless of the employee who performed it. The employee rate is preferable if your employees deliver the same service at a different rate. For instance, junior and senior consultants would deliver the same service (= consultancy), but at a different rate because of their level of seniority.' search='_search_pricing_type' string='Pricing' args: [('task_rate', 'Task rate'), ('fixed_rate', 'Project rate'), ('employee_rate', 'Employee rate')]
  • sale_line_employee_ids One2many → project.sale.line.employee.map
    copy=False export_string_translation=False help="Sales order item that will be selected by default on the timesheets of the corresponding employee. It bypasses the sales order item defined on the project and the task, and can be modified on each timesheet entry if necessary. In other words, it defines the rate at which an employee's time is billed based on their expertise, skills or experience, for instance.\nIf you would like to bill the same service at a different rate, you need to create two separate sales order items as each sales order item can only have a single unit price at a time.\nYou can also define the hourly company cost of your employees for their timesheets on this project specifically. It will bypass the timesheet cost set on the employee." args: 'project.sale.line.employee.map', 'project_id', 'Sale line/Employee map'
  • timesheet_product_id Many2one → product.product
    check_company=True compute='_compute_timesheet_product_id' default=_default_timesheet_product_id domain="[\n ('type', '=', 'service'),\n ('invoice_policy', '=', 'delivery'),\n ('service_type', '=', 'timesheet'),\n ]" help='Service that will be used by default when invoicing the time spent on a task. It can be modified on each task individually by selecting a specific sales order item.' readonly=False store=True string='Timesheet Product' args: 'product.product'
  • warning_employee_rate Boolean
    compute='_compute_warning_employee_rate' compute_sudo=True export_string_translation=False
Public methods (6)
  • action_billable_time_button(self)
  • action_profitability_items(self, section_name, domain=None, res_id=False)
  • action_view_timesheet(self)
  • default_get(self, fields)
    @api.model
    Pre-fill timesheet product as "Time" data product when creating new project allowing billable tasks by default.
  • get_panel_data(self)
  • write(self, values)

New fields (13)
  • company_id Many2one → res.company
    export_string_translation=False related='project_id.company_id' string='Company' args: 'res.company'
  • cost Monetary
    compute='_compute_cost' currency_field='cost_currency_id' help="This cost overrides the employee's default employee hourly wage in employee's HR Settings" readonly=False store=True
  • cost_currency_id Many2one → res.currency
    export_string_translation=False readonly=True related='employee_id.currency_id' string='Cost Currency' args: 'res.currency'
  • currency_id Many2one → res.currency
    compute='_compute_currency_id' readonly=False store=True string='Currency' args: 'res.currency'
  • display_cost Monetary
    compute='_compute_display_cost' currency_field='cost_currency_id' groups='project.group_project_manager,hr.group_hr_user' inverse='_inverse_display_cost' string='Hourly Cost'
  • employee_id Many2one → hr.employee
    domain="[('id', 'not in', existing_employee_ids)]" required=True args: 'hr.employee', 'Employee'
  • existing_employee_ids Many2many → hr.employee
    compute='_compute_existing_employee_ids' export_string_translation=False args: 'hr.employee'
  • is_cost_changed Boolean
    compute='_compute_is_cost_changed' export_string_translation=False store=True args: 'Is Cost Manually Changed'
  • partner_id Many2one
    export_string_translation=False related='project_id.partner_id'
  • price_unit Float
    compute='_compute_price_unit' readonly=True store=True args: 'Unit Price'
  • project_id Many2one → project.project
    required=True args: 'project.project', 'Project'
  • sale_line_id Many2one → sale.order.line
    compute='_compute_sale_line_id' domain=<expr> readonly=False store=True args: 'sale.order.line', 'Sales Order Item'
  • sale_order_id Many2one
    export_string_translation=False related='project_id.sale_order_id'
Public methods (2)
  • create(self, vals_list)
    @api.model_create_multi
  • write(self, values)

New fields (7)
  • has_multi_sol Boolean
    compute='_compute_has_multi_sol' compute_sudo=True
  • is_project_map_empty Boolean
    compute='_compute_is_project_map_empty' args: 'Is Project map empty'
  • pricing_type Selection
    related='project_id.pricing_type'
  • remaining_hours_available Boolean
    related='sale_line_id.remaining_hours_available'
  • remaining_hours_so Float
    compute='_compute_remaining_hours_so' compute_sudo=True search='_search_remaining_hours_so' args: 'Time Remaining on SO'
  • sale_order_id Many2one
    domain="['|', '|', ('partner_id', '=', partner_id), ('partner_id.commercial_partner_id.id', 'parent_of', partner_id), ('partner_id', 'parent_of', partner_id)]"
  • timesheet_product_id Many2one
    related='project_id.timesheet_product_id'
Public methods (1)
  • SELF_READABLE_FIELDS(self)
    @property

New fields (0)

No new fields.

Public methods (0)

No public methods.

New fields (1)
  • remaining_hours_so Float
    groups='hr_timesheet.group_hr_timesheet_user' readonly=True args: 'Time Remaining on SO'
Public methods (0)

No public methods.

New fields (1)
  • invoice_policy Boolean
    help='Timesheets taken when invoicing time spent' string='Invoice Policy'
Public methods (0)

No public methods.

New fields (3)
  • date_end_invoice_timesheet Date
    help='Only timesheets not yet invoiced (and validated, if applicable) from this period will be invoiced. If the period is not indicated, all timesheets not yet invoiced (and validated, if applicable) will be invoiced without distinction.' string='End Date'
  • date_start_invoice_timesheet Date
    help='Only timesheets not yet invoiced (and validated, if applicable) from this period will be invoiced. If the period is not indicated, all timesheets not yet invoiced (and validated, if applicable) will be invoiced without distinction.' string='Start Date'
  • invoicing_timesheet_enabled Boolean
    compute='_compute_invoicing_timesheet_enabled' export_string_translation=False store=True
Public methods (0)

No public methods.

New fields (4)
  • show_hours_recorded_button Boolean
    compute='_compute_show_hours_recorded_button' export_string_translation=False groups='hr_timesheet.group_hr_timesheet_user'
  • timesheet_count Float
    compute='_compute_timesheet_count' export_string_translation=False groups='hr_timesheet.group_hr_timesheet_user' string='Timesheet activities'
  • timesheet_encode_uom_id Many2one → uom.uom
    export_string_translation=False related='company_id.timesheet_encode_uom_id' args: 'uom.uom'
  • timesheet_total_duration Integer
    compute='_compute_timesheet_total_duration' compute_sudo=True export_string_translation=False groups='hr_timesheet.group_hr_timesheet_user' help='Total recorded duration, expressed in the encoding UoM, and rounded to the unit' args: 'Timesheet Total Duration'
Public methods (1)
  • action_view_timesheet(self)

New fields (6)
  • analytic_line_ids One2many
    domain=[('project_id', '=', False)]
  • has_displayed_warning_upsell Boolean
    copy=False export_string_translation=False args: 'Has Displayed Warning Upsell'
  • qty_delivered_method Selection
    selection_add=[('timesheet', 'Timesheets')]
  • remaining_hours Float
    compute='_compute_remaining_hours' compute_sudo=True store=True args: 'Time Remaining on SO'
  • remaining_hours_available Boolean
    compute='_compute_remaining_hours_available' compute_sudo=True
  • timesheet_ids One2many → account.analytic.line
    domain=[('project_id', '!=', False)] export_string_translation=False string='Timesheets' args: 'account.analytic.line', 'so_line'
Public methods (0)

No public methods.

New fields (8)
  • billable_time Float
    help='Number of hours/days linked to a SOL.' readonly=True args: 'Billable Time'
  • margin Monetary
    currency_field='currency_id' help='Timesheets revenues minus the costs' readonly=True args: 'Margin'
  • non_billable_time Float
    help='Number of hours/days not linked to a SOL.' readonly=True args: 'Non-billable Time'
  • order_id Many2one → sale.order
    readonly=True string='Sales Order' args: 'sale.order'
  • so_line Many2one → sale.order.line
    readonly=True string='Sales Order Item' args: 'sale.order.line'
  • timesheet_invoice_id Many2one → account.move
    help='Invoice created from the timesheet' readonly=True string='Invoice' args: 'account.move'
  • timesheet_invoice_type Selection
    readonly=True string='Billable Type' args: TIMESHEET_INVOICE_TYPES
  • timesheet_revenues Monetary
    currency_field='currency_id' help='Number of hours spent multiplied by the unit price per hour/day.' readonly=True args: 'Timesheet Revenues'
Public methods (0)

No public methods.

REPOSITORY
REPOSITORYodoo/odoo
GIT
GIThttps://github.com/odoo/odoo.git
GIT FOLDER
GIT FOLDERhttps://github.com/odoo/odoo/tree/17.0/sale_timesheet
VERSION
VERSION 0.1.0
CATEGORY
CATEGORYHidden
LICENSE
LICENSELGPL-3
APPLICATION
APPLICATIONNo
AUTO-INSTALLABLE
AUTO-INSTALLABLEYes
AUTHORS
AUTHORS
MAINTAINERS
MAINTAINERS
COMMITTERS
COMMITTERSRaphael Collet, Odoo Translation Bot, Stéphane Bidoul, Martin Trigaux, Denis Ledoux, Xavier Morel, qdp-odoo, Géry Debongnie, Aaron Bohy, GitHub, Damien Bouvy, Barad Mahendra, Yannick Tivisse, Odoo Online, Jorge Pinna Puissant, Alexandre Kühn, Victor Feyens, Andrea Grazioso (agr-odoo), Joseph Caburnay, Julien Mougenot, Alvaro Fuentes, Prakash Prajapati, Tiffany Chang (tic), Rémy Voet (ryv), Gorash, Pierre Paridans, William Braeckman, Stefan-Calin Crainiciuc (stcc), Xavier BOL (xbo), roen-odoo, william-andre, Florian Damhaut, Audric Onockx (auon), Louis Baudoux, John Laterre (jol), gawa-odoo, Renaud Thiry, Vincent Larcin, bve-odoo, Habib (ayh), damr, Thomas Lefebvre (thle), alsh-odoo, Louis Wicket (wil), Victor Piryns (pivi), Yash Vaishnav, Andrew Gavgavian, pash-odoo, Levi Siuzdak (sile), Maruan Aguerdouh (magm), Dylan Kiss (dyki), Hansun (hale), Sanket Brahmbhatt, Pratik Awasthi, Michael (mcm), Leloup Loïc (lole), Raouf, Kartik Chavda, Hugo Carlier (Huca), Xavier Bol (xbo), Brieuc-brd, Sarah Bellefroid, Mahdi Cheikh Rouhou (macr), Mattis Megevand (mmeg), Giang Pham, kdeb@odoo.com, Paul Stroobant, ravb-odoo, bary, Abderraouf Ghrissi (abgh), Julien Carion (juca), kcv-odoo, Tanguy Quéguineur, Ritika Rathore, Chrysanthe (chgo), Louis (wil), mash, Abdessamad Zouiten (abzo), st-yes, David Van Droogenbroeck (DROD), Chris Duvivier, Olivia Fantinel, Pierre Pulinckx (pipu), Bastien PIERRE, hiroh-odoo, Bastien (bvdn), Maeva De Keyser (MDEK), Sohail JAIDI (soja), mjvi-odoo, bhra-odoo, shap@odoo.com
WEBSITE
WEBSITE
LAST TRACKING UPDATE
LAST TRACKING UPDATE2026-07-06 01:02:06
ODOO DEPENDENCIES
ODOO DEPENDENCIES odoo/odoo:
    - sale_project
    - sale_management
    - sale
    - sales_team
    - base
    - mail
    - base_setup
    - web
    - bus
    - web_tour
    - account_payment
    - account
    - onboarding
    - product
    - uom
    - analytic
    - portal
    - web_editor
    - http_routing
    - auth_signup
    - digest
    - resource
    - payment
    - utm
    - sale_service
    - project_account
    - project
    - rating
    - hr_timesheet
    - hr
    - phone_validation
    - hr_hourly_cost
PYTHON DEPENDENCIES
PYTHON DEPENDENCIES Not have
SYSTEM DEPENDENCIES
SYSTEM DEPENDENCIES Not have
DESCRIPTION
DESCRIPTION
Allows to sell timesheets in your sales order
=============================================

This module set the right product on all timesheet lines
according to the order/contract you work on. This allows to
have real delivered quantities in sales orders.

Code Analysis

Views touched (45)
XML IDNameModelTypeStatus
account_invoice_view_form_inherit_sale_timesheet account.invoice.form.inherit.timesheet account.move xpath Inherits account.view_move_form
hr_timesheet_line_form_inherit account.analytic.line.form.inherit account.analytic.line xpath Inherits hr_timesheet.hr_timesheet_line_form
hr_timesheet_line_tree_inherit account.analytic.line.tree.inherit account.analytic.line xpath Inherits hr_timesheet.hr_timesheet_line_tree
hr_timesheet_report_search_sale_timesheet timesheets.analysis.report.search timesheets.analysis.report search Inherits sale_timesheet.timesheet_view_search
portal_invoice_page_inherit portal_invoice_page_inherit ir.ui.view qweb Inherits account.portal_invoice_page
portal_my_task_inherit portal_my_task_inherit ir.ui.view qweb Inherits project.portal_my_task
portal_my_timesheets_inherit portal_my_timesheets_inherit ir.ui.view qweb Inherits hr_timesheet.portal_my_timesheets
portal_timesheet_table_inherit portal_timesheet_table_inherit ir.ui.view qweb Inherits hr_timesheet.portal_timesheet_table
portal_timesheet_table_inherit portal_timesheet_table_inherit ir.ui.view qweb Inherits hr_timesheet.portal_timesheet_table
product_template_view_search_sale_timesheet product.template.search.timesheet product.template xpath Inherits product.product_template_search_view
project_create_invoice_view_form project.create.invoice.view.form project.create.invoice form New
project_create_sale_order_view_form project.create.sale.order.wizard.form project.create.sale.order form New
project_project_view_form project.project.form.inherit project.project xpath Inherits hr_timesheet.project_invoice_form
project_project_view_kanban_inherit_sale_timesheet project.project.kanban.inherit.sale.timesheet project.project xpath Inherits hr_timesheet.view_project_kanban_inherited
project_project_view_kanban_inherit_sale_timesheet_so_button project.project.kanban.inherit.sale.timesheet.so.button project.project xpath Inherits project.view_project_kanban
project_sharing_inherit_project_task_view_form project.task.form.inherit.timesheet project.task xpath Inherits hr_timesheet.project_sharing_inherit_project_task_view_form
project_sharing_inherit_project_task_view_tree_sale_timesheet project.task.tree.inherit.sale.timesheet project.task field Inherits project.project_sharing_project_task_view_tree
project_task_view_form_inherit_sale_timesheet project.task.form.inherit.timesheet project.task xpath Inherits project.view_task_form2
project_task_view_search_inherit_sale_timesheet project.task.view.search.inherit project.task filter Inherits hr_timesheet.project_task_view_search
project_update_default_description project_update_default_description ir.ui.view qweb Inherits project.project_update_default_description
report_timesheet_account_move report_timesheet_account_move ir.ui.view qweb New
report_timesheet_sale_order report_timesheet_sale_order ir.ui.view qweb New
res_config_settings_view_form res.config.settings.view.form.inherit.sale.timesheet res.config.settings xpath Inherits hr_timesheet.res_config_settings_view_form
sale_advance_payment_inv_timesheet_view_form sale_timesheet.sale.advance.payment.inv.view.form sale.advance.payment.inv xpath Inherits sale.view_sale_advance_payment_inv
sale_order_portal_content_inherit sale_order_portal_content_inherit ir.ui.view qweb Inherits sale.sale_order_portal_template
timesheet_sale_page timesheet_sale_page ir.ui.view qweb New
timesheet_view_search account.analytic.line.search account.analytic.line xpath Inherits hr_timesheet.hr_timesheet_line_search
timesheets_analysis_report_graph_inherit timesheets.analysis.report.graph timesheets.analysis.report xpath Inherits hr_timesheet.timesheets_analysis_report_pivot_employee
timesheets_analysis_report_graph_invoice_type timesheets.analysis.report.graph timesheets.analysis.report graph New
timesheets_analysis_report_graph_project_inherit timesheets.analysis.report.graph.project timesheets.analysis.report xpath Inherits hr_timesheet.timesheets_analysis_report_graph_project
timesheets_analysis_report_graph_task_inherit timesheets.analysis.report.graph.task timesheets.analysis.report xpath Inherits hr_timesheet.timesheets_analysis_report_graph_task
timesheets_analysis_report_graph_timesheet_grid timesheets.analysis.report.graph timesheets.analysis.report xpath Inherits hr_timesheet.timesheets_analysis_report_graph_employee
timesheets_analysis_report_pivot_inherit timesheets.analysis.report.pivot timesheets.analysis.report xpath Inherits hr_timesheet.timesheets_analysis_report_pivot_employee
timesheets_analysis_report_pivot_invoice_type timesheets.analysis.report.pivot timesheets.analysis.report pivot New
timesheets_analysis_report_pivot_project_inherit timesheets.analysis.report.pivot.project timesheets.analysis.report xpath Inherits hr_timesheet.timesheets_analysis_report_pivot_project
timesheets_analysis_report_pivot_task_inherit timesheets.analysis.report.pivot.task timesheets.analysis.report xpath Inherits hr_timesheet.timesheets_analysis_report_pivot_task
view_hr_timesheet_line_graph_employee_per_date account.analytic.line.graph.employee.per.date account.analytic.line graph New
view_hr_timesheet_line_graph_invoice_employee account.analytic.line.graph.invoice.employee account.analytic.line xpath Inherits view_hr_timesheet_line_graph_employee_per_date
view_hr_timesheet_line_pivot_billing_rate account.analytic.line.pivot.billing.rate account.analytic.line pivot New
view_hr_timesheet_line_pivot_inherited account.analytic.line.pivot account.analytic.line xpath Inherits hr_timesheet.view_hr_timesheet_line_pivot
view_order_form_inherit_sale_timesheet sale.order.form.sale.timesheet sale.order data Inherits sale_project.view_order_form_inherit_sale_project
view_product_timesheet_form product.template.timesheet.form product.template field Inherits sale.product_template_form_view
view_task_form2_inherit_sale_timesheet view.task.form2.inherit project.task xpath Inherits sale_project.view_sale_project_inherit_form
view_task_project_user_pivot_inherited report.project.task.user.pivot.inherited report.project.task.user xpath Inherits project.view_task_project_user_pivot
view_task_tree2_inherited project.task.tree.inherited project.task xpath Inherits hr_timesheet.view_task_tree2_inherited
Models touched (21)

New fields (7)
  • allow_billable Boolean
    related='project_id.allow_billable'
  • commercial_partner_id Many2one → res.partner
    compute='_compute_commercial_partner' args: 'res.partner'
  • is_so_line_edited Boolean
    args: 'Is Sales Order Item Manually Edited'
  • order_id Many2one
    index=True readonly=True related='so_line.order_id' store=True
  • so_line Many2one
    compute='_compute_so_line' domain=_domain_so_line help='Sales order item to which the time spent will be added in order to be invoiced to your customer. Remove the sales order item for the timesheet entry to be non-billable.' readonly=False store=True
  • timesheet_invoice_id Many2one → account.move
    copy=False help='Invoice created from the timesheet' index='btree_not_null' readonly=True string='Invoice' args: 'account.move'
  • timesheet_invoice_type Selection
    compute='_compute_timesheet_invoice_type' compute_sudo=True readonly=True store=True string='Billable Type' args: TIMESHEET_INVOICE_TYPES
Public methods (2)
  • action_invoice_from_timesheet(self)
  • action_sale_order_from_timesheet(self)

New fields (4)
  • timesheet_count Integer
    compute='_compute_timesheet_count' compute_sudo=True args: 'Number of timesheets'
  • timesheet_encode_uom_id Many2one → uom.uom
    related='company_id.timesheet_encode_uom_id' args: 'uom.uom'
  • timesheet_ids One2many → account.analytic.line
    copy=False readonly=True string='Timesheets' args: 'account.analytic.line', 'timesheet_invoice_id'
  • timesheet_total_duration Integer
    compute='_compute_timesheet_total_duration' compute_sudo=True help='Total recorded duration, expressed in the encoding UoM, and rounded to the unit' args: 'Timesheet Total Duration'
Public methods (2)
  • action_post(self)
  • action_view_timesheet(self)

New fields (0)

No new fields.

Public methods (1)
  • unlink(self)

New fields (0)

No new fields.

Public methods (1)
  • reverse_moves(self, is_modify=False)

New fields (0)

No new fields.

Public methods (1)
  • default_get(self, fields)
    @api.model

New fields (0)

No new fields.

Public methods (1)
  • write(self, vals)

New fields (5)
  • project_id Many2one
    domain="['|', ('company_id', '=', False), '&', ('company_id', '=?', company_id), ('company_id', '=', current_company_id), ('allow_billable', '=', True), ('pricing_type', '=', 'task_rate'), ('allow_timesheets', 'in', [service_policy == 'delivered_timesheet', True])]"
  • project_template_id Many2one
    domain="['|', ('company_id', '=', False), '&', ('company_id', '=?', company_id), ('company_id', '=', current_company_id), ('allow_billable', '=', True), ('allow_timesheets', 'in', [service_policy == 'delivered_timesheet', True])]"
  • service_type Selection
    ondelete={'timesheet': 'set manual'} selection_add=[('timesheet', 'Timesheets on project (one fare per SO/Project)')]
  • service_upsell_threshold Float
    default=1 help='Percentage of time delivered compared to the prepaid amount that must be reached for the upselling opportunity activity to be triggered.' args: 'Threshold'
  • service_upsell_threshold_ratio Char
    compute='_compute_service_upsell_threshold_ratio'
Public methods (1)
  • write(self, vals)

New fields (5)
  • _candidate_orders Many2many → sale.order
    compute='_compute_candidate_orders' args: 'sale.order'
  • amount_to_invoice Monetary
    compute='_compute_amount_to_invoice' currency_field='currency_id' help='Total amount to invoice on the sales order, including all items (services, storables, expenses, ...)' args: 'Amount to invoice'
  • currency_id Many2one
    readonly=True related='sale_order_id.currency_id'
  • project_id Many2one → project.project
    help='Project to make billable' required=True args: 'project.project', 'Project'
  • sale_order_id Many2one → sale.order
    domain="[('id', 'in', _candidate_orders)]" required=True string='Choose the Sales Order to invoice' args: 'sale.order'
Public methods (2)
  • action_create_invoice(self)
  • default_get(self, fields)
    @api.model

New fields (7)
  • commercial_partner_id Many2one
    related='partner_id.commercial_partner_id'
  • company_id Many2one
    related='project_id.company_id'
  • info_invoice Char
    compute='_compute_info_invoice'
  • line_ids One2many → project.create.sale.order.line
    string='Lines' args: 'project.create.sale.order.line', 'wizard_id'
  • partner_id Many2one → res.partner
    help='Customer of the sales order' required=True string='Customer' args: 'res.partner'
  • project_id Many2one → project.project
    domain=[('sale_line_id', '=', False)] help='Project for which we are creating a sales order' required=True args: 'project.project', 'Project'
  • sale_order_id Many2one → sale.order
    domain="['|', '|', ('partner_id', '=', partner_id), ('partner_id', 'child_of', commercial_partner_id), ('partner_id', 'parent_of', partner_id)]" string='Sales Order' args: 'sale.order'
Public methods (2)
  • action_create_sale_order(self)
  • default_get(self, fields)
    @api.model

New fields (5)
  • currency_id Many2one → res.currency
    string='Currency' args: 'res.currency'
  • employee_id Many2one → hr.employee
    help='Employee that has timesheets on the project.' string='Employee' args: 'hr.employee'
  • price_unit Float
    help='Unit price of the sales order item.' args: 'Unit Price'
  • product_id Many2one → product.product
    domain=[('detailed_type', '=', 'service'), ('invoice_policy', '=', 'delivery'), ('service_type', '=', 'timesheet')] help='Product of the sales order item. Must be a service invoiced based on timesheets on tasks.' string='Service' args: 'product.product'
  • wizard_id Many2one → project.create.sale.order
    required=True args: 'project.create.sale.order'
Public methods (0)

No public methods.

New fields (8)
  • allocated_hours Float
  • billable_percentage Integer
    compute='_compute_billable_percentage' groups='hr_timesheet.group_hr_timesheet_approver' help='% of timesheets that are billable compared to the total number of timesheets linked to the AA of the project, rounded to the unit.'
  • billing_type Selection
    compute='_compute_billing_type' default='not_billable' readonly=False required=True selection=[('not_billable', 'not billable'), ('manually', 'billed manually')] store=True
  • partner_id Many2one
    compute='_compute_partner_id' readonly=False store=True
  • pricing_type Selection
    compute='_compute_pricing_type' default='task_rate' help='The task rate is perfect if you would like to bill different services to different customers at different rates. The fixed rate is perfect if you bill a service at a fixed rate per hour or day worked regardless of the employee who performed it. The employee rate is preferable if your employees deliver the same service at a different rate. For instance, junior and senior consultants would deliver the same service (= consultancy), but at a different rate because of their level of seniority.' search='_search_pricing_type' string='Pricing' args: [('task_rate', 'Task rate'), ('fixed_rate', 'Project rate'), ('employee_rate', 'Employee rate')]
  • sale_line_employee_ids One2many → project.sale.line.employee.map
    copy=False help="Sales order item that will be selected by default on the timesheets of the corresponding employee. It bypasses the sales order item defined on the project and the task, and can be modified on each timesheet entry if necessary. In other words, it defines the rate at which an employee's time is billed based on their expertise, skills or experience, for instance.\nIf you would like to bill the same service at a different rate, you need to create two separate sales order items as each sales order item can only have a single unit price at a time.\nYou can also define the hourly company cost of your employees for their timesheets on this project specifically. It will bypass the timesheet cost set on the employee." args: 'project.sale.line.employee.map', 'project_id', 'Sale line/Employee map'
  • timesheet_product_id Many2one → product.product
    check_company=True compute='_compute_timesheet_product_id' default=_default_timesheet_product_id domain="[\n ('detailed_type', '=', 'service'),\n ('invoice_policy', '=', 'delivery'),\n ('service_type', '=', 'timesheet'),\n ]" help='Service that will be used by default when invoicing the time spent on a task. It can be modified on each task individually by selecting a specific sales order item.' readonly=False store=True string='Timesheet Product' args: 'product.product'
  • warning_employee_rate Boolean
    compute='_compute_warning_employee_rate' compute_sudo=True
Public methods (7)
  • action_billable_time_button(self)
  • action_make_billable(self)
  • action_profitability_items(self, section_name, domain=None, res_id=False)
  • action_view_timesheet(self)
  • default_get(self, fields)
    @api.model
    Pre-fill timesheet product as "Time" data product when creating new project allowing billable tasks by default.
  • get_panel_data(self)
  • write(self, values)

New fields (13)
  • company_id Many2one → res.company
    related='project_id.company_id' string='Company' args: 'res.company'
  • cost Monetary
    compute='_compute_cost' currency_field='cost_currency_id' help="This cost overrides the employee's default employee hourly wage in employee's HR Settings" readonly=False store=True
  • cost_currency_id Many2one → res.currency
    readonly=True related='employee_id.currency_id' string='Cost Currency' args: 'res.currency'
  • currency_id Many2one → res.currency
    compute='_compute_currency_id' readonly=False store=True string='Currency' args: 'res.currency'
  • display_cost Monetary
    compute='_compute_display_cost' currency_field='cost_currency_id' inverse='_inverse_display_cost' string='Hourly Cost'
  • employee_id Many2one → hr.employee
    domain="[('id', 'not in', existing_employee_ids)]" required=True args: 'hr.employee', 'Employee'
  • existing_employee_ids Many2many → hr.employee
    compute='_compute_existing_employee_ids' args: 'hr.employee'
  • is_cost_changed Boolean
    compute='_compute_is_cost_changed' store=True args: 'Is Cost Manually Changed'
  • partner_id Many2one
    related='project_id.partner_id'
  • price_unit Float
    compute='_compute_price_unit' readonly=True store=True args: 'Unit Price'
  • project_id Many2one → project.project
    required=True args: 'project.project', 'Project'
  • sale_line_id Many2one → sale.order.line
    compute='_compute_sale_line_id' domain=<expr> readonly=False store=True args: 'sale.order.line', 'Sales Order Item'
  • sale_order_id Many2one
    related='project_id.sale_order_id'
Public methods (2)
  • create(self, vals_list)
    @api.model_create_multi
  • write(self, values)

New fields (8)
  • has_multi_sol Boolean
    compute='_compute_has_multi_sol' compute_sudo=True
  • is_project_map_empty Boolean
    compute='_compute_is_project_map_empty' args: 'Is Project map empty'
  • pricing_type Selection
    related='project_id.pricing_type'
  • remaining_hours_available Boolean
    related='sale_line_id.remaining_hours_available'
  • remaining_hours_so Float
    compute='_compute_remaining_hours_so' compute_sudo=True search='_search_remaining_hours_so' args: 'Remaining Hours on SO'
  • sale_order_id Many2one
    domain="['|', '|', ('partner_id', '=', partner_id), ('partner_id.commercial_partner_id.id', 'parent_of', partner_id), ('partner_id', 'parent_of', partner_id)]"
  • so_analytic_account_id Many2one
    related='sale_order_id.analytic_account_id' string='Sale Order Analytic Account'
  • timesheet_product_id Many2one
    related='project_id.timesheet_product_id'
Public methods (1)
  • SELF_READABLE_FIELDS(self)
    @property

New fields (0)

No new fields.

Public methods (0)

No public methods.

New fields (0)

No new fields.

Public methods (0)

No public methods.

New fields (1)
  • remaining_hours_so Float
    readonly=True args: 'Remaining Hours on SO'
Public methods (0)

No public methods.

New fields (1)
  • invoice_policy Boolean
    help='Timesheets taken when invoicing time spent' string='Invoice Policy'
Public methods (0)

No public methods.

New fields (3)
  • date_end_invoice_timesheet Date
    help='Only timesheets not yet invoiced (and validated, if applicable) from this period will be invoiced. If the period is not indicated, all timesheets not yet invoiced (and validated, if applicable) will be invoiced without distinction.' string='End Date'
  • date_start_invoice_timesheet Date
    help='Only timesheets not yet invoiced (and validated, if applicable) from this period will be invoiced. If the period is not indicated, all timesheets not yet invoiced (and validated, if applicable) will be invoiced without distinction.' string='Start Date'
  • invoicing_timesheet_enabled Boolean
    compute='_compute_invoicing_timesheet_enabled' store=True
Public methods (0)

No public methods.

New fields (5)
  • project_id Many2one
    check_company=True domain="[('pricing_type', '!=', 'employee_rate'), ('analytic_account_id', '!=', False)]"
  • show_hours_recorded_button Boolean
    compute='_compute_show_hours_recorded_button' groups='hr_timesheet.group_hr_timesheet_user'
  • timesheet_count Float
    compute='_compute_timesheet_count' groups='hr_timesheet.group_hr_timesheet_user' string='Timesheet activities'
  • timesheet_encode_uom_id Many2one → uom.uom
    related='company_id.timesheet_encode_uom_id' args: 'uom.uom'
  • timesheet_total_duration Integer
    compute='_compute_timesheet_total_duration' compute_sudo=True groups='hr_timesheet.group_hr_timesheet_user' help='Total recorded duration, expressed in the encoding UoM, and rounded to the unit' args: 'Timesheet Total Duration'
Public methods (1)
  • action_view_timesheet(self)

New fields (6)
  • analytic_line_ids One2many
    domain=[('project_id', '=', False)]
  • has_displayed_warning_upsell Boolean
    copy=False args: 'Has Displayed Warning Upsell'
  • qty_delivered_method Selection
    selection_add=[('timesheet', 'Timesheets')]
  • remaining_hours Float
    compute='_compute_remaining_hours' compute_sudo=True store=True args: 'Remaining Hours on SO'
  • remaining_hours_available Boolean
    compute='_compute_remaining_hours_available' compute_sudo=True
  • timesheet_ids One2many → account.analytic.line
    domain=[('project_id', '!=', False)] string='Timesheets' args: 'account.analytic.line', 'so_line'
Public methods (0)

No public methods.

New fields (8)
  • billable_time Float
    help='Number of hours/days linked to a SOL.' readonly=True args: 'Billable Hours'
  • margin Float
    help='Timesheets revenues minus the costs' readonly=True args: 'Margin'
  • non_billable_time Float
    help='Number of hours/days not linked to a SOL.' readonly=True args: 'Non-billable Hours'
  • order_id Many2one → sale.order
    readonly=True string='Sales Order' args: 'sale.order'
  • so_line Many2one → sale.order.line
    readonly=True string='Sales Order Item' args: 'sale.order.line'
  • timesheet_invoice_id Many2one → account.move
    help='Invoice created from the timesheet' readonly=True string='Invoice' args: 'account.move'
  • timesheet_invoice_type Selection
    readonly=True string='Billable Type' args: TIMESHEET_INVOICE_TYPES
  • timesheet_revenues Float
    help='Number of hours spent multiplied by the unit price per hour/day.' readonly=True args: 'Timesheet Revenues'
Public methods (0)

No public methods.

REPOSITORY
REPOSITORYodoo/odoo
GIT
GIThttps://github.com/odoo/odoo.git
GIT FOLDER
GIT FOLDERhttps://github.com/odoo/odoo/tree/16.0/sale_timesheet
VERSION
VERSION 0.1.0
CATEGORY
CATEGORYHidden
LICENSE
LICENSELGPL-3
APPLICATION
APPLICATIONNo
AUTO-INSTALLABLE
AUTO-INSTALLABLEYes
AUTHORS
AUTHORS
MAINTAINERS
MAINTAINERS
COMMITTERS
COMMITTERSRaphael Collet, Odoo Translation Bot, Stéphane Bidoul, Martin Trigaux, Fabien Pinckaers, Thibault Delavallée, Denis Ledoux, Goffin Simon, Thibault Francois, Géry Debongnie, Aaron Bohy, GitHub, Damien Bouvy, Barad Mahendra, Yannick Tivisse, Christophe Monniez, Alexandre Kühn, Romeo Fragomeli, Victor Feyens, Andrea Grazioso (agr-odoo), Luis González, Alvaro Fuentes, Prakash Prajapati, Tiffany Chang (tic), Rémy Voet (ryv), Gorash, William Braeckman, Kevin Baptiste, Stefan-Calin Crainiciuc (stcc), Xavier BOL (xbo), roen-odoo, Nicolas (vin), william-andre, Audric Onockx (auon), william, Louis Baudoux, Pouya Malekinejad, Nicolas Bayet, Thibault Libioulle, Laurent Stukkens (LTU), gawa-odoo, Demesmaeker, Renaud Thiry, Vincent Larcin, Pierrot (prro), bve-odoo, damr, Thomas Lefebvre (thle), Victor Piryns (pivi), Andrew Gavgavian, Levi Siuzdak (sile), Maruan Aguerdouh (magm), Hansun (hale), Sanket Brahmbhatt, Bruno Boi, Cedric Prieels (cpr), Leloup Loïc (lole), Raouf, Kartik Chavda, Xavier Bol (xbo), Sarah Bellefroid, Dawn Hwang, Mahdi Cheikh Rouhou (macr), Mattis Megevand (mmeg), Nidhi Patel, ravb-odoo, bary, Abderraouf Ghrissi (abgh), kcv-odoo, Tanguy Quéguineur, Martin Trigaux (mat), bt-ojossen, Adesh Jolhe (adjo), Rohitkumar (roku), abderraouf, Manisha Tulsiyani, mash, David Van Droogenbroeck (DROD), Chris Duvivier, Olivia Fantinel
WEBSITE
WEBSITE
LAST TRACKING UPDATE
LAST TRACKING UPDATE2026-07-06 00:40:48
ODOO DEPENDENCIES
ODOO DEPENDENCIES odoo/odoo:
    - sale_project
    - sale_management
    - sale
    - sales_team
    - base
    - mail
    - base_setup
    - web
    - bus
    - web_tour
    - account_payment
    - account
    - product
    - uom
    - analytic
    - portal
    - web_editor
    - http_routing
    - auth_signup
    - digest
    - resource
    - payment
    - utm
    - project
    - rating
    - hr_timesheet
    - hr
    - hr_hourly_cost
PYTHON DEPENDENCIES
PYTHON DEPENDENCIES Not have
SYSTEM DEPENDENCIES
SYSTEM DEPENDENCIES Not have
DESCRIPTION
DESCRIPTION
Allows to sell timesheets in your sales order
=============================================

This module set the right product on all timesheet lines
according to the order/contract you work on. This allows to
have real delivered quantities in sales orders.

Code Analysis

Views touched (42)
XML IDNameModelTypeStatus
account_invoice_view_form_inherit_sale_timesheet account.invoice.form.inherit.timesheet account.move xpath Inherits account.view_move_form
hr_timesheet_line_form_inherit account.analytic.line.form.inherit account.analytic.line xpath Inherits hr_timesheet.hr_timesheet_line_form
hr_timesheet_line_tree_inherit account.analytic.line.tree.inherit account.analytic.line xpath Inherits hr_timesheet.hr_timesheet_line_tree
hr_timesheet_report_search_sale_timesheet timesheets.analysis.report.search timesheets.analysis.report xpath Inherits hr_timesheet.hr_timesheet_report_search
portal_invoice_page_inherit portal_invoice_page_inherit ir.ui.view qweb Inherits account.portal_invoice_page
portal_my_task_inherit portal_my_task_inherit ir.ui.view qweb Inherits project.portal_my_task
portal_my_timesheets_inherit portal_my_timesheets_inherit ir.ui.view qweb Inherits hr_timesheet.portal_my_timesheets
portal_timesheet_table_inherit portal_timesheet_table_inherit ir.ui.view qweb Inherits hr_timesheet.portal_timesheet_table
product_template_view_search_sale_timesheet product.template.search.timesheet product.template xpath Inherits product.product_template_search_view
project_create_invoice_view_form project.create.invoice.view.form project.create.invoice form New
project_create_sale_order_view_form project.create.sale.order.wizard.form project.create.sale.order form New
project_project_view_form project.project.form.inherit project.project xpath Inherits hr_timesheet.project_invoice_form
project_project_view_form_simplified_inherit project.project.view.form.simplified.inherit project.project xpath Inherits hr_timesheet.project_project_view_form_simplified_inherit_timesheet
project_project_view_kanban_inherit_sale_timesheet project.project.kanban.inherit.sale.timesheet project.project xpath Inherits hr_timesheet.view_project_kanban_inherited
project_project_view_kanban_inherit_sale_timesheet_so_button project.project.kanban.inherit.sale.timesheet.so.button project.project xpath Inherits project.view_project_kanban
project_sharing_inherit_project_task_view_form project.task.form.inherit.timesheet project.task xpath Inherits project.project_sharing_project_task_view_form
project_sharing_inherit_project_task_view_tree_sale_timesheet project.task.tree.inherit.sale.timesheet project.task xpath Inherits hr_timesheet.project_sharing_inherit_project_task_view_tree
project_task_view_form_inherit_sale_timesheet project.task.form.inherit.timesheet project.task xpath Inherits project.view_task_form2
project_update_default_description project_update_default_description ir.ui.view qweb Inherits project.project_update_default_description
report_timesheet_account_move report_timesheet_account_move ir.ui.view qweb New
report_timesheet_sale_order report_timesheet_sale_order ir.ui.view qweb New
res_config_settings_view_form res.config.settings.view.form.inherit.sale.timesheet res.config.settings xpath Inherits hr_timesheet.res_config_settings_view_form
sale_advance_payment_inv_timesheet_view_form sale_timesheet.sale.advance.payment.inv.view.form sale.advance.payment.inv xpath Inherits sale.view_sale_advance_payment_inv
sale_order_portal_content_inherit sale_order_portal_content_inherit ir.ui.view qweb Inherits sale.sale_order_portal_template
timesheet_sale_page timesheet_sale_page ir.ui.view qweb New
timesheet_view_search account.analytic.line.search account.analytic.line xpath Inherits hr_timesheet.hr_timesheet_line_search
timesheets_analysis_report_graph_inherit timesheets.analysis.report.graph timesheets.analysis.report xpath Inherits hr_timesheet.timesheets_analysis_report_pivot_employee
timesheets_analysis_report_graph_invoice_type timesheets.analysis.report.graph timesheets.analysis.report graph New
timesheets_analysis_report_graph_project_inherit timesheets.analysis.report.graph.project timesheets.analysis.report xpath Inherits hr_timesheet.timesheets_analysis_report_graph_project
timesheets_analysis_report_graph_task_inherit timesheets.analysis.report.graph.task timesheets.analysis.report xpath Inherits hr_timesheet.timesheets_analysis_report_graph_task
timesheets_analysis_report_graph_timesheet_grid timesheets.analysis.report.graph timesheets.analysis.report xpath Inherits hr_timesheet.timesheets_analysis_report_graph_employee
timesheets_analysis_report_pivot_inherit timesheets.analysis.report.pivot timesheets.analysis.report xpath Inherits hr_timesheet.timesheets_analysis_report_pivot_employee
timesheets_analysis_report_pivot_invoice_type timesheets.analysis.report.pivot timesheets.analysis.report pivot New
timesheets_analysis_report_pivot_project_inherit timesheets.analysis.report.pivot.project timesheets.analysis.report xpath Inherits hr_timesheet.timesheets_analysis_report_pivot_project
timesheets_analysis_report_pivot_task_inherit timesheets.analysis.report.pivot.task timesheets.analysis.report xpath Inherits hr_timesheet.timesheets_analysis_report_pivot_task
view_hr_timesheet_line_graph_employee_per_date account.analytic.line.graph.employee.per.date account.analytic.line graph New
view_hr_timesheet_line_pivot_billing_rate account.analytic.line.pivot.billing.rate account.analytic.line pivot New
view_order_form_inherit_sale_timesheet sale.order.form.sale.timesheet sale.order data Inherits sale_project.view_order_form_inherit_sale_project
view_product_timesheet_form product.template.timesheet.form product.template field Inherits sale.product_template_form_view
view_sale_service_inherit_form2 sale.service.form.view.inherit project.task xpath Inherits hr_timesheet.view_task_form2_inherited
view_task_form2_inherit_sale_timesheet view.task.form2.inherit project.task xpath Inherits sale_project.view_sale_project_inherit_form
view_task_tree2_inherited project.task.tree.inherited project.task xpath Inherits hr_timesheet.view_task_tree2_inherited
Models touched (19)

New fields (6)
  • commercial_partner_id Many2one → res.partner
    compute='_compute_commercial_partner' args: 'res.partner'
  • is_so_line_edited Boolean
    args: 'Is Sales Order Item Manually Edited'
  • order_id Many2one
    index=True readonly=True related='so_line.order_id' store=True
  • so_line Many2one
    compute='_compute_so_line' domain=_domain_so_line help='Sales order item to which the time spent will be added in order to be invoiced to your customer. Remove the sales order item for the timesheet entry to be non-billable.' readonly=False store=True
  • timesheet_invoice_id Many2one → account.move
    copy=False help='Invoice created from the timesheet' index='btree_not_null' readonly=True string='Invoice' args: 'account.move'
  • timesheet_invoice_type Selection
    compute='_compute_timesheet_invoice_type' compute_sudo=True readonly=True store=True string='Billable Type' args: TIMESHEET_INVOICE_TYPES
Public methods (1)
  • write(self, values)

New fields (4)
  • timesheet_count Integer
    compute='_compute_timesheet_count' args: 'Number of timesheets'
  • timesheet_encode_uom_id Many2one → uom.uom
    related='company_id.timesheet_encode_uom_id' args: 'uom.uom'
  • timesheet_ids One2many → account.analytic.line
    copy=False readonly=True string='Timesheets' args: 'account.analytic.line', 'timesheet_invoice_id'
  • timesheet_total_duration Integer
    compute='_compute_timesheet_total_duration' help='Total recorded duration, expressed in the encoding UoM, and rounded to the unit' args: 'Timesheet Total Duration'
Public methods (2)
  • action_post(self)
  • action_view_timesheet(self)

New fields (0)

No new fields.

Public methods (1)
  • unlink(self)

New fields (0)

No new fields.

Public methods (1)
  • reverse_moves(self)

New fields (0)

No new fields.

Public methods (1)
  • write(self, vals)

New fields (5)
  • project_id Many2one
    domain="[('company_id', '=', current_company_id), ('allow_billable', '=', True), ('pricing_type', '=', 'task_rate'), ('allow_timesheets', 'in', [service_policy == 'delivered_timesheet', True])]"
  • project_template_id Many2one
    domain="[('company_id', '=', current_company_id), ('allow_billable', '=', True), ('allow_timesheets', 'in', [service_policy == 'delivered_timesheet', True])]"
  • service_type Selection
    ondelete={'timesheet': 'set manual'} selection_add=[('timesheet', 'Timesheets on project (one fare per SO/Project)')]
  • service_upsell_threshold Float
    default=1 help='Percentage of time delivered compared to the prepaid amount that must be reached for the upselling opportunity activity to be triggered.' args: 'Threshold'
  • service_upsell_threshold_ratio Char
    compute='_compute_service_upsell_threshold_ratio'
Public methods (1)
  • write(self, vals)

New fields (5)
  • _candidate_orders Many2many → sale.order
    compute='_compute_candidate_orders' args: 'sale.order'
  • amount_to_invoice Monetary
    compute='_compute_amount_to_invoice' currency_field='currency_id' help='Total amount to invoice on the sales order, including all items (services, storables, expenses, ...)' args: 'Amount to invoice'
  • currency_id Many2one
    readonly=True related='sale_order_id.currency_id'
  • project_id Many2one → project.project
    help='Project to make billable' required=True args: 'project.project', 'Project'
  • sale_order_id Many2one → sale.order
    domain="[('id', 'in', _candidate_orders)]" required=True string='Choose the Sales Order to invoice' args: 'sale.order'
Public methods (2)
  • action_create_invoice(self)
  • default_get(self, fields)
    @api.model

New fields (7)
  • commercial_partner_id Many2one
    related='partner_id.commercial_partner_id'
  • company_id Many2one
    related='project_id.company_id'
  • info_invoice Char
    compute='_compute_info_invoice'
  • line_ids One2many → project.create.sale.order.line
    string='Lines' args: 'project.create.sale.order.line', 'wizard_id'
  • partner_id Many2one → res.partner
    help='Customer of the sales order' required=True string='Customer' args: 'res.partner'
  • project_id Many2one → project.project
    domain=[('sale_line_id', '=', False)] help='Project for which we are creating a sales order' required=True args: 'project.project', 'Project'
  • sale_order_id Many2one → sale.order
    domain="['|', '|', ('partner_id', '=', partner_id), ('partner_id', 'child_of', commercial_partner_id), ('partner_id', 'parent_of', partner_id)]" string='Sales Order' args: 'sale.order'
Public methods (2)
  • action_create_sale_order(self)
  • default_get(self, fields)
    @api.model

New fields (5)
  • currency_id Many2one → res.currency
    string='Currency' args: 'res.currency'
  • employee_id Many2one → hr.employee
    help='Employee that has timesheets on the project.' string='Employee' args: 'hr.employee'
  • price_unit Float
    help='Unit price of the sales order item.' args: 'Unit Price'
  • product_id Many2one → product.product
    domain=[('detailed_type', '=', 'service'), ('invoice_policy', '=', 'delivery'), ('service_type', '=', 'timesheet')] help='Product of the sales order item. Must be a service invoiced based on timesheets on tasks.' string='Service' args: 'product.product'
  • wizard_id Many2one → project.create.sale.order
    required=True args: 'project.create.sale.order'
Public methods (0)

No public methods.

New fields (8)
  • allocated_hours Float
    compute='_compute_allocated_hours' readonly=False store=True
  • billable_percentage Integer
    compute='_compute_billable_percentage' groups='hr_timesheet.group_hr_timesheet_approver' help='% of timesheets that are billable compared to the total number of timesheets linked to the AA of the project, rounded to the unit.'
  • display_create_order Boolean
    compute='_compute_display_create_order'
  • partner_id Many2one
    compute='_compute_partner_id' readonly=False store=True
  • pricing_type Selection
    compute='_compute_pricing_type' default='task_rate' help='The task rate is perfect if you would like to bill different services to different customers at different rates. The fixed rate is perfect if you bill a service at a fixed rate per hour or day worked regardless of the employee who performed it. The employee rate is preferable if your employees deliver the same service at a different rate. For instance, junior and senior consultants would deliver the same service (= consultancy), but at a different rate because of their level of seniority.' search='_search_pricing_type' string='Pricing' args: [('task_rate', 'Task rate'), ('fixed_rate', 'Project rate'), ('employee_rate', 'Employee rate')]
  • sale_line_employee_ids One2many → project.sale.line.employee.map
    copy=False help="Sales order item that will be selected by default on the timesheets of the corresponding employee. It bypasses the sales order item defined on the project and the task, and can be modified on each timesheet entry if necessary. In other words, it defines the rate at which an employee's time is billed based on their expertise, skills or experience, for instance.\nIf you would like to bill the same service at a different rate, you need to create two separate sales order items as each sales order item can only have a single unit price at a time.\nYou can also define the hourly company cost of your employees for their timesheets on this project specifically. It will bypass the timesheet cost set on the employee." args: 'project.sale.line.employee.map', 'project_id', 'Sale line/Employee map'
  • timesheet_product_id Many2one → product.product
    compute='_compute_timesheet_product_id' default=_default_timesheet_product_id domain="[\n ('detailed_type', '=', 'service'),\n ('invoice_policy', '=', 'delivery'),\n ('service_type', '=', 'timesheet'),\n '|', ('company_id', '=', False), ('company_id', '=', company_id)]" help='Service that will be used by default when invoicing the time spent on a task. It can be modified on each task individually by selecting a specific sales order item.' readonly=False store=True string='Timesheet Product' args: 'product.product'
  • warning_employee_rate Boolean
    compute='_compute_warning_employee_rate' compute_sudo=True
Public methods (7)
  • action_billable_time_button(self)
  • action_make_billable(self)
  • action_profitability_items(self, section_name, domain=None, res_id=False)
  • action_view_timesheet(self)
  • default_get(self, fields)
    @api.model
    Pre-fill timesheet product as "Time" data product when creating new project allowing billable tasks by default.
  • get_panel_data(self)
  • write(self, values)

New fields (11)
  • company_id Many2one → res.company
    related='project_id.company_id' string='Company' args: 'res.company'
  • cost Monetary
    compute='_compute_cost' currency_field='cost_currency_id' help="This cost overrides the employee's default employee hourly wage in employee's HR Settings" readonly=False store=True
  • cost_currency_id Many2one → res.currency
    readonly=True related='employee_id.currency_id' string='Cost Currency' args: 'res.currency'
  • currency_id Many2one → res.currency
    compute='_compute_currency_id' readonly=False store=True string='Currency' args: 'res.currency'
  • display_cost Monetary
    compute='_compute_display_cost' currency_field='cost_currency_id' inverse='_inverse_display_cost' string='Hourly Cost'
  • employee_id Many2one → hr.employee
    required=True args: 'hr.employee', 'Employee'
  • is_cost_changed Boolean
    compute='_compute_is_cost_changed' store=True args: 'Is Cost Manually Changed'
  • partner_id Many2one
    related='project_id.partner_id'
  • price_unit Float
    compute='_compute_price_unit' readonly=True store=True args: 'Unit Price'
  • project_id Many2one → project.project
    required=True args: 'project.project', 'Project'
  • sale_line_id Many2one → sale.order.line
    compute='_compute_sale_line_id' domain=<expr> readonly=False store=True args: 'sale.order.line', 'Sales Order Item'
Public methods (2)
  • create(self, vals_list)
    @api.model_create_multi
  • write(self, values)

New fields (9)
  • allow_billable Boolean
    related='project_id.allow_billable'
  • has_multi_sol Boolean
    compute='_compute_has_multi_sol' compute_sudo=True
  • is_project_map_empty Boolean
    compute='_compute_is_project_map_empty' args: 'Is Project map empty'
  • pricing_type Selection
    related='project_id.pricing_type'
  • remaining_hours_available Boolean
    related='sale_line_id.remaining_hours_available'
  • remaining_hours_so Float
    compute='_compute_remaining_hours_so' compute_sudo=True search='_search_remaining_hours_so' args: 'Remaining Hours on SO'
  • sale_order_id Many2one
    domain="['|', '|', ('partner_id', '=', partner_id), ('partner_id', 'child_of', commercial_partner_id), ('partner_id', 'parent_of', partner_id)]"
  • so_analytic_account_id Many2one
    related='sale_order_id.analytic_account_id' string='Sale Order Analytic Account'
  • timesheet_product_id Many2one
    related='project_id.timesheet_product_id'
Public methods (1)
  • SELF_READABLE_FIELDS(self)
    @property

New fields (0)

No new fields.

Public methods (0)

No public methods.

New fields (0)

No new fields.

Public methods (0)

No public methods.

New fields (1)
  • invoice_policy Boolean
    help='Timesheets taken when invoicing time spent' string='Invoice Policy'
Public methods (0)

No public methods.

New fields (3)
  • date_end_invoice_timesheet Date
    help='Only timesheets not yet invoiced (and validated, if applicable) from this period will be invoiced. If the period is not indicated, all timesheets not yet invoiced (and validated, if applicable) will be invoiced without distinction.' string='End Date'
  • date_start_invoice_timesheet Date
    help='Only timesheets not yet invoiced (and validated, if applicable) from this period will be invoiced. If the period is not indicated, all timesheets not yet invoiced (and validated, if applicable) will be invoiced without distinction.' string='Start Date'
  • invoicing_timesheet_enabled Boolean
    compute='_compute_invoicing_timesheet_enabled' store=True
Public methods (0)

No public methods.

New fields (5)
  • project_id Many2one
    domain="[('pricing_type', '!=', 'employee_rate'), ('analytic_account_id', '!=', False), ('company_id', '=', company_id)]"
  • timesheet_count Float
    compute='_compute_timesheet_ids' groups='hr_timesheet.group_hr_timesheet_user' string='Timesheet activities'
  • timesheet_encode_uom_id Many2one → uom.uom
    related='company_id.timesheet_encode_uom_id' args: 'uom.uom'
  • timesheet_ids Many2many → account.analytic.line
    compute='_compute_timesheet_ids' string='Timesheet activities associated to this sale' args: 'account.analytic.line'
  • timesheet_total_duration Integer
    compute='_compute_timesheet_total_duration' help='Total recorded duration, expressed in the encoding UoM, and rounded to the unit' args: 'Timesheet Total Duration'
Public methods (1)
  • action_view_timesheet(self)

New fields (6)
  • analytic_line_ids One2many
    domain=[('project_id', '=', False)]
  • has_displayed_warning_upsell Boolean
    copy=False args: 'Has Displayed Warning Upsell'
  • qty_delivered_method Selection
    selection_add=[('timesheet', 'Timesheets')]
  • remaining_hours Float
    compute='_compute_remaining_hours' compute_sudo=True store=True args: 'Remaining Hours on SO'
  • remaining_hours_available Boolean
    compute='_compute_remaining_hours_available' compute_sudo=True
  • timesheet_ids One2many → account.analytic.line
    domain=[('project_id', '!=', False)] string='Timesheets' args: 'account.analytic.line', 'so_line'
Public methods (1)
  • name_get(self)

New fields (8)
  • billable_time Float
    help='Number of hours/days linked to a SOL.' readonly=True args: 'Billable Hours'
  • margin Float
    help='Timesheets revenues minus the costs' readonly=True args: 'Margin'
  • non_billable_time Float
    help='Number of hours/days not linked to a SOL.' readonly=True args: 'Non-billable Hours'
  • order_id Many2one → sale.order
    readonly=True string='Sales Order' args: 'sale.order'
  • so_line Many2one → sale.order.line
    readonly=True string='Sales Order Item' args: 'sale.order.line'
  • timesheet_invoice_id Many2one → account.move
    help='Invoice created from the timesheet' readonly=True string='Invoice' args: 'account.move'
  • timesheet_invoice_type Selection
    readonly=True string='Billable Type' args: TIMESHEET_INVOICE_TYPES
  • timesheet_revenues Float
    help='Number of hours spent multiplied by the unit price per hour/day.' readonly=True args: 'Timesheet Revenues'
Public methods (0)

No public methods.

REPOSITORY
REPOSITORYodoo/odoo
GIT
GIThttps://github.com/odoo/odoo.git
GIT FOLDER
GIT FOLDERhttps://github.com/odoo/odoo/tree/15.0/sale_timesheet
VERSION
VERSION 0.1.0
CATEGORY
CATEGORYHidden
LICENSE
LICENSELGPL-3
APPLICATION
APPLICATIONNo
AUTO-INSTALLABLE
AUTO-INSTALLABLEYes
AUTHORS
AUTHORS
MAINTAINERS
MAINTAINERS
COMMITTERS
COMMITTERSRaphael Collet, Odoo Translation Bot, Martin Trigaux, Thibault Delavallée, Christophe Simonis, Xavier Morel, Goffin Simon, Thibault Francois, Aaron Bohy, GitHub, Barad Mahendra, Yannick Tivisse, Christophe Monniez, Adrian Torres, wan, Xavier-Do, Victor Feyens, Andrea Grazioso (agr-odoo), Julien Mougenot, Anh Thao Pham (pta), Alvaro Fuentes, Ivan Yelizariev, Philémon van Helden, Prakash Prajapati, Kamesh Patel, Tiffany Chang (tic), Nathan Marotte (nama), Mathieu Duckerts-Antoine, jbw, Djamel (otd), Hiral Bhavsar, jbm-odoo, William Braeckman, root, Kevin Baptiste, Stefan-Calin Crainiciuc (stcc), Xavier BOL (xbo), roen-odoo, prro-odoo, Mitali Patel, Nicolas (vin), william-andre, Audric Onockx (auon), Florian Vranckx, Pouya Malekinejad, Thibault Libioulle, Laurent Stukkens (LTU), Vincent Larcin, Pierrot (prro), Thomas Lefebvre (thle), Victor Piryns (pivi), Andrew Gavgavian, Levi Siuzdak (sile), Bruno Boi, Krina Oza, Kamlesh Pathekar, Cedric Prieels (cpr), Leloup Loïc (lole), Raouf, Xavier Bol (xbo), Leonardo Pavan Rocha, Dawn Hwang, Mattis Megevand (mmeg), Giang Phạm, Nidhi Patel, ravb-odoo, Abderraouf Ghrissi (abgh), Anjali
WEBSITE
WEBSITE
LAST TRACKING UPDATE
LAST TRACKING UPDATE2026-07-06 00:25:27
ODOO DEPENDENCIES
ODOO DEPENDENCIES odoo/odoo:
    - sale_project
    - sale_management
    - sale
    - sales_team
    - base
    - mail
    - base_setup
    - web
    - bus
    - web_tour
    - payment
    - account
    - product
    - uom
    - analytic
    - portal
    - web_editor
    - http_routing
    - auth_signup
    - digest
    - resource
    - utm
    - project
    - rating
    - hr_timesheet
    - hr
PYTHON DEPENDENCIES
PYTHON DEPENDENCIES Not have
SYSTEM DEPENDENCIES
SYSTEM DEPENDENCIES Not have
DESCRIPTION
DESCRIPTION
Allows to sell timesheets in your sales order
=============================================

This module set the right product on all timesheet lines
according to the order/contract you work on. This allows to
have real delivered quantities in sales orders.

Code Analysis

Views touched (32)
XML IDNameModelTypeStatus
account_invoice_view_form_inherit_sale_timesheet account.invoice.form.inherit.timesheet account.move xpath Inherits account.view_move_form
hr_timesheet_line_form_inherit account.analytic.line.form.inherit account.analytic.line xpath Inherits hr_timesheet.hr_timesheet_line_form
hr_timesheet_line_tree_inherit account.analytic.line.tree.inherit account.analytic.line xpath Inherits hr_timesheet.hr_timesheet_line_tree
portal_invoice_page_inherit portal_invoice_page_inherit ir.ui.view qweb Inherits account.portal_invoice_page
portal_my_task_inherit portal_my_task_inherit ir.ui.view qweb Inherits project.portal_my_task
portal_my_timesheets_inherit portal_my_timesheets_inherit ir.ui.view qweb Inherits hr_timesheet.portal_my_timesheets
portal_timesheet_table_inherit portal_timesheet_table_inherit ir.ui.view qweb Inherits hr_timesheet.portal_timesheet_table
product_template_view_search_sale_timesheet product.template.search.timesheet product.template xpath Inherits product.product_template_search_view
project_create_invoice_view_form project.create.invoice.view.form project.create.invoice form New
project_create_sale_order_view_form project.create.sale.order.wizard.form project.create.sale.order form New
project_profitability_report_view_graph project.profitability.report.graph project.profitability.report graph New
project_profitability_report_view_pivot project.profitability.report.pivot project.profitability.report pivot New
project_profitability_report_view_search project.profitability.report.search project.profitability.report search New
project_profitability_report_view_tree project.profitability.report.view.tree project.profitability.report tree New
project_project_view_form project.project.form.inherit project.project xpath Inherits hr_timesheet.project_invoice_form
project_project_view_form_salesman project.project.form.inherit.salesman project.project field Inherits sale_timesheet.project_project_view_form
project_project_view_form_simplified_inherit project.project.view.form.simplified.inherit project.project field Inherits hr_timesheet.project_project_view_form_simplified_inherit_timesheet
project_project_view_kanban_inherit_sale_timesheet project.project.kanban.inherit.sale.timesheet project.project xpath Inherits hr_timesheet.view_project_kanban_inherited
project_sharing_inherit_project_task_view_form project.task.form.inherit.timesheet project.task xpath Inherits project.project_sharing_project_task_view_form
project_task_view_form_inherit_sale_timesheet project.task.form.inherit.timesheet project.task xpath Inherits project.view_task_form2
project_task_view_form_inherit_sale_timesheet_editable project.task.form.view.form.inherit.sale.timesheet.editable project.task xpath Inherits project_task_view_form_inherit_sale_timesheet
project_update_default_description project_update_default_description ir.ui.view qweb Inherits project.project_update_default_description
res_config_settings_view_form res.config.settings.view.form.inherit.sale.timesheet res.config.settings xpath Inherits hr_timesheet.res_config_settings_view_form
sale_advance_payment_inv_timesheet_view_form sale_timesheet.sale.advance.payment.inv.view.form sale.advance.payment.inv xpath Inherits sale.view_sale_advance_payment_inv
sale_order_portal_content_inherit sale_order_portal_content_inherit ir.ui.view qweb Inherits sale.sale_order_portal_template
timesheet_view_pivot_revenue account.analytic.line.pivot.revenue account.analytic.line pivot New
timesheet_view_search account.analytic.line.search account.analytic.line xpath Inherits hr_timesheet.hr_timesheet_line_search
view_hr_timesheet_line_pivot_billing_rate account.analytic.line.pivot.billing.rate account.analytic.line pivot New
view_order_form_inherit_sale_timesheet sale.order.form.sale.timesheet sale.order data Inherits sale_project.view_order_form_inherit_sale_project
view_product_timesheet_form product.template.timesheet.form product.template xpath Inherits sale.product_template_form_view_invoice_policy
view_sale_service_inherit_form2 sale.service.form.view.inherit project.task xpath Inherits project.view_task_form2
view_task_form2_inherit_sale_timesheet view.task.form2.inherit project.task xpath Inherits project.view_task_form2
Models touched (18)

New fields (6)
  • commercial_partner_id Many2one → res.partner
    compute='_compute_commercial_partner' args: 'res.partner'
  • is_so_line_edited Boolean
    args: 'Is Sales Order Item Manually Edited'
  • order_id Many2one
    readonly=True related='so_line.order_id' store=True
  • so_line Many2one
    compute='_compute_so_line' domain="[('is_service', '=', True), ('is_expense', '=', False), ('state', 'in', ['sale', 'done']), ('order_partner_id', 'child_of', commercial_partner_id)]" readonly=False store=True
  • timesheet_invoice_id Many2one → account.move
    copy=False help='Invoice created from the timesheet' readonly=True string='Invoice' args: 'account.move'
  • timesheet_invoice_type Selection
    compute='_compute_timesheet_invoice_type' compute_sudo=True readonly=True store=True string='Billable Type' args: [('billable_time', 'Billed on Timesheets'), ('billable_fixed', 'Billed at a Fixed price'), ('non_billable', 'Non Billable Tasks'), ('timesheet_revenues', 'Timesheet Revenues'), ('service_revenues', 'Service Revenues'), ('other_revenues', 'Other Revenues'), ('other_costs', 'Other Costs')]
Public methods (1)
  • write(self, values)

New fields (4)
  • timesheet_count Integer
    compute='_compute_timesheet_count' args: 'Number of timesheets'
  • timesheet_encode_uom_id Many2one → uom.uom
    related='company_id.timesheet_encode_uom_id' args: 'uom.uom'
  • timesheet_ids One2many → account.analytic.line
    copy=False readonly=True string='Timesheets' args: 'account.analytic.line', 'timesheet_invoice_id'
  • timesheet_total_duration Integer
    compute='_compute_timesheet_total_duration' help='Total recorded duration, expressed in the encoding UoM, and rounded to the unit' args: 'Timesheet Total Duration'
Public methods (1)
  • action_view_timesheet(self)

New fields (0)

No new fields.

Public methods (1)
  • unlink(self)

New fields (0)

No new fields.

Public methods (1)
  • write(self, vals)

New fields (6)
  • project_id Many2one
    domain="[('company_id', '=', current_company_id), ('allow_billable', '=', True), ('pricing_type', '=', 'task_rate'), ('allow_timesheets', 'in', [service_policy == 'delivered_timesheet', True])]"
  • project_template_id Many2one
    domain="[('company_id', '=', current_company_id), ('allow_billable', '=', True), ('allow_timesheets', 'in', [service_policy == 'delivered_timesheet', True])]"
  • service_policy Selection
    compute='_compute_service_policy' inverse='_inverse_service_policy' string='Service Invoicing Policy' args: <expr>
  • service_type Selection
    ondelete={'timesheet': 'set default'} selection_add=[('timesheet', 'Timesheets on project (one fare per SO/Project)')]
  • service_upsell_threshold Float
    default=1 help='Percentage of time delivered compared to the prepaid amount that must be reached for the upselling opportunity activity to be triggered.' args: 'Threshold'
  • service_upsell_threshold_ratio Char
    compute='_compute_service_upsell_threshold_ratio'
Public methods (1)
  • write(self, vals)

New fields (5)
  • _candidate_orders Many2many → sale.order
    compute='_compute_candidate_orders' args: 'sale.order'
  • amount_to_invoice Monetary
    compute='_compute_amount_to_invoice' currency_field='currency_id' help='Total amount to invoice on the sales order, including all items (services, storables, expenses, ...)' args: 'Amount to invoice'
  • currency_id Many2one
    readonly=True related='sale_order_id.currency_id'
  • project_id Many2one → project.project
    help='Project to make billable' required=True args: 'project.project', 'Project'
  • sale_order_id Many2one → sale.order
    domain="[('id', 'in', _candidate_orders)]" required=True string='Choose the Sales Order to invoice' args: 'sale.order'
Public methods (2)
  • action_create_invoice(self)
  • default_get(self, fields)
    @api.model

New fields (7)
  • commercial_partner_id Many2one
    related='partner_id.commercial_partner_id'
  • company_id Many2one
    related='project_id.company_id'
  • info_invoice Char
    compute='_compute_info_invoice'
  • line_ids One2many → project.create.sale.order.line
    string='Lines' args: 'project.create.sale.order.line', 'wizard_id'
  • partner_id Many2one → res.partner
    help='Customer of the sales order' required=True string='Customer' args: 'res.partner'
  • project_id Many2one → project.project
    domain=[('sale_line_id', '=', False)] help='Project for which we are creating a sales order' required=True args: 'project.project', 'Project'
  • sale_order_id Many2one → sale.order
    domain="['|', '|', ('partner_id', '=', partner_id), ('partner_id', 'child_of', commercial_partner_id), ('partner_id', 'parent_of', partner_id)]" string='Sales Order' args: 'sale.order'
Public methods (2)
  • action_create_sale_order(self)
  • default_get(self, fields)
    @api.model

New fields (5)
  • currency_id Many2one → res.currency
    string='Currency' args: 'res.currency'
  • employee_id Many2one → hr.employee
    help='Employee that has timesheets on the project.' string='Employee' args: 'hr.employee'
  • price_unit Float
    help='Unit price of the sales order item.' args: 'Unit Price'
  • product_id Many2one → product.product
    domain=[('detailed_type', '=', 'service'), ('invoice_policy', '=', 'delivery'), ('service_type', '=', 'timesheet')] help='Product of the sales order item. Must be a service invoiced based on timesheets on tasks.' string='Service' args: 'product.product'
  • wizard_id Many2one → project.create.sale.order
    required=True args: 'project.create.sale.order'
Public methods (0)

No public methods.

New fields (21)
  • amount_untaxed_invoiced Float
    digits=(16, 2) group_operator='sum' readonly=True args: 'Amount Invoiced'
  • amount_untaxed_to_invoice Float
    digits=(16, 2) group_operator='sum' readonly=True args: 'Amount to Invoice'
  • analytic_account_id Many2one → account.analytic.account
    readonly=True string='Analytic Account' args: 'account.analytic.account'
  • company_id Many2one → res.company
    readonly=True string='Project Company' args: 'res.company'
  • currency_id Many2one → res.currency
    readonly=True string='Project Currency' args: 'res.currency'
  • expense_amount_untaxed_invoiced Float
    digits=(16, 2) group_operator='sum' readonly=True args: 'Amount Re-invoiced'
  • expense_amount_untaxed_to_invoice Float
    digits=(16, 2) group_operator='sum' readonly=True args: 'Amount to Re-invoice'
  • expense_cost Float
    digits=(16, 2) group_operator='sum' readonly=True args: 'Other Costs'
  • line_date Date
    readonly=True args: 'Date'
  • margin Float
    digits=(16, 2) group_operator='sum' readonly=True args: 'Margin'
  • order_confirmation_date Datetime
    readonly=True args: 'Sales Order Confirmation Date'
  • other_revenues Float
    digits=(16, 2) group_operator='sum' help='All revenues that are not from timesheets and that are linked to the analytic account of the project.' readonly=True args: 'Other Revenues'
  • partner_id Many2one → res.partner
    readonly=True string='Customer' args: 'res.partner'
  • product_id Many2one → product.product
    readonly=True string='Product' args: 'product.product'
  • project_id Many2one → project.project
    readonly=True string='Project' args: 'project.project'
  • sale_line_id Many2one → sale.order.line
    readonly=True string='Sale Order Line' args: 'sale.order.line'
  • sale_order_id Many2one → sale.order
    readonly=True string='Sale Order' args: 'sale.order'
  • task_id Many2one → project.task
    readonly=True string='Task' args: 'project.task'
  • timesheet_cost Float
    digits=(16, 2) group_operator='sum' readonly=True args: 'Timesheet Cost'
  • timesheet_unit_amount Float
    digits=(16, 2) group_operator='sum' readonly=True args: 'Timesheet Duration'
  • user_id Many2one → res.users
    readonly=True string='Project Manager' args: 'res.users'
Public methods (1)
  • init(self)

New fields (8)
  • allow_billable Boolean
    help='Invoice your time and material from tasks.' args: 'Billable'
  • billable_percentage Integer
    compute='_compute_billable_percentage' groups='hr_timesheet.group_hr_timesheet_approver' help='% of timesheets that are billable compared to the total number of timesheets linked to the AA of the project, rounded to the unit.'
  • display_create_order Boolean
    compute='_compute_display_create_order'
  • partner_id Many2one
    compute='_compute_partner_id' readonly=False store=True
  • pricing_type Selection
    compute='_compute_pricing_type' default='task_rate' help='The task rate is perfect if you would like to bill different services to different customers at different rates. The fixed rate is perfect if you bill a service at a fixed rate per hour or day worked regardless of the employee who performed it. The employee rate is preferable if your employees deliver the same service at a different rate. For instance, junior and senior consultants would deliver the same service (= consultancy), but at a different rate because of their level of seniority.' search='_search_pricing_type' string='Pricing' args: [('task_rate', 'Task rate'), ('fixed_rate', 'Project rate'), ('employee_rate', 'Employee rate')]
  • sale_line_employee_ids One2many → project.sale.line.employee.map
    copy=False help="Employee/Sale Order Item Mapping:\n Defines to which sales order item an employee's timesheet entry will be linked.By extension, it defines the rate at which an employee's time on the project is billed." args: 'project.sale.line.employee.map', 'project_id', 'Sale line/Employee map'
  • timesheet_product_id Many2one → product.product
    compute='_compute_timesheet_product_id' default=_default_timesheet_product_id domain="[\n ('detailed_type', '=', 'service'),\n ('invoice_policy', '=', 'delivery'),\n ('service_type', '=', 'timesheet'),\n '|', ('company_id', '=', False), ('company_id', '=', company_id)]" help='Select a Service product with which you would like to bill your time spent on tasks.' readonly=False store=True string='Timesheet Product' args: 'product.product'
  • warning_employee_rate Boolean
    compute='_compute_warning_employee_rate' compute_sudo=True
Public methods (8)
  • action_billable_time_button(self)
  • action_make_billable(self)
  • action_open_project_invoices(self)
  • action_view_all_rating(self)
  • action_view_timesheet(self)
  • default_get(self, fields)
    @api.model
    Pre-fill timesheet product as "Time" data product when creating new project allowing billable tasks by default.
  • get_panel_data(self)
  • write(self, values)

New fields (10)
  • company_id Many2one → res.company
    related='project_id.company_id' string='Company' args: 'res.company'
  • cost Monetary
    compute='_compute_cost' currency_field='cost_currency_id' help="This cost overrides the employee's default timesheet cost in employee's HR Settings" readonly=False store=True
  • cost_currency_id Many2one → res.currency
    readonly=True related='employee_id.currency_id' string='Cost Currency' args: 'res.currency'
  • currency_id Many2one → res.currency
    compute='_compute_currency_id' readonly=False store=True string='Currency' args: 'res.currency'
  • employee_id Many2one → hr.employee
    required=True args: 'hr.employee', 'Employee'
  • is_cost_changed Boolean
    compute='_compute_is_cost_changed' store=True args: 'Is Cost Manually Changed'
  • partner_id Many2one
    related='project_id.partner_id'
  • price_unit Float
    compute='_compute_price_unit' readonly=True store=True args: 'Unit Price'
  • project_id Many2one → project.project
    required=True args: 'project.project', 'Project'
  • sale_line_id Many2one → sale.order.line
    compute='_compute_sale_line_id' domain="[\n ('is_service', '=', True),\n ('is_expense', '=', False),\n ('state', 'in', ['sale', 'done']),\n ('order_partner_id', '=?', partner_id),\n '|', ('company_id', '=', False), ('company_id', '=', company_id)]" readonly=False store=True args: 'sale.order.line', 'Sale Order Item'
Public methods (2)
  • create(self, values)
    @api.model
  • write(self, values)

New fields (9)
  • allow_billable Boolean
    related='project_id.allow_billable'
  • has_multi_sol Boolean
    compute='_compute_has_multi_sol' compute_sudo=True
  • is_project_map_empty Boolean
    compute='_compute_is_project_map_empty' args: 'Is Project map empty'
  • pricing_type Selection
    related='project_id.pricing_type'
  • remaining_hours_available Boolean
    related='sale_line_id.remaining_hours_available'
  • remaining_hours_so Float
    compute='_compute_remaining_hours_so' compute_sudo=True args: 'Remaining Hours on SO'
  • sale_order_id Many2one
    domain="['|', '|', ('partner_id', '=', partner_id), ('partner_id', 'child_of', commercial_partner_id), ('partner_id', 'parent_of', partner_id)]"
  • so_analytic_account_id Many2one
    related='sale_order_id.analytic_account_id' string='Sale Order Analytic Account'
  • timesheet_product_id Many2one
    related='project_id.timesheet_product_id'
Public methods (1)
  • SELF_READABLE_FIELDS(self)
    @property

New fields (0)

No new fields.

Public methods (0)

No public methods.

New fields (0)

No new fields.

Public methods (0)

No public methods.

New fields (1)
  • invoice_policy Boolean
    help='Timesheets taken when invoicing time spent' string='Invoice Policy'
Public methods (0)

No public methods.

New fields (3)
  • date_end_invoice_timesheet Date
    help='Only timesheets not yet invoiced (and validated, if applicable) from this period will be invoiced. If the period is not indicated, all timesheets not yet invoiced (and validated, if applicable) will be invoiced without distinction.' string='End Date'
  • date_start_invoice_timesheet Date
    help='Only timesheets not yet invoiced (and validated, if applicable) from this period will be invoiced. If the period is not indicated, all timesheets not yet invoiced (and validated, if applicable) will be invoiced without distinction.' string='Start Date'
  • invoicing_timesheet_enabled Boolean
    default=_default_invoicing_timesheet_enabled
Public methods (1)
  • create_invoices(self)
    Override method from sale/wizard/sale_make_invoice_advance.py When the user want to invoice the timesheets to the SO up to a specific period then we need to recompute the qty_to_invoice for each product_id in sale.order.line, before creating the invoice.

New fields (5)
  • project_id Many2one
    domain="[('pricing_type', '!=', 'employee_rate'), ('analytic_account_id', '!=', False), ('company_id', '=', company_id)]"
  • timesheet_count Float
    compute='_compute_timesheet_ids' groups='hr_timesheet.group_hr_timesheet_user' string='Timesheet activities'
  • timesheet_encode_uom_id Many2one → uom.uom
    related='company_id.timesheet_encode_uom_id' args: 'uom.uom'
  • timesheet_ids Many2many → account.analytic.line
    compute='_compute_timesheet_ids' string='Timesheet activities associated to this sale' args: 'account.analytic.line'
  • timesheet_total_duration Integer
    compute='_compute_timesheet_total_duration' help='Total recorded duration, expressed in the encoding UoM, and rounded to the unit' args: 'Timesheet Total Duration'
Public methods (1)
  • action_view_timesheet(self)

New fields (5)
  • analytic_line_ids One2many
    domain=[('project_id', '=', False)]
  • has_displayed_warning_upsell Boolean
    copy=False args: 'Has Displayed Warning Upsell'
  • qty_delivered_method Selection
    selection_add=[('timesheet', 'Timesheets')]
  • remaining_hours Float
    compute='_compute_remaining_hours' compute_sudo=True store=True args: 'Remaining Hours on SO'
  • remaining_hours_available Boolean
    compute='_compute_remaining_hours_available' compute_sudo=True
Public methods (1)
  • name_get(self)
REPOSITORY
REPOSITORYodoo/odoo
GIT
GIThttps://github.com/odoo/odoo.git
GIT FOLDER
GIT FOLDERhttps://github.com/odoo/odoo/tree/14.0/sale_timesheet
VERSION
VERSION 0.1.0
CATEGORY
CATEGORYHidden
LICENSE
LICENSELGPL-3
APPLICATION
APPLICATIONNo
AUTO-INSTALLABLE
AUTO-INSTALLABLEYes
AUTHORS
AUTHORS
MAINTAINERS
MAINTAINERS
COMMITTERS
COMMITTERSRaphael Collet, Odoo Translation Bot, Martin Trigaux, Olivier Dony, Thibault Delavallée, Christophe Simonis, Xavier Morel, Goffin Simon, Nicolas Martinelli, Thibault Francois, Jeremy Kersten, GitHub, Barad Mahendra, Yannick Tivisse, Laurent Smet, Christophe Monniez, Jorge Pinna Puissant, wan, Julien Castiaux, Rémi Rahir, Xavier-Do, Victor Feyens, Andrea Grazioso (agr-odoo), fw-bot, Anh Thao Pham (pta), Luis González, Alvaro Fuentes, Rémy Baranx (bar), Lucas Lefèvre, Ivan Yelizariev, Touati Djamel (otd), Prakash Prajapati, Kamesh Patel, Nathan Marotte (nama), Mathieu Duckerts-Antoine, Djamel (otd), Hiral Bhavsar, Benjamin Frantzen (bfr), jbm-odoo, Arnaud Joset, Yann Papouin, Kevin Baptiste, Pierre Verkest, Xavier BOL (xbo), roen-odoo, Hubert Van de Walle (huvw), jdoutreloux, prro-odoo, Jigar Vaghela, MerlinGuillaume, Audric Onockx (auon), william, Pouya Malekinejad, Antoine Vandevenne (anv), Thibault Libioulle, Laurent Stukkens (LTU), Pierrot (prro), Thomas Lefebvre (thle), Victor Piryns (pivi), Krina Oza, ntlquynh2209, sang250399
WEBSITE
WEBSITE
LAST TRACKING UPDATE
LAST TRACKING UPDATE2026-07-06 00:14:28
ODOO DEPENDENCIES
ODOO DEPENDENCIES odoo/odoo:
    - sale_project
    - sale_management
    - sale
    - sales_team
    - base
    - mail
    - base_setup
    - web
    - bus
    - web_tour
    - payment
    - account
    - product
    - uom
    - analytic
    - portal
    - web_editor
    - http_routing
    - auth_signup
    - digest
    - resource
    - utm
    - project
    - rating
    - hr_timesheet
    - hr
PYTHON DEPENDENCIES
PYTHON DEPENDENCIES Not have
SYSTEM DEPENDENCIES
SYSTEM DEPENDENCIES Not have
DESCRIPTION
DESCRIPTION
Allows to sell timesheets in your sales order
=============================================

This module set the right product on all timesheet lines
according to the order/contract you work on. This allows to
have real delivered quantities in sales orders.

Code Analysis

Views touched (33)
XML IDNameModelTypeStatus
account_invoice_view_form_inherit_sale_timesheet account.invoice.form.inherit.timesheet account.move xpath Inherits account.view_move_form
assets_backend sale timesheet assets ir.ui.view qweb Inherits web.assets_backend
assets_frontend report timesheet assets ir.ui.view qweb Inherits web.assets_frontend
portal_invoice_page_inherit_timesheet portal_invoice_page_inherit_timesheet ir.ui.view qweb Inherits account.portal_invoice_page
portal_my_timesheets_inherit portal_my_timesheets_inherit ir.ui.view qweb Inherits hr_timesheet.portal_my_timesheets
product_template_view_search_sale_timesheet product.template.search.timesheet product.template xpath Inherits product.product_template_search_view
progressbar project overview progressbar segments ir.ui.view qweb New
project_create_invoice_view_form project.create.invoice.view.form project.create.invoice form New
project_create_sale_order_view_form project.create.sale.order.wizard.form project.create.sale.order form New
project_profitability_report_view_graph project.profitability.report.graph project.profitability.report graph New
project_profitability_report_view_pivot project.profitability.report.pivot project.profitability.report pivot New
project_profitability_report_view_search project.profitability.report.search project.profitability.report search New
project_project_view_form project.project.form.inherit project.project div Inherits hr_timesheet.project_invoice_form
project_project_view_form_salesman project.project.form.inherit.salesman project.project field Inherits sale_timesheet.project_project_view_form
project_project_view_form_simplified_inherit project.project.view.form.simplified.inherit project.project field Inherits hr_timesheet.project_project_view_form_simplified_inherit_timesheet
project_project_view_kanban_inherit_sale_timesheet project.project.kanban.inherit.sale.timesheet project.project xpath Inherits hr_timesheet.view_project_kanban_inherited
project_task_create_sale_order_view_form project.task.create.sale.order.wizard.form project.task.create.sale.order form New
project_task_view_form_inherit_sale_timesheet project.task.form.inherit.timesheet project.task xpath Inherits project.view_task_form2
quick_create_task_form_sale_timesheet project.task.form.inherit.timesheet project.task field Inherits project.quick_create_task_form
report_invoice_document report_invoice_document ir.ui.view qweb Inherits account.report_invoice_document
res_config_settings_view_form res.config.settings.view.form.inherit.sale.timesheet res.config.settings xpath Inherits hr_timesheet.res_config_settings_view_form
sale_advance_payment_inv_timesheet_view_form sale_timesheet.sale.advance.payment.inv.view.form sale.advance.payment.inv xpath Inherits sale.view_sale_advance_payment_inv
sale_order_portal_content_inherit sale_order_portal_content_inherit ir.ui.view qweb Inherits sale.sale_order_portal_content
sale_order_portal_template_inherit sale_order_portal_template_inherit ir.ui.view qweb Inherits sale.sale_order_portal_template
timesheet_plan Timesheet Plan project.project qweb New
timesheet_view_pivot_revenue account.analytic.line.pivot.revenue account.analytic.line pivot New
timesheet_view_search account.analytic.line.search account.analytic.line xpath Inherits hr_timesheet.hr_timesheet_line_search
timesheet_view_tree_sale account.analytic.line.view.tree.with.allow.billable account.analytic.line xpath Inherits hr_timesheet.timesheet_view_tree_user
view_hr_timesheet_line_pivot_billing_rate account.analytic.line.pivot.billing.rate account.analytic.line pivot New
view_order_form_inherit_sale_timesheet sale.order.form.sale.timesheet sale.order data Inherits sale_project.view_order_form_inherit_sale_project
view_product_timesheet_form product.template.timesheet.form product.template xpath Inherits sale.product_template_form_view_invoice_policy
view_sale_service_inherit_form2 sale.service.form.view.inherit project.task xpath Inherits project.view_task_form2
view_task_form2_inherit_sale_timesheet view.task.form2.inherit project.task xpath Inherits project.view_task_form2
Models touched (18)

New fields (4)
  • non_allow_billable Boolean
    help='Your timesheet will not be billed.' args: 'Non-Billable'
  • so_line Many2one
    compute='_compute_so_line' readonly=False store=True
  • timesheet_invoice_id Many2one → account.move
    copy=False help='Invoice created from the timesheet' readonly=True string='Invoice' args: 'account.move'
  • timesheet_invoice_type Selection
    compute='_compute_timesheet_invoice_type' compute_sudo=True readonly=True store=True string='Billable Type' args: [('billable_time', 'Billed on Timesheets'), ('billable_fixed', 'Billed at a Fixed price'), ('non_billable', 'Non Billable Tasks'), ('non_billable_timesheet', 'Non Billable Timesheet'), ('non_billable_project', 'No task found')]
Public methods (2)
  • unlink(self)
  • write(self, values)

New fields (2)
  • timesheet_count Integer
    compute='_compute_timesheet_count' args: 'Number of timesheets'
  • timesheet_ids One2many → account.analytic.line
    copy=False readonly=True string='Timesheets' args: 'account.analytic.line', 'timesheet_invoice_id'
Public methods (1)
  • action_view_timesheet(self)

New fields (0)

No new fields.

Public methods (1)
  • unlink(self)

New fields (0)

No new fields.

Public methods (2)
  • unlink(self)
  • write(self, vals)

New fields (4)
  • project_id Many2one
    domain="[('company_id', '=', current_company_id), ('allow_billable', '=', True), ('bill_type', '=', 'customer_task'), ('allow_timesheets', 'in', [service_policy == 'delivered_timesheet', True])]"
  • project_template_id Many2one
    domain="[('company_id', '=', current_company_id), ('allow_billable', '=', True), ('bill_type', '=', 'customer_project'), ('allow_timesheets', 'in', [service_policy == 'delivered_timesheet', True])]"
  • service_policy Selection
    compute='_compute_service_policy' inverse='_inverse_service_policy' string='Service Invoicing Policy' args: [('ordered_timesheet', 'Prepaid'), ('delivered_timesheet', 'Timesheets on tasks'), ('delivered_manual', 'Milestones (manually set quantities on order)')]
  • service_type Selection
    ondelete={'timesheet': 'set default'} selection_add=[('timesheet', 'Timesheets on project (one fare per SO/Project)')]
Public methods (2)
  • unlink(self)
  • write(self, vals)

New fields (5)
  • _candidate_orders Many2many → sale.order
    compute='_compute_candidate_orders' args: 'sale.order'
  • amount_to_invoice Monetary
    compute='_compute_amount_to_invoice' currency_field='currency_id' help='Total amount to invoice on the sales order, including all items (services, storables, expenses, ...)' args: 'Amount to invoice'
  • currency_id Many2one
    readonly=True related='sale_order_id.currency_id'
  • project_id Many2one → project.project
    help='Project to make billable' required=True args: 'project.project', 'Project'
  • sale_order_id Many2one → sale.order
    domain="[('id', 'in', _candidate_orders)]" required=True string='Choose the Sales Order to invoice' args: 'sale.order'
Public methods (2)
  • action_create_invoice(self)
  • default_get(self, fields)
    @api.model

New fields (9)
  • commercial_partner_id Many2one
    related='partner_id.commercial_partner_id'
  • company_id Many2one
    related='project_id.company_id'
  • info_invoice Char
    compute='_compute_info_invoice'
  • line_ids One2many → project.create.sale.order.line
    string='Lines' args: 'project.create.sale.order.line', 'wizard_id'
  • link_selection Selection
    default='create' required=True args: [('create', 'Create a new sales order'), ('link', 'Link to an existing sales order')]
  • partner_id Many2one → res.partner
    help='Customer of the sales order' required=True string='Customer' args: 'res.partner'
  • pricing_type Selection
    related='project_id.pricing_type'
  • project_id Many2one → project.project
    domain=[('sale_line_id', '=', False)] help='Project for which we are creating a sales order' required=True args: 'project.project', 'Project'
  • sale_order_id Many2one → sale.order
    domain="['|', '|', ('partner_id', '=', partner_id), ('partner_id', 'child_of', commercial_partner_id), ('partner_id', 'parent_of', partner_id)]" string='Sales Order' args: 'sale.order'
Public methods (3)
  • action_create_sale_order(self)
  • action_link_sale_order(self)
  • default_get(self, fields)
    @api.model

New fields (6)
  • currency_id Many2one → res.currency
    string='Currency' args: 'res.currency'
  • employee_id Many2one → hr.employee
    help='Employee that has timesheets on the project.' string='Employee' args: 'hr.employee'
  • price_unit Float
    help='Unit price of the sales order item.' args: 'Unit Price'
  • product_id Many2one → product.product
    domain=[('type', '=', 'service'), ('invoice_policy', '=', 'delivery'), ('service_type', '=', 'timesheet')] help='Product of the sales order item. Must be a service invoiced based on timesheets on tasks.' string='Service' args: 'product.product'
  • sale_line_id Many2one → sale.order.line
    compute='_compute_sale_line_id' readonly=False store=True args: 'sale.order.line', 'Sale Order Item'
  • wizard_id Many2one → project.create.sale.order
    required=True args: 'project.create.sale.order'
Public methods (0)

No public methods.

New fields (20)
  • amount_untaxed_invoiced Float
    digits=(16, 2) group_operator='sum' readonly=True args: 'Untaxed Amount Invoiced'
  • amount_untaxed_to_invoice Float
    digits=(16, 2) group_operator='sum' readonly=True args: 'Untaxed Amount to Invoice'
  • analytic_account_id Many2one → account.analytic.account
    readonly=True string='Analytic Account' args: 'account.analytic.account'
  • company_id Many2one → res.company
    readonly=True string='Project Company' args: 'res.company'
  • currency_id Many2one → res.currency
    readonly=True string='Project Currency' args: 'res.currency'
  • expense_amount_untaxed_invoiced Float
    digits=(16, 2) group_operator='sum' readonly=True args: 'Untaxed Amount Re-invoiced'
  • expense_amount_untaxed_to_invoice Float
    digits=(16, 2) group_operator='sum' readonly=True args: 'Untaxed Amount to Re-invoice'
  • expense_cost Float
    digits=(16, 2) group_operator='sum' readonly=True args: 'Other Costs'
  • line_date Date
    readonly=True args: 'Date'
  • margin Float
    digits=(16, 2) group_operator='sum' readonly=True args: 'Margin'
  • order_confirmation_date Datetime
    readonly=True args: 'Sales Order Confirmation Date'
  • other_revenues Float
    digits=(16, 2) group_operator='sum' help='All revenues that are not from timesheets and that are linked to the analytic account of the project.' readonly=True args: 'Other Revenues'
  • partner_id Many2one → res.partner
    readonly=True string='Customer' args: 'res.partner'
  • product_id Many2one → product.product
    readonly=True string='Product' args: 'product.product'
  • project_id Many2one → project.project
    readonly=True string='Project' args: 'project.project'
  • sale_line_id Many2one → sale.order.line
    readonly=True string='Sale Order Line' args: 'sale.order.line'
  • sale_order_id Many2one → sale.order
    readonly=True string='Sale Order' args: 'sale.order'
  • timesheet_cost Float
    digits=(16, 2) group_operator='sum' readonly=True args: 'Timesheet Cost'
  • timesheet_unit_amount Float
    digits=(16, 2) group_operator='sum' readonly=True args: 'Timesheet Duration'
  • user_id Many2one → res.users
    readonly=True string='Project Manager' args: 'res.users'
Public methods (1)
  • init(self)

New fields (0)

No new fields.

Public methods (0)

No public methods.

New fields (7)
  • allow_billable Boolean
    help='Invoice your time and material from tasks.' args: 'Billable'
  • bill_type Selection
    default='customer_task' help='When billing tasks individually, a Sales Order will be created from each task. It is perfect if you would like to bill different services to different customers at different rates. \n When billing the whole project, a Sales Order will be created from the project instead. This option is better if you would like to bill all the tasks of a given project to a specific customer either at a fixed rate, or at an employee rate.' string='Invoice Tasks to' args: [('customer_task', 'Different customers'), ('customer_project', 'A unique customer')]
  • display_create_order Boolean
    compute='_compute_display_create_order'
  • pricing_type Selection
    default='fixed_rate' help='The fixed rate is perfect if you bill a service at a fixed rate per hour or day worked regardless of the employee who performed it. The employee rate is preferable if your employees deliver the same service at a different rate. For instance, junior and senior consultants would deliver the same service (= consultancy), but at a different rate because of their level of seniority.' string='Pricing' args: [('fixed_rate', 'Project rate'), ('employee_rate', 'Employee rate')]
  • sale_line_employee_ids One2many → project.sale.line.employee.map
    copy=False help="Employee/Sale Order Item Mapping:\n Defines to which sales order item an employee's timesheet entry will be linked.By extension, it defines the rate at which an employee's time on the project is billed." args: 'project.sale.line.employee.map', 'project_id', 'Sale line/Employee map'
  • timesheet_product_id Many2one → product.product
    compute='_compute_timesheet_product_id' default=_default_timesheet_product_id domain="[\n ('type', '=', 'service'),\n ('invoice_policy', '=', 'delivery'),\n ('service_type', '=', 'timesheet'),\n '|', ('company_id', '=', False), ('company_id', '=', company_id)]" help='Select a Service product with which you would like to bill your time spent on tasks.' readonly=False store=True string='Timesheet Product' args: 'product.product'
  • warning_employee_rate Boolean
    compute='_compute_warning_employee_rate'
Public methods (5)
  • action_make_billable(self)
  • action_view_timesheet(self)
  • action_view_timesheet_plan(self)
  • default_get(self, fields)
    @api.model
    Pre-fill timesheet product as "Time" data product when creating new project allowing billable tasks by default.
  • write(self, values)

New fields (7)
  • company_id Many2one → res.company
    related='project_id.company_id' string='Company' args: 'res.company'
  • currency_id Many2one → res.currency
    compute='_compute_price_unit' readonly=False store=True string='Currency' args: 'res.currency'
  • employee_id Many2one → hr.employee
    required=True args: 'hr.employee', 'Employee'
  • price_unit Float
    compute='_compute_price_unit' readonly=True store=True args: 'Unit Price'
  • project_id Many2one → project.project
    required=True args: 'project.project', 'Project'
  • sale_line_id Many2one → sale.order.line
    domain=[('is_service', '=', True)] args: 'sale.order.line', 'Sale Order Item'
  • timesheet_product_id Many2one → product.product
    domain="[\n ('type', '=', 'service'),\n ('invoice_policy', '=', 'delivery'),\n ('service_type', '=', 'timesheet'),\n '|', ('company_id', '=', False), ('company_id', '=', company_id)]" string='Service' args: 'product.product'
Public methods (2)
  • create(self, values)
    @api.model
  • write(self, values)

New fields (13)
  • allow_billable Boolean
    related='project_id.allow_billable'
  • analytic_account_id Many2one → account.analytic.account
    related='sale_order_id.analytic_account_id' args: 'account.analytic.account'
  • bill_type Selection
    related='project_id.bill_type'
  • display_create_order Boolean
    compute='_compute_display_create_order'
  • has_multi_sol Boolean
    compute='_compute_has_multi_sol' compute_sudo=True
  • is_project_map_empty Boolean
    compute='_compute_is_project_map_empty' args: 'Is Project map empty'
  • non_allow_billable Boolean
    help='Your timesheets linked to this task will not be billed.' args: 'Non-Billable'
  • pricing_type Selection
    related='project_id.pricing_type'
  • project_sale_order_id Many2one
    compute='_compute_project_sale_order_id' related=None search='_search_project_sale_order_id'
  • remaining_hours_available Boolean
    related='sale_line_id.remaining_hours_available'
  • remaining_hours_so Float
    compute='_compute_remaining_hours_so' compute_sudo=True args: 'Remaining Hours on SO'
  • sale_order_id Many2one
    compute='_compute_sale_order_id' domain="['|', '|', ('partner_id', '=', partner_id), ('partner_id', 'child_of', commercial_partner_id), ('partner_id', 'parent_of', partner_id)]" readonly=False store=True
  • timesheet_product_id Many2one → product.product
    domain="[\n ('type', '=', 'service'),\n ('invoice_policy', '=', 'delivery'),\n ('service_type', '=', 'timesheet'),\n '|', ('company_id', '=', False), ('company_id', '=', company_id)]" help='Select a Service product with which you would like to bill your time spent on this task.' string='Service' args: 'product.product'
Public methods (3)
  • action_make_billable(self)
  • default_get(self, fields)
    @api.model
  • write(self, values)

New fields (10)
  • commercial_partner_id Many2one
    related='partner_id.commercial_partner_id'
  • currency_id Many2one → res.currency
    readonly=False related='product_id.currency_id' string='Currency' args: 'res.currency'
  • info_invoice Char
    compute='_compute_info_invoice'
  • link_selection Selection
    default='create' required=True args: [('create', 'Create a new sales order'), ('link', 'Link to an existing sales order')]
  • partner_id Many2one → res.partner
    help='Customer of the sales order' required=True string='Customer' args: 'res.partner'
  • price_unit Float
    help='Unit price of the sales order item.' args: 'Unit Price'
  • product_id Many2one → product.product
    domain=[('type', '=', 'service'), ('invoice_policy', '=', 'delivery'), ('service_type', '=', 'timesheet')] help='Product of the sales order item. Must be a service invoiced based on timesheets on tasks. The existing timesheet will be linked to this product.' required=True string='Service' args: 'product.product'
  • sale_line_id Many2one → sale.order.line
    domain="[('is_service', '=', True), ('order_partner_id', 'child_of', commercial_partner_id), ('is_expense', '=', False), ('state', 'in', ['sale', 'done']), ('order_id', '=?', sale_order_id)]" args: 'sale.order.line', 'Sales Order Item'
  • sale_order_id Many2one → sale.order
    domain="['|', '|', ('partner_id', '=', partner_id), ('partner_id', 'child_of', commercial_partner_id), ('partner_id', 'parent_of', partner_id)]" string='Sales Order' args: 'sale.order'
  • task_id Many2one → project.task
    domain=[('sale_line_id', '=', False)] help='Task for which we are creating a sales order' required=True args: 'project.task', 'Task'
Public methods (3)
  • action_create_sale_order(self)
  • action_link_sale_order(self)
  • default_get(self, fields)
    @api.model

New fields (0)

No new fields.

Public methods (0)

No public methods.

New fields (3)
  • date_end_invoice_timesheet Date
    help='Only timesheets not yet invoiced (and validated, if applicable) from this period will be invoiced. If the period is not indicated, all timesheets not yet invoiced (and validated, if applicable) will be invoiced without distinction.' string='End Date'
  • date_start_invoice_timesheet Date
    help='Only timesheets not yet invoiced (and validated, if applicable) from this period will be invoiced. If the period is not indicated, all timesheets not yet invoiced (and validated, if applicable) will be invoiced without distinction.' string='Start Date'
  • invoicing_timesheet_enabled Boolean
    default=_default_invoicing_timesheet_enabled
Public methods (1)
  • create_invoices(self)
    Override method from sale/wizard/sale_make_invoice_advance.py When the user want to invoice the timesheets to the SO up to a specific period then we need to recompute the qty_to_invoice for each product_id in sale.order.line, before creating the invoice.

New fields (5)
  • project_id Many2one
    domain="['|', ('bill_type', '=', 'customer_task'), ('pricing_type', '=', 'fixed_rate'), ('analytic_account_id', '!=', False), ('company_id', '=', company_id)]"
  • timesheet_count Float
    compute='_compute_timesheet_ids' groups='hr_timesheet.group_hr_timesheet_user' string='Timesheet activities'
  • timesheet_encode_uom_id Many2one → uom.uom
    related='company_id.timesheet_encode_uom_id' args: 'uom.uom'
  • timesheet_ids Many2many → account.analytic.line
    compute='_compute_timesheet_ids' string='Timesheet activities associated to this sale' args: 'account.analytic.line'
  • timesheet_total_duration Integer
    compute='_compute_timesheet_total_duration' help='Total recorded duration, expressed in the encoding UoM, and rounded to the unit' args: 'Timesheet Total Duration'
Public methods (2)
  • action_view_project_ids(self)
  • action_view_timesheet(self)

New fields (4)
  • analytic_line_ids One2many
    domain=[('project_id', '=', False)]
  • qty_delivered_method Selection
    selection_add=[('timesheet', 'Timesheets')]
  • remaining_hours Float
    compute='_compute_remaining_hours' args: 'Remaining Hours on SO'
  • remaining_hours_available Boolean
    compute='_compute_remaining_hours_available'
Public methods (1)
  • name_get(self)
REPOSITORY
REPOSITORYodoo/odoo
GIT
GIThttps://github.com/odoo/odoo.git
GIT FOLDER
GIT FOLDERhttps://github.com/odoo/odoo/tree/13.0/sale_timesheet
VERSION
VERSION 0.1.0
CATEGORY
CATEGORYHidden
LICENSE
LICENSELGPL-3
APPLICATION
APPLICATIONNo
AUTO-INSTALLABLE
AUTO-INSTALLABLEYes
AUTHORS
AUTHORS
MAINTAINERS
MAINTAINERS
COMMITTERS
COMMITTERSRaphael Collet, Odoo Translation Bot, Christophe Matthieu, Martin Trigaux, Denis Ledoux, Christophe Simonis, Xavier Morel, Goffin Simon, Nicolas Martinelli, qdp-odoo, Géry Debongnie, Aaron Bohy, GitHub, Damien Bouvy, Yannick Tivisse, Odoo Online, jem-odoo, Christophe Monniez, Jorge Pinna Puissant, Toufik Ben Jaa, Adrian Torres, Aurélien Warnon, Priyanka Kakadiya, Xavier-Do, Victor Feyens, Andrea Grazioso (agr-odoo), Shreya Modi, fw-bot, Anh Thao Pham (pta), svs-odoo, Ivan Yelizariev, Prakash Prajapati, Djamel (otd), qho, Romain Estievenart, Kevin Baptiste, Xavier BOL (xbo), Geoffroy Larue, Audric Onockx (auon), Antoine Vandevenne (anv), Thibault Libioulle, MaxLarue
WEBSITE
WEBSITE
LAST TRACKING UPDATE
LAST TRACKING UPDATE2026-07-06 00:06:42
ODOO DEPENDENCIES
ODOO DEPENDENCIES odoo/odoo:
    - sale_management
    - sale
    - sales_team
    - base
    - mail
    - base_setup
    - web
    - bus
    - web_tour
    - payment
    - account
    - product
    - uom
    - analytic
    - portal
    - web_editor
    - http_routing
    - auth_signup
    - digest
    - resource
    - utm
    - hr_timesheet
    - hr
    - mail_bot
    - project
    - rating
PYTHON DEPENDENCIES
PYTHON DEPENDENCIES Not have
SYSTEM DEPENDENCIES
SYSTEM DEPENDENCIES Not have
DESCRIPTION
DESCRIPTION
Allows to sell timesheets in your sales order
=============================================

This module set the right product on all timesheet lines
according to the order/contract you work on. This allows to
have real delivered quantities in sales orders.

Code Analysis

Views touched (24)
XML IDNameModelTypeStatus
account_invoice_view_form_inherit_sale_timesheet account.invoice.form.inherit.timesheet account.move xpath Inherits account.view_move_form
assets_backend sale timesheet assets ir.ui.view qweb Inherits web.assets_backend
assets_frontend report timesheet assets ir.ui.view qweb Inherits web.assets_frontend
portal_invoice_page_inherit_timesheet portal_invoice_page_inherit_timesheet ir.ui.view qweb Inherits account.portal_invoice_page
product_template_view_search_sale_timesheet product.template.search.timesheet product.template xpath Inherits product.product_template_search_view
progressbar project overview progressbar segments ir.ui.view qweb New
project_create_invoice_view_form project.create.invoice.view.form project.create.invoice form New
project_create_sale_order_view_form project.create.sale.order.wizard.form project.create.sale.order form New
project_profitability_report_view_graph project.profitability.report.graph project.profitability.report graph New
project_profitability_report_view_pivot project.profitability.report.pivot project.profitability.report pivot New
project_profitability_report_view_search project.profitability.report.search project.profitability.report search New
project_project_view_form project.project.form.inherit project.project xpath Inherits project.edit_project
project_project_view_kanban_inherit_sale_timesheet project.project.kanban.inherit.sale.timesheet project.project xpath Inherits hr_timesheet.view_project_kanban_inherited
project_task_view_form_inherit_sale_line_editable project.task.form.inherit.sale.line.editable.salesman project.task xpath Inherits view_sale_service_inherit_form2
project_task_view_form_inherit_sale_timesheet project.task.form.inherit.timesheet project.task xpath Inherits hr_timesheet.view_task_form2_inherited
project_task_view_form_sale_order project.task.form.inherit.sale.order project.task div Inherits project.view_task_form2
project_task_view_search project.task.search.inherit project.task xpath Inherits project.view_task_search_form
res_config_settings_view_form res.config.settings.view.form.inherit.sale.timesheet res.config.settings xpath Inherits hr_timesheet.res_config_settings_view_form
timesheet_plan Timesheet Plan project.project nav New
timesheet_view_pivot_revenue account.analytic.line.pivot.revenue account.analytic.line pivot New
timesheet_view_search account.analytic.line.search account.analytic.line xpath Inherits hr_timesheet.hr_timesheet_line_search
view_order_form_inherit_sale_timesheet sale.order.form.sale.timesheet sale.order data Inherits sale.view_order_form
view_product_timesheet_form product.template.timesheet.form product.template xpath Inherits sale.product_template_form_view_invoice_policy
view_sale_service_inherit_form2 sale.service.form.view.inherit project.task field Inherits project.view_task_form2
Models touched (15)

New fields (2)
  • timesheet_invoice_id Many2one → account.move
    copy=False help='Invoice created from the timesheet' readonly=True string='Invoice' args: 'account.move'
  • timesheet_invoice_type Selection
    compute='_compute_timesheet_invoice_type' compute_sudo=True readonly=True store=True string='Billable Type' args: [('billable_time', 'Billed on Timesheets'), ('billable_fixed', 'Billed at a Fixed price'), ('non_billable', 'Non Billable Tasks'), ('non_billable_project', 'No task found')]
Public methods (1)
  • write(self, values)

New fields (2)
  • timesheet_count Integer
    compute='_compute_timesheet_count' args: 'Number of timesheets'
  • timesheet_ids One2many → account.analytic.line
    copy=False readonly=True string='Timesheets' args: 'account.analytic.line', 'timesheet_invoice_id'
Public methods (1)
  • action_view_timesheet(self)

New fields (0)

No new fields.

Public methods (1)
  • create(self, vals_list)
    @api.model_create_multi

New fields (0)

No new fields.

Public methods (1)
  • write(self, values)

New fields (5)
  • project_id Many2one → project.project
    company_dependent=True domain=[('billable_type', '=', 'no')] help='Select a non billable project on which tasks can be created. This setting must be set for each company.' args: 'project.project', 'Project'
  • project_template_id Many2one → project.project
    company_dependent=True copy=True domain=[('billable_type', '=', 'no')] help='Select a non billable project to be the skeleton of the new created project when selling the current product. Its stages and tasks will be duplicated.' args: 'project.project', 'Project Template'
  • service_policy Selection
    compute='_compute_service_policy' inverse='_inverse_service_policy' string='Service Invoicing Policy' args: [('ordered_timesheet', 'Ordered quantities'), ('delivered_timesheet', 'Timesheets on tasks'), ('delivered_manual', 'Milestones (manually set quantities on order)')]
  • service_tracking Selection
    default='no' help="On Sales order confirmation, this product can generate a project and/or task. From those, you can track the service you are selling.\n 'In sale order's project': Will use the sale order's configured project if defined or fallback to creating a new project based on the selected template." string='Service Tracking' args: [('no', "Don't create task"), ('task_global_project', 'Create a task in an existing project'), ('task_in_project', "Create a task in sales order's project"), ('project_only', 'Create a new project but no task')]
  • service_type Selection
    selection_add=[('timesheet', 'Timesheets on project (one fare per SO/Project)')]
Public methods (1)
  • write(self, values)

New fields (4)
  • amount_to_invoice Monetary
    compute='_compute_amount_to_invoice' currency_field='currency_id' help='Total amount to invoice on the sales order, including all items (services, storables, expenses, ...)' args: 'Amount to invoice'
  • currency_id Many2one
    readonly=True related='sale_order_id.currency_id'
  • project_id Many2one → project.project
    help='Project to make billable' required=True args: 'project.project', 'Project'
  • sale_order_id Many2one → sale.order
    required=True string='Choose the Sales Order to invoice' args: 'sale.order'
Public methods (2)
  • action_create_invoice(self)
  • default_get(self, fields)
    @api.model

New fields (8)
  • billable_type Selection
    default='project_rate' help='* At Project Rate: All timesheets on the project will be billed at the same rate\n* At Employee Rate: Timesheets will be billed at a rate defined at employee level' required=True string='Billing Type' args: [('project_rate', 'At Project Rate'), ('employee_rate', 'At Employee Rate')]
  • company_id Many2one
    related='project_id.company_id'
  • currency_id Many2one → res.currency
    readonly=False related='product_id.currency_id' string='Currency' args: 'res.currency'
  • line_ids One2many → project.create.sale.order.line
    string='Lines' args: 'project.create.sale.order.line', 'wizard_id'
  • partner_id Many2one → res.partner
    help='Customer of the sales order' required=True string='Customer' args: 'res.partner'
  • price_unit Float
    help='Unit price of the sales order item.' args: 'Unit Price'
  • product_id Many2one → product.product
    domain=[('type', '=', 'service'), ('invoice_policy', '=', 'delivery'), ('service_type', '=', 'timesheet')] help='Product of the sales order item. Must be a service invoiced based on timesheets on tasks.' string='Service' args: 'product.product'
  • project_id Many2one → project.project
    domain=[('sale_line_id', '=', False)] help='Project for which we are creating a sales order' required=True args: 'project.project', 'Project'
Public methods (2)
  • action_create_sale_order(self)
  • default_get(self, fields)
    @api.model

New fields (5)
  • currency_id Many2one → res.currency
    readonly=False related='product_id.currency_id' string='Currency' args: 'res.currency'
  • employee_id Many2one → hr.employee
    help='Employee that has timesheets on the project.' required=True string='Employee' args: 'hr.employee'
  • price_unit Float
    default=1.0 help='Unit price of the sales order item.' args: 'Unit Price'
  • product_id Many2one → product.product
    domain=[('type', '=', 'service'), ('invoice_policy', '=', 'delivery'), ('service_type', '=', 'timesheet')] help='Product of the sales order item. Must be a service invoiced based on timesheets on tasks.' required=True string='Service' args: 'product.product'
  • wizard_id Many2one → project.create.sale.order
    required=True args: 'project.create.sale.order'
Public methods (0)

No public methods.

New fields (17)
  • amount_untaxed_invoiced Float
    digits=(16, 2) group_operator='sum' readonly=True args: 'Untaxed Amount Invoiced'
  • amount_untaxed_to_invoice Float
    digits=(16, 2) group_operator='sum' readonly=True args: 'Untaxed Amount To Invoice'
  • analytic_account_id Many2one → account.analytic.account
    readonly=True string='Analytic Account' args: 'account.analytic.account'
  • company_id Many2one → res.company
    readonly=True string='Project Company' args: 'res.company'
  • currency_id Many2one → res.currency
    readonly=True string='Project Currency' args: 'res.currency'
  • expense_amount_untaxed_invoiced Float
    digits=(16, 2) group_operator='sum' readonly=True args: 'Untaxed Re-invoiced Amount'
  • expense_amount_untaxed_to_invoice Float
    digits=(16, 2) group_operator='sum' readonly=True args: 'Untaxed Amount to Re-invoice'
  • expense_cost Float
    digits=(16, 2) group_operator='sum' readonly=True args: 'Other Cost'
  • order_confirmation_date Datetime
    readonly=True args: 'Sales Order Confirmation Date'
  • partner_id Many2one → res.partner
    readonly=True string='Customer' args: 'res.partner'
  • product_id Many2one → product.product
    readonly=True string='Product' args: 'product.product'
  • project_id Many2one → project.project
    readonly=True string='Project' args: 'project.project'
  • sale_line_id Many2one → sale.order.line
    readonly=True string='Sale Order Line' args: 'sale.order.line'
  • sale_order_id Many2one → sale.order
    readonly=True string='Sale Order' args: 'sale.order'
  • timesheet_cost Float
    digits=(16, 2) group_operator='sum' readonly=True args: 'Timesheet Cost'
  • timesheet_unit_amount Float
    digits=(16, 2) group_operator='sum' readonly=True args: 'Timesheet Unit Amount'
  • user_id Many2one → res.users
    readonly=True string='Project Manager' args: 'res.users'
Public methods (1)
  • init(self)

New fields (0)

No new fields.

Public methods (0)

No public methods.

New fields (4)
  • billable_type Selection
    compute='_compute_billable_type' compute_sudo=True help='At which rate timesheets will be billed:\n - At task rate: each time spend on a task is billed at task rate.\n - At employee rate: each employee log time billed at his rate.\n - No Billable: track time without invoicing it' store=True string='Billable Type' args: [('task_rate', 'At Task Rate'), ('employee_rate', 'At Employee Rate'), ('no', 'No Billable')]
  • sale_line_employee_ids One2many → project.sale.line.employee.map
    copy=False help="Employee/Sale Order Item Mapping:\n Defines to which sales order item an employee's timesheet entry will be linked.By extension, it defines the rate at which an employee's time on the project is billed." args: 'project.sale.line.employee.map', 'project_id', 'Sale line/Employee map'
  • sale_line_id Many2one → sale.order.line
    copy=False domain="[('is_expense', '=', False), ('order_id', '=', sale_order_id), ('state', 'in', ['sale', 'done']), '|', ('company_id', '=', False), ('company_id', '=', company_id)]" help="Sales order item to which the project is linked. If an employee timesheets on a task that does not have a sale order item defines, and if this employee is not in the 'Employee/Sales Order Item Mapping' of the project, the timesheet entry will be linked to the sales order item defined on the project." args: 'sale.order.line', 'Sales Order Item'
  • sale_order_id Many2one → sale.order
    copy=False domain="[('partner_id', '=', partner_id)]" help='Sales order to which the project is linked.' readonly=True args: 'sale.order', 'Sales Order'
Public methods (3)
  • action_make_billable(self)
  • action_view_timesheet(self)
  • action_view_timesheet_plan(self)

New fields (4)
  • employee_id Many2one → hr.employee
    required=True args: 'hr.employee', 'Employee'
  • price_unit Float
    readonly=True related='sale_line_id.price_unit'
  • project_id Many2one → project.project
    default=_default_project_id domain=[('billable_type', '!=', 'no')] required=True args: 'project.project', 'Project'
  • sale_line_id Many2one → sale.order.line
    domain=[('is_service', '=', True)] required=True args: 'sale.order.line', 'Sale Order Item'
Public methods (0)

No public methods.

New fields (4)
  • billable_type Selection
    compute='_compute_billable_type' compute_sudo=True store=True string='Billable Type' args: [('task_rate', 'At Task Rate'), ('employee_rate', 'At Employee Rate'), ('no', 'No Billable')]
  • is_project_map_empty Boolean
    compute='_compute_is_project_map_empty' args: 'Is Project map empty'
  • sale_line_id Many2one → sale.order.line
    copy=False default=_default_sale_line_id domain="[('is_service', '=', True), ('order_partner_id', '=', partner_id), ('is_expense', '=', False), ('state', 'in', ['sale', 'done'])]" help="Sales order item to which the task is linked. If an employee timesheets on a this task, and if this employee is not in the 'Employee/Sales Order Item Mapping' of the project, the timesheet entry will be linked to this sales order item." args: 'sale.order.line', 'Sales Order Item'
  • sale_order_id Many2one → sale.order
    compute='_compute_sale_order_id' help='Sales order to which the task is linked.' readonly=False store=True args: 'sale.order', 'Sales Order'
Public methods (4)
  • action_view_so(self)
  • rating_get_partner_id(self)
  • unlink(self)
  • write(self, values)

New fields (9)
  • project_id Many2one → project.project
    domain="[('billable_type', 'in', ('no', 'task_rate')), ('analytic_account_id', '!=', False), ('company_id', '=', company_id)]" help='Select a non billable project on which tasks can be created.' readonly=True states={'draft': [('readonly', False)], 'sent': [('readonly', False)]} args: 'project.project', 'Project'
  • project_ids Many2many → project.project
    compute='_compute_project_ids' copy=False groups='project.group_project_user' help='Projects used in this sales order.' string='Projects' args: 'project.project'
  • tasks_count Integer
    compute='_compute_tasks_ids' groups='project.group_project_user' string='Tasks'
  • tasks_ids Many2many → project.task
    compute='_compute_tasks_ids' string='Tasks associated to this sale' args: 'project.task'
  • timesheet_count Float
    compute='_compute_timesheet_ids' groups='hr_timesheet.group_hr_timesheet_user' string='Timesheet activities'
  • timesheet_encode_uom_id Many2one → uom.uom
    related='company_id.timesheet_encode_uom_id' args: 'uom.uom'
  • timesheet_ids Many2many → account.analytic.line
    compute='_compute_timesheet_ids' string='Timesheet activities associated to this sale' args: 'account.analytic.line'
  • timesheet_total_duration Float
    compute='_compute_timesheet_total_duration' help='Total recorded duration, expressed in the encoding UoM' args: 'Timesheet Total Duration'
  • visible_project Boolean
    compute='_compute_visible_project' readonly=True args: 'Display project'
Public methods (3)
  • action_view_project_ids(self)
  • action_view_task(self)
  • action_view_timesheet(self)

New fields (5)
  • analytic_line_ids One2many
    domain=[('project_id', '=', False)]
  • is_service Boolean
    compute='_compute_is_service' compute_sudo=True help='Sales Order item should generate a task and/or a project, depending on the product settings.' store=True args: 'Is a Service'
  • project_id Many2one → project.project
    copy=False help='Project generated by the sales order item' index=True args: 'project.project', 'Generated Project'
  • qty_delivered_method Selection
    selection_add=[('timesheet', 'Timesheets')]
  • task_id Many2one → project.task
    copy=False help='Task generated by the sales order item' index=True args: 'project.task', 'Generated Task'
Public methods (2)
  • create(self, vals_list)
    @api.model_create_multi
  • write(self, values)
REPOSITORY
REPOSITORYodoo/odoo
GIT
GIThttps://github.com/odoo/odoo.git
GIT FOLDER
GIT FOLDERhttps://github.com/odoo/odoo/tree/12.0/sale_timesheet
VERSION
VERSION 0.1.0
CATEGORY
CATEGORYHidden
LICENSE
LICENSELGPL-3
APPLICATION
APPLICATIONNo
AUTO-INSTALLABLE
AUTO-INSTALLABLEYes
AUTHORS
AUTHORS
MAINTAINERS
MAINTAINERS
COMMITTERS
COMMITTERSRaphael Collet, Odoo Translation Bot, Martin Trigaux, Fabien Pinckaers, Thibault Delavallée, Christophe Simonis, Xavier Morel, Goffin Simon, Mohammed Shekha, Simon Lejeune, Nicolas Martinelli, qdp-odoo, Géry Debongnie, Aaron Bohy, GitHub, Yannick Tivisse, Alexandre Kühn, qsm-odoo, Toufik Benjaa, jem-odoo, Adrien Dieudonne, RomainLibert, Christophe Monniez, len-odoo, Jorge Pinna Puissant, Nans Lefebvre, Adrian Torres, XavierDo, Julien Castiaux, Xavier-Do, Andrea Grazioso (agr-odoo), Fanny He, Sébastien Theys, Anh Thao Pham (pta), Ivan Yelizariev, LofbergValentin, qho, tafaRU
WEBSITE
WEBSITE
LAST TRACKING UPDATE
LAST TRACKING UPDATE2026-07-05 23:57:01
ODOO DEPENDENCIES
ODOO DEPENDENCIES odoo/odoo:
    - sale_management
    - sale
    - sales_team
    - base
    - mail
    - base_setup
    - web
    - bus
    - web_tour
    - payment
    - account
    - product
    - decimal_precision
    - uom
    - analytic
    - portal
    - http_routing
    - digest
    - hr_timesheet
    - hr
    - resource
    - project
    - rating
PYTHON DEPENDENCIES
PYTHON DEPENDENCIES Not have
SYSTEM DEPENDENCIES
SYSTEM DEPENDENCIES Not have
DESCRIPTION
DESCRIPTION
Allows to sell timesheets in your sales order
=============================================

This module set the right product on all timesheet lines
according to the order/contract you work on. This allows to
have real delivered quantities in sales orders.

Code Analysis

Views touched (24)
XML IDNameModelTypeStatus
account_invoice_view_form_inherit_sale_timesheet account.invoice.form.inherit.timesheet account.invoice xpath Inherits account.invoice_form
assets_backend sale timesheet assets ir.ui.view qweb Inherits web.assets_backend
product_template_view_search_sale_timesheet product.template.search.timesheet product.template xpath Inherits product.product_template_search_view
project_create_invoice_view_form project.create.invoice.view.form project.create.invoice form New
project_create_sale_order_view_form project.create.sale.order.wizard.form project.create.sale.order form New
project_profitability_report_view_graph project.profitability.report.graph project.profitability.report graph New
project_profitability_report_view_pivot project.profitability.report.pivot project.profitability.report pivot New
project_profitability_report_view_search project.profitability.report.search project.profitability.report search New
project_project_view_form project.project.form.inherit project.project xpath Inherits project.edit_project
project_project_view_kanban_inherit_sale_timesheet project.project.kanban.inherit.sale.timesheet project.project xpath Inherits hr_timesheet.view_project_kanban_inherited
project_project_view_search project.project.search.inherit project.project xpath Inherits project.view_project_project_filter
project_task_assign_so_line_view_form project.task.assign.sale.wizard.form project.task.assign.sale form New
project_task_view_form_inherit_sale_line_editable project.task.form.inherit.sale.line.editable.salesman project.task xpath Inherits view_sale_service_inherit_form2
project_task_view_form_inherit_sale_timesheet project.task.form.inherit.timesheet project.task xpath Inherits hr_timesheet.view_task_form2_inherited
project_task_view_form_sale_order project.task.form.inherit.sale.order project.task xpath Inherits project.view_task_form2
project_task_view_search project.task.search.inherit project.task xpath Inherits project.view_task_search_form
qunit_suite timesheet plan tests ir.ui.view qweb Inherits web.qunit_suite
res_config_settings_view_form res.config.settings.view.form.inherit.sale.timesheet res.config.settings xpath Inherits hr_timesheet.res_config_settings_view_form
timesheet_plan Timesheet Plan ir.ui.view qweb New
timesheet_view_pivot_revenue account.analytic.line.pivot.revenue account.analytic.line pivot New
timesheet_view_search account.analytic.line.search account.analytic.line xpath Inherits hr_timesheet.hr_timesheet_line_search
view_order_form_inherit_sale_timesheet sale.order.form.sale.timesheet sale.order data Inherits sale.view_order_form
view_product_timesheet_form product.template.timesheet.form product.template xpath Inherits sale.product_template_form_view_invoice_policy
view_sale_service_inherit_form2 sale.service.form.view.inherit project.task field Inherits project.view_task_form2
Models touched (15)

New fields (2)
  • timesheet_invoice_id Many2one → account.invoice
    copy=False help='Invoice created from the timesheet' readonly=True string='Invoice' args: 'account.invoice'
  • timesheet_invoice_type Selection
    compute='_compute_timesheet_invoice_type' compute_sudo=True readonly=True store=True string='Billable Type' args: [('billable_time', 'Billable Time'), ('billable_fixed', 'Billable Fixed'), ('non_billable', 'Non Billable'), ('non_billable_project', 'No task found')]
Public methods (1)
  • write(self, values)
    @api.multi

New fields (2)
  • timesheet_count Integer
    compute='_compute_timesheet_count' args: 'Number of timesheets'
  • timesheet_ids One2many → account.analytic.line
    copy=False readonly=True string='Timesheets' args: 'account.analytic.line', 'timesheet_invoice_id'
Public methods (1)
  • action_view_timesheet(self)

New fields (0)

No new fields.

Public methods (1)
  • create(self, values)
    @api.model
    Link the timesheet from the SO lines to the corresponding draft invoice. NOTE: only the timesheets linked to an Sale Line with a product invoiced on delivered quantity are concerned, since in ordered quantity, the timesheet quantity is not invoiced, but is simply to compute the delivered one (for reporting).

New fields (0)

No new fields.

Public methods (0)

No public methods.

New fields (5)
  • project_id Many2one → project.project
    company_dependent=True domain=[('billable_type', '=', 'no')] help='Select a non billable project on which tasks can be created. This setting must be set for each company.' args: 'project.project', 'Project'
  • project_template_id Many2one → project.project
    company_dependent=True copy=True domain=[('billable_type', '=', 'no')] help='Select a non billable project to be the skeleton of the new created project when selling the current product. Its stages and tasks will be duplicated.' args: 'project.project', 'Project Template'
  • service_policy Selection
    compute='_compute_service_policy' inverse='_inverse_service_policy' string='Service Invoicing Policy' args: [('ordered_timesheet', 'Ordered quantities'), ('delivered_timesheet', 'Timesheets on tasks'), ('delivered_manual', 'Milestones (manually set quantities on order)')]
  • service_tracking Selection
    default='no' help='On Sales order confirmation, this product can generate a project and/or task. From those, you can track the service you are selling.' string='Service Tracking' args: [('no', "Don't create task"), ('task_global_project', 'Create a task in an existing project'), ('task_new_project', 'Create a task in a new project'), ('project_only', 'Create a new project but no task')]
  • service_type Selection
    selection_add=[('timesheet', 'Timesheets on project (one fare per SO/Project)')]
Public methods (0)

No public methods.

New fields (4)
  • amount_to_invoice Monetary
    compute='_compute_amount_to_invoice' currency_field='currency_id' help='Total amount to invoice on the sales order, including all items (services, storables, expenses, ...)' args: 'Amount to invoice'
  • currency_id Many2one
    readonly=True related='sale_order_id.currency_id'
  • project_id Many2one → project.project
    help='Project to make billable' required=True args: 'project.project', 'Project'
  • sale_order_id Many2one → sale.order
    required=True string='Choose the Sales Order to invoice' args: 'sale.order'
Public methods (2)
  • action_create_invoice(self)
    @api.multi
  • default_get(self, fields)
    @api.model

New fields (7)
  • billable_type Selection
    default='project_rate' help='* At Project Rate: All timesheets on the project will be billed at the same rate\n* At Employee Rate: Timesheets will be billed at a rate defined at employee level' required=True string='Billing Type' args: [('project_rate', 'At Project Rate'), ('employee_rate', 'At Employee Rate')]
  • currency_id Many2one → res.currency
    readonly=False related='product_id.currency_id' string='Currency' args: 'res.currency'
  • line_ids One2many → project.create.sale.order.line
    string='Lines' args: 'project.create.sale.order.line', 'wizard_id'
  • partner_id Many2one → res.partner
    domain=[('customer', '=', True)] help='Customer of the sales order' required=True string='Customer' args: 'res.partner'
  • price_unit Float
    help='Unit price of the sales order item.' args: 'Unit Price'
  • product_id Many2one → product.product
    domain=[('type', '=', 'service'), ('invoice_policy', '=', 'delivery'), ('service_type', '=', 'timesheet')] help='Product of the sales order item. Must be a service invoiced based on timesheets on tasks.' string='Service' args: 'product.product'
  • project_id Many2one → project.project
    domain=[('sale_line_id', '=', False)] help='Project for which we are creating a sales order' required=True args: 'project.project', 'Project'
Public methods (2)
  • action_create_sale_order(self)
    @api.multi
  • default_get(self, fields)
    @api.model

New fields (5)
  • currency_id Many2one → res.currency
    readonly=False related='product_id.currency_id' string='Currency' args: 'res.currency'
  • employee_id Many2one → hr.employee
    help='Employee that has timesheets on the project.' required=True string='Employee' args: 'hr.employee'
  • price_unit Float
    default=1.0 help='Unit price of the sales order item.' args: 'Unit Price'
  • product_id Many2one → product.product
    domain=[('type', '=', 'service'), ('invoice_policy', '=', 'delivery'), ('service_type', '=', 'timesheet')] help='Product of the sales order item. Must be a service invoiced based on timesheets on tasks.' required=True string='Service' args: 'product.product'
  • wizard_id Many2one → project.create.sale.order
    required=True args: 'project.create.sale.order'
Public methods (0)

No public methods.

New fields (17)
  • amount_untaxed_invoiced Float
    digits=(16, 2) group_operator='sum' readonly=True args: 'Untaxed Amount Invoiced'
  • amount_untaxed_to_invoice Float
    digits=(16, 2) group_operator='sum' readonly=True args: 'Untaxed Amount To Invoice'
  • analytic_account_id Many2one → account.analytic.account
    readonly=True string='Analytic Account' args: 'account.analytic.account'
  • company_id Many2one → res.company
    readonly=True string='Project Company' args: 'res.company'
  • currency_id Many2one → res.currency
    readonly=True string='Project Currency' args: 'res.currency'
  • expense_amount_untaxed_invoiced Float
    digits=(16, 2) group_operator='sum' readonly=True args: 'Untaxed Re-invoiced Amount'
  • expense_amount_untaxed_to_invoice Float
    digits=(16, 2) group_operator='sum' readonly=True args: 'Untaxed Amount to Re-invoiced'
  • expense_cost Float
    digits=(16, 2) group_operator='sum' readonly=True args: 'Other Cost'
  • order_confirmation_date Datetime
    readonly=True args: 'Sales Order Confirmation Date'
  • partner_id Many2one → res.partner
    readonly=True string='Customer' args: 'res.partner'
  • product_id Many2one → product.product
    readonly=True string='Product' args: 'product.product'
  • project_id Many2one → project.project
    readonly=True string='Project' args: 'project.project'
  • sale_line_id Many2one → sale.order.line
    readonly=True string='Sale Order Line' args: 'sale.order.line'
  • sale_order_id Many2one → sale.order
    readonly=True string='Sale Order' args: 'sale.order'
  • timesheet_cost Float
    digits=(16, 2) group_operator='sum' readonly=True args: 'Timesheet Cost'
  • timesheet_unit_amount Float
    digits=(16, 2) group_operator='sum' readonly=True args: 'Timesheet Unit Amount'
  • user_id Many2one → res.users
    readonly=True string='Project Manager' args: 'res.users'
Public methods (1)
  • init(self)

New fields (4)
  • billable_type Selection
    compute='_compute_billable_type' compute_sudo=True help='Billable type implies:\n - At task rate: each time spend on a task is billed at task rate.\n - At employee rate: each employee log time billed at his rate.\n - No Billable: track time without invoicing it' store=True string='Billable Type' args: [('task_rate', 'At Task Rate'), ('employee_rate', 'At Employee Rate'), ('no', 'No Billable')]
  • sale_line_employee_ids One2many → project.sale.line.employee.map
    copy=False args: 'project.sale.line.employee.map', 'project_id', 'Sale line/Employee map'
  • sale_line_id Many2one → sale.order.line
    copy=False domain="[('is_expense', '=', False), ('order_id', '=', sale_order_id), ('state', 'in', ['sale', 'done'])]" help='Sale order line from which the project has been created. Used for tracability.' args: 'sale.order.line', 'Sales Order Item'
  • sale_order_id Many2one → sale.order
    copy=False domain="[('partner_id', '=', partner_id)]" readonly=True args: 'sale.order', 'Sales Order'
Public methods (3)
  • action_make_billable(self)
    @api.multi
  • action_view_timesheet(self)
    @api.multi
  • action_view_timesheet_plan(self)
    @api.multi

New fields (4)
  • employee_id Many2one → hr.employee
    required=True args: 'hr.employee', 'Employee'
  • price_unit Float
    readonly=True related='sale_line_id.price_unit'
  • project_id Many2one → project.project
    default=_default_project_id domain=[('billable_type', '!=', 'no')] required=True args: 'project.project', 'Project'
  • sale_line_id Many2one → sale.order.line
    domain=[('is_service', '=', True)] required=True args: 'sale.order.line', 'Sale Order Item'
Public methods (0)

No public methods.

New fields (4)
  • billable_type Selection
    compute='_compute_billable_type' compute_sudo=True store=True string='Billable Type' args: [('task_rate', 'At Task Rate'), ('employee_rate', 'At Employee Rate'), ('no', 'No Billable')]
  • is_project_map_empty Boolean
    compute='_compute_is_project_map_empty' args: 'Is Project map empty'
  • sale_line_id Many2one → sale.order.line
    default=_default_sale_line_id domain="[('is_service', '=', True), ('order_partner_id', '=', partner_id), ('is_expense', '=', False), ('state', 'in', ['sale', 'done'])]" args: 'sale.order.line', 'Sales Order Item'
  • sale_order_id Many2one → sale.order
    compute='_compute_sale_order_id' compute_sudo=True readonly=True store=True args: 'sale.order', 'Sales Order'
Public methods (4)
  • action_view_so(self)
    @api.multi
  • rating_get_partner_id(self)
  • unlink(self)
    @api.multi
  • write(self, values)
    @api.multi

New fields (3)
  • partner_id Many2one → res.partner
    readonly=True related='sale_line_id.order_partner_id' args: 'res.partner'
  • sale_line_id Many2one → sale.order.line
    domain=[('is_service', '=', True)] help='Sale order line to link to selected tasks' args: 'sale.order.line', 'Sales Order Item'
  • task_ids Many2many → project.task
    domain=[('parent_id', '=', False)] help='Select the tasks to assign to the Sale Order Items. You can only choose task (no sub tasks).' string='Tasks' args: 'project.task', 'project_task_assign_so_line_rel', 'task_id', 'wizard_id'
Public methods (2)
  • action_assign_sale_line(self)
    @api.multi
  • default_get(self, fields)
    @api.model

New fields (5)
  • project_ids Many2many → project.project
    compute='_compute_project_ids' copy=False groups='project.group_project_user' help='Projects used in this sales order.' string='Projects' args: 'project.project'
  • tasks_count Integer
    compute='_compute_tasks_ids' groups='project.group_project_user' string='Tasks'
  • tasks_ids Many2many → project.task
    compute='_compute_tasks_ids' string='Tasks associated to this sale' args: 'project.task'
  • timesheet_count Float
    compute='_compute_timesheet_ids' groups='hr_timesheet.group_hr_timesheet_user' string='Timesheet activities'
  • timesheet_ids Many2many → account.analytic.line
    compute='_compute_timesheet_ids' string='Timesheet activities associated to this sale' args: 'account.analytic.line'
Public methods (3)
  • action_view_project_ids(self)
    @api.multi
  • action_view_task(self)
    @api.multi
  • action_view_timesheet(self)
    @api.multi

New fields (5)
  • analytic_line_ids One2many
    domain=[('project_id', '=', False)]
  • is_service Boolean
    compute='_compute_is_service' compute_sudo=True help='Sales Order item should generate a task and/or a project, depending on the product settings.' store=True args: 'Is a Service'
  • project_id Many2one → project.project
    copy=False help='Project generated by the sales order item' index=True args: 'project.project', 'Generated Project'
  • qty_delivered_method Selection
    selection_add=[('timesheet', 'Timesheets')]
  • task_id Many2one → project.task
    copy=False help='Task generated by the sales order item' index=True args: 'project.task', 'Generated Task'
Public methods (2)
  • create(self, vals_list)
    @api.model_create_multi
  • write(self, values)
    @api.multi
REPOSITORY
REPOSITORYodoo/odoo
GIT
GIThttps://github.com/odoo/odoo.git
GIT FOLDER
GIT FOLDERhttps://github.com/odoo/odoo/tree/11.0/sale_timesheet
VERSION
VERSION 0.1.0
CATEGORY
CATEGORYHidden
LICENSE
LICENSELGPL-3
APPLICATION
APPLICATIONNo
AUTO-INSTALLABLE
AUTO-INSTALLABLEYes
AUTHORS
AUTHORS
MAINTAINERS
MAINTAINERS
COMMITTERS
COMMITTERSOdoo Translation Bot, Martin Trigaux, Fabien Pinckaers, Olivier Dony, Thibault Delavallée, Christophe Simonis, Goffin Simon, Nicolas Martinelli, Nicolas Lempereur, Jérome Maes, Julien Legros, Richard Mathot, Géry Debongnie, Aaron Bohy, Jeremy Kersten, GitHub, Lucas Perais (lpe), Yannick Tivisse, qsm-odoo, Odoo Online, jem-odoo, fwi-odoo, tbe-odoo, Adrien Dieudonne, Christophe Monniez, Nans Lefebvre, Denis Vermylen, amoyaux, Julien Castiaux
WEBSITE
WEBSITEhttps://www.odoo.com/page/warehouse
LAST TRACKING UPDATE
LAST TRACKING UPDATE2026-07-05 23:53:42
ODOO DEPENDENCIES
ODOO DEPENDENCIES odoo/odoo:
    - sale_management
    - sale
    - sales_team
    - base
    - mail
    - base_setup
    - web
    - bus
    - web_tour
    - account
    - product
    - decimal_precision
    - analytic
    - web_planner
    - portal
    - http_routing
    - account_invoicing
    - hr_timesheet
    - hr
    - resource
    - project
PYTHON DEPENDENCIES
PYTHON DEPENDENCIES Not have
SYSTEM DEPENDENCIES
SYSTEM DEPENDENCIES Not have
DESCRIPTION
DESCRIPTION
Allows to sell timesheets in your sales order
=============================================

This module set the right product on all timesheet lines
according to the order/contract you work on. This allows to
have real delivered quantities in sales orders.

Code Analysis

Views touched (13)
XML IDNameModelTypeStatus
account_invoice_view_form_inherit_sale_timesheet account.invoice.form.inherit.timesheet account.invoice xpath Inherits account.invoice_form
assets_backend sale timesheet assets ir.ui.view qweb Inherits web.assets_backend
hr_timesheet_employee_extd_form hr.timesheet.employee.extd_form hr.employee xpath Inherits hr_timesheet.hr_employee_view_form_inherit_timesheet
product_template_search_view_sale_timesheet product.template.search.timesheet product.template xpath Inherits product.product_template_search_view
project_project_view_kanban_inherit_sale_timesheet project.project.kanban.inherit.sale.timesheet project.project xpath Inherits hr_timesheet.view_project_kanban_inherited
project_task_view_form_inherit_sale_timesheet project.task.form.inherit.timesheet project.task xpath Inherits hr_timesheet.view_task_form2_inherited
res_config_settings_view_form res.config.settings.view.form.inherit.sale.timesheet res.config.settings xpath Inherits hr_timesheet.res_config_settings_view_form
timesheet_plan Timesheet Plan ir.ui.view qweb New
timesheet_view_pivot_revenue account.analytic.line.pivot.revenue account.analytic.line pivot New
timesheet_view_search account.analytic.line.search account.analytic.line xpath Inherits hr_timesheet.hr_timesheet_line_search
view_order_form_inherit_sale_timesheet sale.order.form.sale.timesheet sale.order data Inherits sale.view_order_form
view_product_timesheet_form product.template.timesheet.form product.template xpath Inherits sale.product_template_form_view_invoice_policy
view_sale_service_inherit_form2 sale.service.form.view.inherit project.task xpath Inherits project.view_task_form2
Models touched (8)

New fields (3)
  • timesheet_invoice_id Many2one → account.invoice
    copy=False help='Invoice created from the timesheet' readonly=True string='Invoice' args: 'account.invoice'
  • timesheet_invoice_type Selection
    copy=False readonly=True string='Billable Type' args: [('billable_time', 'Billable Time'), ('billable_fixed', 'Billable Fixed'), ('non_billable', 'Non Billable'), ('non_billable_project', 'No task found')]
  • timesheet_revenue Monetary
    copy=False currency_field='company_currency_id' default=0.0 readonly=True args: 'Revenue'
Public methods (2)
  • create(self, values)
    @api.model
  • write(self, values)
    @api.multi

New fields (2)
  • timesheet_count Integer
    compute='_compute_timesheet_count' args: 'Number of timesheets'
  • timesheet_ids One2many → account.analytic.line
    copy=False readonly=True string='Timesheets' args: 'account.analytic.line', 'timesheet_invoice_id'
Public methods (2)
  • action_view_timesheet(self)
  • invoice_validate(self)
    @api.multi

New fields (2)
  • currency_id Many2one → res.currency
    readonly=True related='company_id.currency_id' args: 'res.currency'
  • timesheet_cost Monetary
    currency_field='currency_id' default=0.0 args: 'Timesheet Cost'
Public methods (0)

No public methods.

New fields (4)
  • project_id Many2one → project.project
    company_dependent=True domain=[('sale_line_id', '=', False)] help='Select a non billable project on which tasks can be created. This setting must be set for each company.' args: 'project.project', 'Project'
  • service_policy Selection
    compute='_compute_service_policy' inverse='_inverse_service_policy' string='Invoice based on' args: [('ordered_timesheet', 'Ordered quantities'), ('delivered_timesheet', 'Timesheets on tasks'), ('delivered_manual', 'Milestones (manually set quantities on order)')]
  • service_tracking Selection
    default='no' help='On Sales order confirmation, this product can generate a project and/or task. From those, you can track the service you are selling.' string='Service Tracking' args: [('no', "Don't create task"), ('task_global_project', 'Create a task in an existing project'), ('task_new_project', 'Create a task in a new project'), ('project_only', 'Create a new project but no task')]
  • service_type Selection
    selection_add=[('timesheet', 'Timesheets on project (one fare per SO/Project)')]
Public methods (0)

No public methods.

New fields (1)
  • sale_line_id Many2one → sale.order.line
    help='Sale order line from which the project has been created. Used for tracability.' readonly=True args: 'sale.order.line', 'Sales Order Line'
Public methods (2)
  • action_view_timesheet(self)
    @api.multi
  • action_view_timesheet_plan(self)
    @api.multi

New fields (1)
  • sale_line_id Many2one → sale.order.line
    default=_default_sale_line_id domain="[('is_service', '=', True), ('order_partner_id', '=', partner_id)]" args: 'sale.order.line', 'Sales Order Item'
Public methods (5)
  • action_view_so(self)
    @api.multi
  • create(self, values)
    @api.model
  • onchange_parent_id(self)
    @api.onchange('parent_id')
  • unlink(self)
    @api.multi
  • write(self, values)
    @api.multi

New fields (6)
  • project_ids Many2many → project.project
    compute='_compute_project_ids' copy=False groups='project.group_project_user' help='Projects used in this sales order.' string='Projects' args: 'project.project'
  • project_project_id Many2one → project.project
    compute='_compute_project_project_id' string='Project associated to this sale' args: 'project.project'
  • tasks_count Integer
    compute='_compute_tasks_ids' groups='project.group_project_user' string='Tasks'
  • tasks_ids Many2many → project.task
    compute='_compute_tasks_ids' string='Tasks associated to this sale' args: 'project.task'
  • timesheet_count Float
    compute='_compute_timesheet_ids' groups='hr_timesheet.group_hr_timesheet_user' string='Timesheet activities'
  • timesheet_ids Many2many → account.analytic.line
    compute='_compute_timesheet_ids' string='Timesheet activities associated to this sale' args: 'account.analytic.line'
Public methods (4)
  • action_confirm(self)
    @api.multi
    On SO confirmation, some lines should generate a task or a project.
  • action_view_project_ids(self)
    @api.multi
  • action_view_task(self)
    @api.multi
  • action_view_timesheet(self)
    @api.multi

New fields (2)
  • is_service Boolean
    compute='_compute_is_service' compute_sudo=True help='Sales Order item should generate a task and/or a project, depending on the product settings.' store=True args: 'Is a Service'
  • task_id Many2one → project.task
    help='Task generated by the sales order item' index=True args: 'project.task', 'Task'
Public methods (1)
  • create(self, values)
    @api.model
REPOSITORY
REPOSITORYodoo/odoo
GIT
GIThttps://github.com/odoo/odoo.git
GIT FOLDER
GIT FOLDERhttps://github.com/odoo/odoo/tree/10.0/sale_timesheet
VERSION
VERSION 0.1.0
CATEGORY
CATEGORYHidden
LICENSE
LICENSELGPL-3
APPLICATION
APPLICATIONNo
AUTO-INSTALLABLE
AUTO-INSTALLABLEYes
AUTHORS
AUTHORS
MAINTAINERS
MAINTAINERS
COMMITTERS
COMMITTERSOdoo Translation Bot, Martin Trigaux, Fabien Pinckaers, Antony Lesuisse, Olivier Dony, Thibault Delavallée, Denis Ledoux, Christophe Simonis, David Monjoie, Goffin Simon, Nicolas Martinelli, Julien Legros, qdp-odoo, Aaron Bohy, Lucas Perais (lpe), Damien Bouvy, Yannick Tivisse, Tanguy Charlier
WEBSITE
WEBSITEhttps://www.odoo.com/page/warehouse
LAST TRACKING UPDATE
LAST TRACKING UPDATE2026-07-05 23:50:47
ODOO DEPENDENCIES
ODOO DEPENDENCIES odoo/odoo:
    - sale
    - sales_team
    - base
    - mail
    - base_setup
    - web_kanban
    - web
    - bus
    - web_tour
    - account
    - product
    - decimal_precision
    - report
    - analytic
    - web_planner
    - procurement
    - hr_timesheet
    - hr
    - resource
    - project
PYTHON DEPENDENCIES
PYTHON DEPENDENCIES Not have
SYSTEM DEPENDENCIES
SYSTEM DEPENDENCIES Not have
DESCRIPTION
DESCRIPTION
Allows to sell timesheets in your sales order
=============================================

This module set the right product on all timesheet lines
according to the order/contract you work on. This allows to
have real delivered quantities in sales orders.

Code Analysis

Views touched (5)
XML IDNameModelTypeStatus
hr_timesheet_employee_extd_form hr.timesheet.employee.extd_form hr.employee xpath Inherits hr.view_employee_form
view_order_form_inherit_sale_timesheet sale.order.form.sale.timesheet sale.order data Inherits sale.view_order_form
view_procurement_task_form procurement.procurement.form.view.inherit procurement.order field Inherits procurement.procurement_form_view
view_product_timesheet_form product.template.timesheet.form product.template xpath Inherits sale.product_template_form_view_invoice_policy
view_sale_service_inherit_form2 sale.service.form.view.inherit project.task xpath Inherits project.view_task_form2
Models touched (9)

New fields (0)

No new fields.

Public methods (2)
  • create(self, values)
    @api.model
  • write(self, values)
    @api.multi

New fields (2)
  • account_id Many2one → account.account
    string='Account' args: 'account.account'
  • timesheet_cost Float
    default=0.0 args: 'Timesheet Cost'
Public methods (0)

No public methods.

New fields (1)
  • task_id Many2one → project.task
    copy=False args: 'project.task', 'Task'
Public methods (0)

No public methods.

New fields (0)

No new fields.

Public methods (0)

No public methods.

New fields (2)
  • project_id Many2one → project.project
    company_dependent=True help='Create a task under this project on sale order validation. This setting must be set for each company.' args: 'project.project', 'Project'
  • track_service Selection
    selection_add=[('timesheet', 'Timesheets on project'), ('task', 'Create a task and track hours')]
Public methods (0)

No public methods.

New fields (2)
  • procurement_id Many2one → procurement.order
    ondelete='set null' args: 'procurement.order', 'Procurement'
  • sale_line_id Many2one → sale.order.line
    related='procurement_id.sale_line_id' store=True args: 'sale.order.line', 'Sales Order Line'
Public methods (3)
  • action_view_so(self)
    @api.multi
  • onchange_parent_id(self)
    @api.onchange('parent_id')
  • unlink(self)
    @api.multi

New fields (1)
  • project_time_mode_id Many2one → product.uom
    default=_get_default_uom_id args: 'product.uom', 'Timesheet UoM'
Public methods (0)

No public methods.

New fields (5)
  • project_project_id Many2one → project.project
    compute='_compute_project_project_id' string='Project associated to this sale' args: 'project.project'
  • tasks_count Integer
    compute='_compute_tasks_ids' string='Tasks'
  • tasks_ids Many2many → project.task
    compute='_compute_tasks_ids' string='Tasks associated to this sale' args: 'project.task'
  • timesheet_count Float
    compute='_compute_timesheet_ids' string='Timesheet activities'
  • timesheet_ids Many2many → account.analytic.line
    compute='_compute_timesheet_ids' string='Timesheet activities associated to this sale' args: 'account.analytic.line'
Public methods (4)
  • action_confirm(self)
    @api.multi
  • action_view_project_project(self)
    @api.multi
  • action_view_task(self)
    @api.multi
  • action_view_timesheet(self)
    @api.multi

New fields (0)

No new fields.

Public methods (1)
  • create(self, values)
    @api.model
REPOSITORY
REPOSITORYodoo/odoo
GIT
GIThttps://github.com/odoo/odoo.git
GIT FOLDER
GIT FOLDERhttps://github.com/odoo/odoo/tree/9.0/sale_timesheet
VERSION
VERSION 1.0
CATEGORY
CATEGORYHidden
LICENSE
LICENSELGPL-3
APPLICATION
APPLICATIONNo
AUTO-INSTALLABLE
AUTO-INSTALLABLEYes
AUTHORS
AUTHORSOpenERP SA
MAINTAINERS
MAINTAINERSOpenERP SA
COMMITTERS
COMMITTERSOdoo Translation Bot, Martin Trigaux, Olivier Dony, Denis Ledoux, Goffin Simon, Nicolas Martinelli, Damien Bouvy
WEBSITE
WEBSITEhttps://www.odoo.com/page/warehouse
LAST TRACKING UPDATE
LAST TRACKING UPDATE2026-07-05 23:48:30
ODOO DEPENDENCIES
ODOO DEPENDENCIES odoo/odoo:
    - sale
    - sales_team
    - base
    - mail
    - base_setup
    - web_kanban
    - web
    - bus
    - account
    - product
    - decimal_precision
    - report
    - analytic
    - web_tip
    - web_planner
    - procurement
    - hr_timesheet
    - hr
    - resource
    - hr_attendance
PYTHON DEPENDENCIES
PYTHON DEPENDENCIES Not have
SYSTEM DEPENDENCIES
SYSTEM DEPENDENCIES Not have
DESCRIPTION
DESCRIPTION
Allows to sell timesheets in your sales order
=============================================

This module set the right product on all timesheet lines
according to the order/contract you work on. This allows to
have real delivered quantities in sales orders.

Code Analysis

Views touched (3)
XML IDNameModelTypeStatus
hr_timesheet_employee_extd_form hr.timesheet.employee.extd_form hr.employee xpath Inherits hr.view_employee_form
view_order_form_inherit_sale_timesheet sale.order.form.sale.timesheet sale.order data Inherits sale.view_order_form
view_product_timesheet_form product.template.timesheet.form product.template xpath Inherits sale.product_template_form_view_invoice_policy
Models touched (6)

New fields (0)

No new fields.

Public methods (2)
  • create(self, values)
    @api.model
  • write(self, values)
    @api.multi

New fields (1)
  • timesheet_cost Float
    default=0.0 string='Timesheet Cost'
Public methods (0)

No public methods.

New fields (1)
  • track_service Selection
    selection_add=[('timesheet', 'Timesheets on contract')]
Public methods (1)
  • onchange_type_timesheet(self)
    @api.onchange('type', 'invoice_policy')

New fields (1)
  • project_time_mode_id Many2one → product.uom
    default=_get_uom_hours string='Timesheet UoM' args: 'product.uom'
Public methods (0)

No public methods.

New fields (2)
  • timesheet_count Float
    compute='_compute_timesheet_ids' string='Timesheet activities'
  • timesheet_ids Many2many → account.analytic.line
    compute='_compute_timesheet_ids' string='Timesheet activities associated to this sale' args: 'account.analytic.line'
Public methods (2)
  • action_confirm(self)
    @api.multi
  • action_view_timesheet(self)
    @api.multi

New fields (0)

No new fields.

Public methods (0)

No public methods.