Expenses

hr_expense
REPOSITORY
REPOSITORYodoo/odoo
GIT
GIThttps://github.com/odoo/odoo.git
GIT FOLDER
GIT FOLDERhttps://github.com/odoo/odoo/tree/19.0/hr_expense
VERSION
VERSION 2.1
CATEGORY
CATEGORYHuman Resources/Expenses
LICENSE
LICENSELGPL-3
APPLICATION
APPLICATIONYes
AUTO-INSTALLABLE
AUTO-INSTALLABLENo
AUTHORS
AUTHORSOdoo S.A.
MAINTAINERS
MAINTAINERSOdoo S.A.
COMMITTERS
COMMITTERSRaphael Collet, Odoo Translation Bot, Thibault Delavallée, Xavier Morel, Aaron Bohy, Yannick Tivisse, Pierre Masereel, Laurent Smet, Julien Castiaux, Adrien Dieudonné, Anh Thao Pham (pta), Brice bib Bartoletti, Prakash Prajapati, Tiffany Chang (tic), Gorash, Mathieu Duckerts-Antoine, Pierre Paridans, william-andre, JF Aubert, Antoine Guenet, Louis Baudoux, Renaud Thiry, jbw-odoo, Gaetan Vanden Bergh (gavb), Louis Wicket (wil), Maximilien (malb), Victor Piryns (pivi), Mahamadasif Ansari, Thomas Becquevort (thbe), Gauthier Wala (gawa), Antoine Dupuis (andu), Dylan Kiss (dyki), hupo-odoo, Julien Alardot (jual), Chong Wang (cwg), Xavier Bol (xbo), Arnaud Sibille, omra-odoo, Nicolas Viseur (vin), Paul Stroobant, maad-odoo, Seif (segn), kcv-odoo, mega, Louis Gobert, aamo-odoo, Patricia Daloze (pda), Abdelrahman Mahmoud (amah), gteboul, sabr, Saif (segn), Victor Miguel Armenta Carrillo, Mohammad Abdulmoneim (abdu), Pierre Lamotte (pila), Michaël Mattiello, Pierre Pulinckx, Bastien PIERRE, pish-odoo, Christihan Laurel [Vauxoo], Krzysztof Magusiak (krma), krip-odoo, Meet, hatr-odoo, Hadi El Yakhni, sagu-odoo, bhra-odoo, bhna-odoo, mojem, agbr-odoo, igbe, Dhwani Patel, Jeanne Delneste, malb, h4818, leclerc-leo, Amr Elkhatieb, jond-odoo, rapa, Mohamed Barakat, saurabh, smjo-odoo, Mauricio Rubio Haro, chpa-odoo, Oussema Heni, 3amo-magdy, Mathias Mathy, Léo Leclerc
WEBSITE
WEBSITEhttps://www.odoo.com/app/expenses
LAST TRACKING UPDATE
LAST TRACKING UPDATE2026-07-06 01:51:43
ODOO DEPENDENCIES
ODOO DEPENDENCIES odoo/odoo:
    - account
    - base_setup
    - base
    - web
    - onboarding
    - product
    - mail
    - bus
    - web_tour
    - html_editor
    - uom
    - analytic
    - portal
    - http_routing
    - auth_signup
    - digest
    - resource
    - hr
    - phone_validation
    - resource_mail
PYTHON DEPENDENCIES
PYTHON DEPENDENCIES Not have
SYSTEM DEPENDENCIES
SYSTEM DEPENDENCIES Not have
DESCRIPTION
DESCRIPTION
Manage expenses by Employees
============================

This application allows you to manage your employees' daily expenses. It gives you access to your employees’ fee notes and give you the right to complete and validate or refuse the notes. After validation it creates an invoice for the employee.
Employee can encode their own expenses and the validation flow puts it automatically in the accounting after validation by managers.


The whole flow is implemented as:
---------------------------------
* Draft expense
* Submitted by the employee to his manager
* Approved by his manager
* Validation by the accountant and accounting entries creation

This module also uses analytic accounting and is compatible with the invoice on timesheet module so that you are able to automatically re-invoice your customers' expenses if your work by project.
    

Code Analysis

Views touched (38)
XML IDNameModelTypeStatus
hr_department_view_kanban hr.department.kanban.inherit hr.department data Inherits hr.hr_department_view_kanban
hr_employee_search_view hr.employee.search.view hr.employee xpath Inherits hr.view_employee_filter
hr_employee_view_form_inherit_expense hr.employee.view.form.expense hr.employee xpath Inherits hr.view_employee_form
hr_expense.hr_expense_post_wizard_view Post Expenses hr.expense.post.wizard form New
hr_expense_approve_duplicate_view_form hr.expense.approve.duplicate form New
hr_expense_kanban_view hr.expense.kanban hr.expense xpath Inherits hr_expense_view_expenses_analysis_kanban
hr_expense_kanban_view_header hr.expense.kanban hr.expense xpath Inherits hr_expense_view_expenses_analysis_kanban
hr_expense_kanban_view_minimal hr.expense.kanban hr.expense xpath Inherits hr_expense_view_expenses_analysis_kanban
hr_expense_refuse_wizard_view_form hr.expense.refuse.wizard.form hr.expense.refuse.wizard form New
hr_expense_split Expense split hr.expense.split.wizard form New
hr_expense_template_refuse_reason hr_expense_template_refuse_reason ir.ui.view qweb New
hr_expense_template_register hr_expense_template_register ir.ui.view qweb New
hr_expense_template_register_no_user hr_expense_template_register_no_user ir.ui.view qweb New
hr_expense_template_submitted_expenses hr_expense_template_submitted_expenses ir.ui.view qweb New
hr_expense_view_activity hr.expense.activity hr.expense activity New
hr_expense_view_expenses_analysis_kanban hr.expense.kanban hr.expense kanban New
hr_expense_view_expenses_analysis_tree hr.expense.list hr.expense list New
hr_expense_view_form hr.expense.view.form hr.expense form New
hr_expense_view_form_without_header hr.expense.view.form hr.expense xpath Inherits hr_expense.hr_expense_view_form
hr_expense_view_graph hr.expense.graph hr.expense graph New
hr_expense_view_pivot hr.expense.pivot hr.expense pivot New
hr_expense_view_search hr.expense.view.search hr.expense search New
hr_expense_view_search_with_panel hr.expense.view.search.with.panel hr.expense xpath Inherits hr_expense_view_search
product_product_expense_categories_tree_view product.product.expense.categories.list.view product.product list New
product_product_expense_form_view product.product.expense.form product.product form New
product_product_expense_kanban_view product.product.kanban.expense product.product xpath Inherits product.product_kanban_view
product_product_expense_tree_view product.product.expense.list product.product list New
product_template_search_view_inherit_hr_expense product.template.search.view.inherit.hr_expense product.template filter Inherits product.product_template_search_view
report_expense report_expense ir.ui.view qweb New
report_expense_img report_expense_img ir.ui.view qweb New
res_config_settings_view_form res.config.settings.view.form.inherit.hr.expense res.config.settings xpath Inherits base.res_config_settings_view_form
view_employee_tree_inherit_expense hr.employee.list.expense hr.employee xpath Inherits hr.view_employee_tree
view_expenses_tree hr.expense.list hr.expense xpath Inherits hr_expense_view_expenses_analysis_tree
view_move_form_inherit_expense account.move.form.inherit account.move xpath Inherits account.view_move_form
view_move_list_expense account.move.hr.expense.list account.move list New
view_my_expenses_tree hr.expense.list hr.expense xpath Inherits hr_expense.view_expenses_tree
view_payment_form_inherit_expense account.payment.form.inherit account.payment xpath Inherits account.view_account_payment_form
view_product_hr_expense_form product.template.expense.form product.template span Inherits product.product_template_form_view
Models touched (23)

New fields (0)

No new fields.

Public methods (0)

No public methods.

New fields (1)
  • business_domain Selection
    ondelete={'expense': 'cascade'} selection_add=[('expense', 'Expense')]
Public methods (0)

No public methods.

New fields (2)
  • expense_ids One2many → hr.expense
    comodel_name='hr.expense' inverse_name='account_move_id'
  • nb_expenses Integer
    compute='_compute_nb_expenses' compute_sudo=True string='Number of Expenses'
Public methods (2)
  • action_open_expense(self)
  • button_cancel(self)

New fields (1)
  • expense_id Many2one → hr.expense
    copy=True index='btree_not_null' string='Expense' args: 'hr.expense'
Public methods (0)

No public methods.

New fields (1)
  • expense_ids One2many
    related='move_id.expense_ids'
Public methods (2)
  • action_open_expense(self)
  • write(self, vals)

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)
  • expenses_to_approve_count Integer
    compute='_compute_expenses_to_approve_count' string='Expenses to Approve'
Public methods (0)

No public methods.

New fields (2)
  • expense_manager_id Many2one → res.users
    comodel_name='res.users' compute='_compute_expense_manager' domain=_group_hr_expense_user_domain help='Select the user responsible for approving "Expenses" of this employee.\nIf empty, the approval is done by an Administrator or Approver (determined in settings/users).' readonly=False store=True string='Expense Approver'
  • filter_for_expense Boolean
    groups='hr.group_hr_user' search='_search_filter_for_expense' store=False
Public methods (0)

No public methods.

New fields (1)
  • expense_manager_id Many2one → res.users
    readonly=True args: 'res.users'
Public methods (0)

No public methods.

New fields (1)
  • filter_for_expense Boolean
    groups='hr.group_hr_user' search='_search_filter_for_expense' store=False
Public methods (0)

No public methods.

New fields (47)
  • account_id Many2one → account.account
    check_company=True comodel_name='account.account' compute='_compute_account_id' domain="[('account_type', 'not in', ('asset_receivable', 'liability_payable', 'asset_cash', 'liability_credit_card'))]" help='An expense account is expected' precompute=True readonly=False store=True string='Account'
  • account_move_id Many2one → account.move
    comodel_name='account.move' copy=False index='btree_not_null' readonly=True string='Journal Entry'
  • amount_residual Monetary
    currency_field='company_currency_id' readonly=True related='account_move_id.amount_residual' string='Amount Due'
  • approval_date Datetime
    readonly=True string='Approval Date'
  • approval_state Selection
    copy=False readonly=True selection=EXPENSE_APPROVAL_STATE
  • attachment_ids One2many → ir.attachment
    comodel_name='ir.attachment' domain=[('res_model', '=', 'hr.expense')] inverse_name='res_id' string='Attachments'
  • can_approve Boolean
    compute='_compute_can_approve' readonly=True string='Can Approve'
  • can_reset Boolean
    compute='_compute_can_reset' readonly=True string='Can Reset'
  • company_currency_id Many2one → res.currency
    comodel_name='res.currency' readonly=True related='company_id.currency_id' string='Report Company Currency'
  • company_id Many2one → res.company
    comodel_name='res.company' default=<expr> readonly=True required=True string='Company'
  • currency_id Many2one → res.currency
    comodel_name='res.currency' compute='_compute_currency_id' default=<expr> precompute=True readonly=False required=True store=True string='Currency'
  • currency_rate Float
    compute='_compute_currency_rate' digits=(16, 9) readonly=True tracking=True
  • date Date
    default=fields.Date.context_today string='Expense Date'
  • department_id Many2one → hr.department
    comodel_name='hr.department' compute='_compute_from_employee_id' copy=False store=True string='Department'
  • description Text
    string='Internal Notes'
  • duplicate_expense_ids Many2many → hr.expense
    comodel_name='hr.expense' compute='_compute_duplicate_expense_ids'
  • employee_id Many2one → hr.employee
    check_company=True comodel_name='hr.employee' compute='_compute_employee_id' default=_default_employee_id domain=[('filter_for_expense', '=', True)] index=True precompute=True readonly=False required=True store=True string='Employee' tracking=True
  • former_sheet_id Integer
    string='Former Report'
  • is_editable Boolean
    compute='_compute_is_editable' readonly=True string='Is Editable By Current User'
  • is_multiple_currency Boolean
    compute='_compute_is_multiple_currency' string='Is currency_id different from the company_currency_id'
  • journal_id Many2one → account.journal
    comodel_name='account.journal' readonly=True related='payment_method_line_id.journal_id'
  • label_currency_rate Char
    compute='_compute_currency_rate' readonly=True
  • manager_id Many2one → res.users
    comodel_name='res.users' compute='_compute_from_employee_id' copy=False domain=<expr> store=True string='Manager' tracking=True
  • message_main_attachment_checksum Char
    related='message_main_attachment_id.checksum'
  • name Char
    compute='_compute_name' copy=True precompute=True readonly=False required=True store=True string='Description'
  • nb_attachment Integer
    compute='_compute_nb_attachment' string='Number of Attachments'
  • payment_method_line_id Many2one → account.payment.method.line
    comodel_name='account.payment.method.line' compute='_compute_payment_method_line_id' domain="[('id', 'in', selectable_payment_method_line_ids)]" help='The payment method used when the expense is paid by the company.' readonly=False store=True string='Payment Method'
  • payment_mode Selection
    default='own_account' required=True selection=[('own_account', 'Employee (to reimburse)'), ('company_account', 'Company')] string='Paid By' tracking=True
  • price_unit Float
    compute='_compute_price_unit' copy=True min_display_digits='Product Price' precompute=True readonly=True required=True store=True string='Unit Price'
  • product_description Html
    compute='_compute_product_description'
  • product_has_cost Boolean
    compute='_compute_from_product'
  • product_has_tax Boolean
    compute='_compute_from_product' string='Whether tax is defined on a selected product'
  • product_id Many2one → product.product
    check_company=True comodel_name='product.product' domain=[('can_be_expensed', '=', True)] ondelete='restrict' string='Category' tracking=True
  • product_uom_id Many2one → uom.uom
    comodel_name='uom.uom' compute='_compute_uom_id' copy=True precompute=True store=True string='Unit'
  • quantity Float
    default=1 digits='Product Unit' required=True
  • same_receipt_expense_ids Many2many → hr.expense
    comodel_name='hr.expense' compute='_compute_same_receipt_expense_ids'
  • selectable_payment_method_line_ids Many2many → account.payment.method.line
    comodel_name='account.payment.method.line' compute='_compute_selectable_payment_method_line_ids' compute_sudo=True
  • split_expense_origin_id Many2one → hr.expense
    comodel_name='hr.expense' help='Original expense from a split.' string='Origin Split Expense'
  • state Selection
    compute='_compute_state' copy=False default='draft' index=True readonly=True selection=[('draft', 'Draft'), ('submitted', 'Submitted'), ('approved', 'Approved'), ('posted', 'Posted'), ('in_payment', 'In Payment'), ('paid', 'Paid'), ('refused', 'Refused')] store=True string='Status' tracking=True
  • tax_amount Monetary
    compute='_compute_tax_amount' currency_field='company_currency_id' help='Tax amount in company currency' precompute=True store=True string='Tax amount'
  • tax_amount_currency Monetary
    compute='_compute_tax_amount_currency' currency_field='currency_id' help='Tax amount in currency' precompute=True store=True string='Tax amount in Currency'
  • tax_ids Many2many → account.tax
    check_company=True column1='expense_id' column2='tax_id' comodel_name='account.tax' compute='_compute_tax_ids' domain="[('type_tax_use', '=', 'purchase')]" help='Both price-included and price-excluded taxes will behave as price-included taxes for expenses.' precompute=True readonly=False relation='expense_tax' store=True string='Included taxes'
  • total_amount Monetary
    compute='_compute_total_amount' currency_field='company_currency_id' inverse='_inverse_total_amount' precompute=True readonly=False store=True string='Total' tracking=True
  • total_amount_currency Monetary
    compute='_compute_total_amount_currency' currency_field='currency_id' precompute=True readonly=False store=True string='Total In Currency' tracking=True
  • untaxed_amount Monetary
    compute='_compute_tax_amount' currency_field='currency_id' precompute=True store=True string='Total Untaxed Amount'
  • untaxed_amount_currency Monetary
    compute='_compute_tax_amount_currency' currency_field='currency_id' precompute=True store=True string='Total Untaxed Amount In Currency'
  • vendor_id Many2one → res.partner
    comodel_name='res.partner' string='Vendor'
Public methods (19)
  • action_approve(self)
    Approve an expense, pops a wizard if a duplicated expense is found to confirm they are all valid expenses
  • action_approve_duplicates(self)
  • action_open_account_move(self)
  • action_open_split_expense(self)
  • action_pay(self)
    Register payment shortcut on the expense form view
  • action_post(self)
    Post the expense, following one of those two options: - Company-paid expenses: Create and post a payment, with an accounting entry - Employee-paid expenses: Through a wizard, create and post a receipt
  • action_refuse(self)
    Refuse an expense with a reason
  • action_reset(self)
    Reset an expense to draft state, reversing the accounting entries if needed
  • action_show_same_receipt_expense_ids(self)
  • action_split_wizard(self)
  • action_submit(self)
    Submit a draft expense to an approve, may skip to the approval step if no approver on the employee nor the expense
  • attach_document(self, **kwargs)
    When an attachment is uploaded as a receipt, set it as the main attachment.
  • create(self, vals_list)
    @api.model_create_multi
  • create_expense_from_attachments(self, attachment_ids=None, view_type='list')
    @api.model
    Create the expenses from files. :return: An action redirecting to hr.expense list view.
  • get_empty_list_help(self, help_message)
    @api.model
  • get_expense_dashboard(self)
    @api.model
  • message_new(self, msg_dict, custom_values=None)
    @api.model
  • update_activities_and_mails(self)
    Update the "Review this expense" activity with the new state of the expense, also sends mail to approver to ask them to act
  • write(self, vals)

New fields (1)
  • expense_ids Many2many → hr.expense
    readonly=True args: 'hr.expense'
Public methods (3)
  • action_approve(self)
  • action_refuse(self)
  • default_get(self, fields)
    @api.model

New fields (3)
  • accounting_date Date
    default=fields.Date.context_today help='Specify the bill date of the related vendor bill.' string='Accounting Date'
  • company_id Many2one → res.company
    comodel_name='res.company' default=<expr> readonly=True string='Company'
  • employee_journal_id Many2one → account.journal
    check_company=True comodel_name='account.journal' default=_default_journal_id domain=[('type', '=', 'purchase')] help='The journal used when the expense is paid by employee.' string='Journal'
Public methods (1)
  • action_post_entry(self)

New fields (2)
  • expense_ids Many2many → hr.expense
    comodel_name='hr.expense'
  • reason Char
    required=True string='Reason'
Public methods (2)
  • action_refuse(self)
  • default_get(self, fields)
    @api.model

New fields (15)
  • approval_date Datetime
    readonly=True string='Approval Date'
  • approval_state Selection
    copy=False readonly=True selection=EXPENSE_APPROVAL_STATE
  • company_id Many2one → res.company
    comodel_name='res.company'
  • currency_id Many2one → res.currency
    comodel_name='res.currency'
  • employee_id Many2one → hr.employee
    comodel_name='hr.employee' required=True string='Employee'
  • expense_id Many2one → hr.expense
    comodel_name='hr.expense' string='Expense'
  • manager_id Many2one → res.users
    comodel_name='res.users' domain=<expr> readonly=True string='Manager'
  • name Char
    required=True string='Description'
  • product_has_cost Boolean
    compute='_compute_from_product_id' store=True string='Is product with non zero cost selected'
  • product_has_tax Boolean
    compute='_compute_product_has_tax' string='Whether tax is defined on a selected product'
  • product_id Many2one → product.product
    check_company=True comodel_name='product.product' domain=[('can_be_expensed', '=', True)] required=True string='Product'
  • tax_amount_currency Monetary
    compute='_compute_tax_amount_currency' string='Tax amount in Currency'
  • tax_ids Many2many → account.tax
    check_company=True comodel_name='account.tax' domain="[('type_tax_use', '=', 'purchase')]"
  • total_amount_currency Monetary
    compute='_compute_from_product_id' readonly=False required=True store=True string='Total In Currency'
  • wizard_id Many2one → hr.expense.split.wizard
    comodel_name='hr.expense.split.wizard'
Public methods (1)
  • default_get(self, fields)
    @api.model

New fields (7)
  • currency_id Many2one → res.currency
    comodel_name='res.currency' related='expense_id.currency_id'
  • expense_id Many2one → hr.expense
    comodel_name='hr.expense' required=True string='Expense'
  • expense_split_line_ids One2many → hr.expense.split
    comodel_name='hr.expense.split' inverse_name='wizard_id'
  • split_possible Boolean
    compute='_compute_split_possible' help='The sum of after split shut remain the same'
  • tax_amount_currency Monetary
    compute='_compute_tax_amount_currency' currency_field='currency_id' string='Taxes'
  • total_amount_currency Monetary
    compute='_compute_total_amount_currency' currency_field='currency_id' string='Total Amount'
  • total_amount_currency_original Monetary
    currency_field='currency_id' help='Total amount of the original Expense that we are splitting' related='expense_id.total_amount_currency' string='Total amount original'
Public methods (1)
  • action_split_expense(self)

New fields (0)

No new fields.

Public methods (0)

No public methods.

New fields (0)

No new fields.

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

New fields (1)
  • standard_price_update_warning Char
    compute='_compute_standard_price_update_warning'
Public methods (1)
  • write(self, vals)

New fields (1)
  • can_be_expensed Boolean
    compute='_compute_can_be_expensed' help='Specify whether the product can be selected in an expense.' readonly=False store=True string='Expenses'
Public methods (1)
  • default_get(self, fields)
    @api.model

New fields (2)
  • company_expense_allowed_payment_method_line_ids Many2many → account.payment.method.line
    check_company=True domain="[('payment_type', '=', 'outbound'), ('journal_id', '!=', False), ('journal_id.active', '=', True)]" string='Payment methods available for expenses paid by company' args: 'account.payment.method.line'
  • expense_journal_id Many2one → account.journal
    check_company=True domain="[('type', '=', 'purchase')]" help="The company's default journal used when an employee expense is created." string='Default Expense Journal' args: 'account.journal'
Public methods (0)

No public methods.

New fields (8)
  • company_expense_allowed_payment_method_line_ids Many2many → account.payment.method.line
    check_company=True comodel_name='account.payment.method.line' readonly=False related='company_id.company_expense_allowed_payment_method_line_ids'
  • expense_journal_id Many2one → account.journal
    check_company=True domain="[('type', '=', 'purchase')]" readonly=False related='company_id.expense_journal_id' args: 'account.journal'
  • hr_expense_alias_domain_id Many2one → mail.alias.domain
    comodel_name='mail.alias.domain' compute='_compute_hr_expense_alias_domain_id' inverse='_inverse_hr_expense_alias_domain_id' readonly=False
  • hr_expense_alias_prefix Char
    compute='_compute_hr_expense_alias_prefix' readonly=False store=True args: 'Default Alias Name for Expenses'
  • hr_expense_use_mailgateway Boolean
    config_parameter='hr_expense.use_mailgateway' string='Let your employees record expenses by email'
  • module_hr_expense_extract Boolean
    string='Send bills to OCR to generate expenses'
  • module_hr_expense_stripe Boolean
    string='Link your stripe issuing account to manage company credit cards for your employees through Odoo'
  • module_hr_payroll_expense Boolean
    string='Reimburse Expenses in Payslip'
Public methods (2)
  • get_values(self)
    @api.model
  • set_values(self)
REPOSITORY
REPOSITORYodoo/odoo
GIT
GIThttps://github.com/odoo/odoo.git
GIT FOLDER
GIT FOLDERhttps://github.com/odoo/odoo/tree/18.0/hr_expense
VERSION
VERSION 2.0
CATEGORY
CATEGORYHuman Resources/Expenses
LICENSE
LICENSELGPL-3
APPLICATION
APPLICATIONYes
AUTO-INSTALLABLE
AUTO-INSTALLABLENo
AUTHORS
AUTHORS
MAINTAINERS
MAINTAINERS
COMMITTERS
COMMITTERSRaphael Collet, Odoo Translation Bot, Martin Trigaux, Thibault Delavallée, Christophe Simonis, Xavier Morel, qdp-odoo, Aaron Bohy, Damien Bouvy, Yannick Tivisse, Odoo Online, Laurent Smet, Jorge Pinna Puissant, Victor Feyens, Andrea Grazioso (agr-odoo), Sébastien Theys, Anh Thao Pham (pta), Achraf (abz), Brice bib Bartoletti, Prakash Prajapati, Tiffany Chang (tic), Gorash, Romain Estievenart, sofiagvaladze, william-andre, Víctor Martínez, Didier (did), Florian Vranckx, assellalou, Hubert Van De Walle, jbw-odoo, alsh-odoo, Louis Wicket (wil), tsm-odoo, Maximilien (malb), Victor Piryns (pivi), Yash Vaishnav, Thomas Becquevort (thbe), Gauthier Wala (gawa), Ali Alfie (alal), Pierre-Yves Dufays, Claire Bretton (clbr), Antoine Dupuis (andu), Antoine Boonen, Dylan Kiss (dyki), Julien Alardot (jual), Chong Wang (cwg), Leloup Loïc (lole), Cameron, Arnaud Sibille, Noe Antoine, omra-odoo, sesn-odoo, guva-odoo, kdeb@odoo.com, Paul Stroobant, Hesham, ravb-odoo, Mélanie, Maryam Kia, Maxime de Neuville, Sven Fuehr, Seif (segn), yosa-odoo, Ruben Gomes (rugo), Nathaniel (naja), jobl, Julien Carion (juca), kcv-odoo, moerradi, yaih-odoo, mega, Louis Gobert, aath-odoo, Louis (wil), asno-odoo, mano-odoo, Mathieu Coutant, gteboul, sabr, Pierre Pulinckx (PIPU), Victor Miguel Armenta Carrillo, Trinh Ngoc Hung, Maira Salazar (srma), elhayyany, khsr-odoo, Pierre Lamotte (pila), Pierre Pulinckx (pipu), laro-odoo, Pierre Pulinckx, Bastien PIERRE, pish-odoo, Christihan Laurel [Vauxoo], devp-odoo, Martin Quinteros, Christopher Rogos, twalter-c2c, Meet, Hadi El Yakhni, bhra-odoo, sawer, igbe, Dhwani Patel, late-odoo, leclerc-leo, Logan Staelens (lost), jond-odoo, saurabh
WEBSITE
WEBSITEhttps://www.odoo.com/app/expenses
LAST TRACKING UPDATE
LAST TRACKING UPDATE2026-07-06 01:25:56
ODOO DEPENDENCIES
ODOO DEPENDENCIES odoo/odoo:
    - account
    - base_setup
    - base
    - web
    - onboarding
    - product
    - mail
    - bus
    - web_tour
    - html_editor
    - uom
    - analytic
    - portal
    - web_editor
    - http_routing
    - auth_signup
    - digest
    - resource
    - hr
    - phone_validation
    - resource_mail
PYTHON DEPENDENCIES
PYTHON DEPENDENCIES Not have
SYSTEM DEPENDENCIES
SYSTEM DEPENDENCIES Not have
DESCRIPTION
DESCRIPTION
Manage expenses by Employees
============================

This application allows you to manage your employees' daily expenses. It gives you access to your employees’ fee notes and give you the right to complete and validate or refuse the notes. After validation it creates an invoice for the employee.
Employee can encode their own expenses and the validation flow puts it automatically in the accounting after validation by managers.


The whole flow is implemented as:
---------------------------------
* Draft expense
* Submitted by the employee to his manager
* Approved by his manager
* Validation by the accountant and accounting entries creation

This module also uses analytic accounting and is compatible with the invoice on timesheet module so that you are able to automatically re-invoice your customers' expenses if your work by project.
    

Code Analysis

Views touched (45)
XML IDNameModelTypeStatus
hr_department_view_kanban hr.department.kanban.inherit hr.department data Inherits hr.hr_department_view_kanban
hr_employee_view_form_inherit_expense hr.employee.view.form.expense hr.employee xpath Inherits hr.view_employee_form
hr_expense_approve_duplicate_view_form hr.expense.approve.duplicate form New
hr_expense_kanban_view hr.expense.kanban hr.expense xpath Inherits hr_expense_view_expenses_analysis_kanban
hr_expense_kanban_view_header hr.expense.kanban hr.expense xpath Inherits hr_expense_view_expenses_analysis_kanban
hr_expense_kanban_view_minimal hr.expense.kanban hr.expense xpath Inherits hr_expense_view_expenses_analysis_kanban
hr_expense_refuse_wizard_view_form hr.expense.refuse.wizard.form hr.expense.refuse.wizard form New
hr_expense_sheet_view_activity hr.expense.sheet.activity hr.expense.sheet activity New
hr_expense_sheet_view_search hr.expense.sheet.view.search hr.expense.sheet search New
hr_expense_sheet_view_search_with_panel hr.expense.sheet.view.search.with.panel hr.expense.sheet xpath Inherits hr_expense_sheet_view_search
hr_expense_split Expense split hr.expense.split.wizard form New
hr_expense_template_refuse_reason hr_expense_template_refuse_reason ir.ui.view qweb New
hr_expense_template_register hr_expense_template_register ir.ui.view qweb New
hr_expense_template_register_no_user hr_expense_template_register_no_user ir.ui.view qweb New
hr_expense_view_activity hr.expense.activity hr.expense activity New
hr_expense_view_expenses_analysis_kanban hr.expense.kanban hr.expense kanban New
hr_expense_view_expenses_analysis_tree hr.expense.list hr.expense list New
hr_expense_view_form hr.expense.view.form hr.expense form New
hr_expense_view_form_without_header hr.expense.view.form hr.expense xpath Inherits hr_expense.hr_expense_view_form
hr_expense_view_graph hr.expense.graph hr.expense graph New
hr_expense_view_pivot hr.expense.pivot hr.expense pivot New
hr_expense_view_search hr.expense.view.search hr.expense search New
product_product_expense_categories_tree_view product.product.expense.categories.list.view product.product list New
product_product_expense_form_view product.product.expense.form product.product form New
product_product_expense_kanban_view product.product.kanban.expense product.product xpath Inherits product.product_kanban_view
product_product_expense_tree_view product.product.expense.list product.product list New
product_template_search_view_inherit_hr_expense product.template.search.view.inherit.hr_expense product.template filter Inherits product.product_template_search_view
report_expense_sheet report_expense_sheet ir.ui.view qweb New
report_expense_sheet_img report_expense_sheet_img ir.ui.view qweb New
res_config_settings_view_form res.config.settings.view.form.inherit.hr.expense res.config.settings xpath Inherits base.res_config_settings_view_form
res_users_view_form_preferences hr.user.preferences.form.inherit.hr.expense res.users xpath Inherits hr.res_users_view_form_profile
view_employee_tree_inherit_expense hr.employee.list.expense hr.employee xpath Inherits hr.view_employee_tree
view_expenses_tree hr.expense.list hr.expense xpath Inherits hr_expense_view_expenses_analysis_tree
view_hr_expense_sheet_dashboard_tree_header hr.expense.sheet.dashboard.list hr.expense.sheet xpath Inherits hr_expense.view_hr_expense_sheet_tree
view_hr_expense_sheet_form hr.expense.sheet.form hr.expense.sheet form New
view_hr_expense_sheet_graph hr.expense.sheet.graph hr.expense.sheet graph New
view_hr_expense_sheet_kanban hr.expense.sheet.kanban hr.expense.sheet kanban New
view_hr_expense_sheet_kanban_header hr.expense.sheet.kanban hr.expense.sheet xpath Inherits view_hr_expense_sheet_kanban
view_hr_expense_sheet_kanban_no_header hr.expense.sheet.kanban hr.expense.sheet xpath Inherits view_hr_expense_sheet_kanban
view_hr_expense_sheet_pivot hr.expense.sheet.pivot hr.expense.sheet pivot New
view_hr_expense_sheet_tree hr.expense.sheet.list hr.expense.sheet list New
view_move_form_inherit_expense account.move.form.inherit account.move xpath Inherits account.view_move_form
view_my_expenses_tree hr.expense.list hr.expense xpath Inherits hr_expense.view_expenses_tree
view_payment_form_inherit_expense account.payment.form.inherit account.payment xpath Inherits account.view_account_payment_form
view_product_hr_expense_form product.template.expense.form product.template div Inherits product.product_template_form_view
Models touched (24)

New fields (0)

No new fields.

Public methods (0)

No public methods.

New fields (1)
  • business_domain Selection
    ondelete={'expense': 'cascade'} selection_add=[('expense', 'Expense')]
Public methods (0)

No public methods.

New fields (2)
  • expense_sheet_id Many2one → hr.expense.sheet
    comodel_name='hr.expense.sheet' copy=False index='btree_not_null' ondelete='set null'
  • show_commercial_partner_warning Boolean
    compute='_compute_show_commercial_partner_warning'
Public methods (2)
  • action_open_expense_report(self)
  • button_cancel(self)

New fields (1)
  • expense_id Many2one → hr.expense
    copy=True index='btree_not_null' string='Expense' args: 'hr.expense'
Public methods (0)

No public methods.

New fields (1)
  • expense_sheet_id Many2one
    related='move_id.expense_sheet_id'
Public methods (2)
  • action_open_expense_report(self)
  • write(self, vals)

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)
  • expense_sheets_to_approve_count Integer
    compute='_compute_expense_sheets_to_approve' string='Expenses Reports to Approve'
Public methods (0)

No public methods.

New fields (1)
  • expense_manager_id Many2one → res.users
    comodel_name='res.users' compute='_compute_expense_manager' domain=_group_hr_expense_user_domain help='Select the user responsible for approving "Expenses" of this employee.\nIf empty, the approval is done by an Administrator or Approver (determined in settings/users).' readonly=False store=True string='Expense'
Public methods (0)

No public methods.

New fields (1)
  • filter_for_expense Boolean
    groups='hr.group_hr_user' search='_search_filter_for_expense' store=False
Public methods (0)

No public methods.

New fields (1)
  • expense_manager_id Many2one → res.users
    readonly=True args: 'res.users'
Public methods (0)

No public methods.

New fields (38)
  • account_id Many2one → account.account
    check_company=True comodel_name='account.account' compute='_compute_account_id' domain="[('account_type', 'not in', ('asset_receivable', 'liability_payable', 'asset_cash', 'liability_credit_card'))]" help='An expense account is expected' precompute=True readonly=False store=True string='Account'
  • accounting_date Date
    groups='account.group_account_invoice,account.group_account_readonly' related='sheet_id.accounting_date' store=True string='Accounting Date'
  • approved_by Many2one → res.users
    comodel_name='res.users' related='sheet_id.user_id' string='Approved By' tracking=False
  • approved_on Datetime
    related='sheet_id.approval_date' string='Approved On'
  • attachment_ids One2many → ir.attachment
    comodel_name='ir.attachment' domain=[('res_model', '=', 'hr.expense')] inverse_name='res_id' string='Attachments'
  • company_currency_id Many2one → res.currency
    comodel_name='res.currency' readonly=True related='company_id.currency_id' string='Report Company Currency'
  • company_id Many2one → res.company
    comodel_name='res.company' default=<expr> readonly=True required=True string='Company'
  • currency_id Many2one → res.currency
    comodel_name='res.currency' compute='_compute_currency_id' default=<expr> precompute=True readonly=False required=True store=True string='Currency'
  • currency_rate Float
    compute='_compute_currency_rate' digits=(16, 9) readonly=True tracking=True
  • date Date
    default=fields.Date.context_today string='Expense Date'
  • description Text
    string='Internal Notes'
  • duplicate_expense_ids Many2many → hr.expense
    comodel_name='hr.expense' compute='_compute_duplicate_expense_ids'
  • employee_id Many2one → hr.employee
    check_company=True comodel_name='hr.employee' compute='_compute_employee_id' default=_default_employee_id domain=[('filter_for_expense', '=', True)] precompute=True readonly=False required=True store=True string='Employee' tracking=True
  • is_editable Boolean
    compute='_compute_is_editable' string='Is Editable By Current User'
  • is_multiple_currency Boolean
    compute='_compute_is_multiple_currency' string='Is currency_id different from the company_currency_id'
  • label_currency_rate Char
    compute='_compute_currency_rate' readonly=True
  • message_main_attachment_checksum Char
    related='message_main_attachment_id.checksum'
  • name Char
    compute='_compute_name' copy=True precompute=True readonly=False required=True store=True string='Description'
  • nb_attachment Integer
    compute='_compute_nb_attachment' string='Number of Attachments'
  • payment_mode Selection
    default='own_account' required=True selection=[('own_account', 'Employee (to reimburse)'), ('company_account', 'Company')] string='Paid By' tracking=True
  • price_unit Float
    compute='_compute_price_unit' copy=True min_display_digits='Product Price' precompute=True readonly=True required=True store=True string='Unit Price'
  • product_description Html
    compute='_compute_product_description'
  • product_has_cost Boolean
    compute='_compute_from_product'
  • product_has_tax Boolean
    compute='_compute_from_product' string='Whether tax is defined on a selected product'
  • product_id Many2one → product.product
    check_company=True comodel_name='product.product' domain=[('can_be_expensed', '=', True)] ondelete='restrict' string='Category' tracking=True
  • product_uom_category_id Many2one → uom.category
    comodel_name='uom.category' readonly=True related='product_id.uom_id.category_id' string='UoM Category'
  • product_uom_id Many2one → uom.uom
    comodel_name='uom.uom' compute='_compute_uom_id' copy=True domain="[('category_id', '=', product_uom_category_id)]" precompute=True store=True string='Unit of Measure'
  • quantity Float
    default=1 digits='Product Unit of Measure' required=True
  • same_receipt_expense_ids Many2many → hr.expense
    comodel_name='hr.expense' compute='_compute_same_receipt_expense_ids'
  • sheet_id Many2one → hr.expense.sheet
    comodel_name='hr.expense.sheet' copy=False domain="[('employee_id', '=', employee_id), ('company_id', '=', company_id)]" index=True readonly=True string='Expense Report'
  • state Selection
    compute='_compute_state' copy=False default='draft' index=True readonly=True selection=[('draft', 'To Report'), ('reported', 'To Submit'), ('submitted', 'Submitted'), ('approved', 'Approved'), ('done', 'Done'), ('refused', 'Refused')] store=True string='Status'
  • tax_amount Monetary
    compute='_compute_tax_amount' currency_field='company_currency_id' help='Tax amount in company currency' precompute=True store=True string='Tax amount'
  • tax_amount_currency Monetary
    compute='_compute_tax_amount_currency' currency_field='currency_id' help='Tax amount in currency' precompute=True store=True string='Tax amount in Currency'
  • tax_ids Many2many → account.tax
    check_company=True column1='expense_id' column2='tax_id' comodel_name='account.tax' compute='_compute_tax_ids' domain="[('type_tax_use', '=', 'purchase')]" help='Both price-included and price-excluded taxes will behave as price-included taxes for expenses.' precompute=True readonly=False relation='expense_tax' store=True string='Included taxes'
  • total_amount Monetary
    compute='_compute_total_amount' currency_field='company_currency_id' inverse='_inverse_total_amount' precompute=True readonly=False store=True string='Total' tracking=True
  • total_amount_currency Monetary
    compute='_compute_total_amount_currency' currency_field='currency_id' precompute=True readonly=False store=True string='Total In Currency' tracking=True
  • untaxed_amount_currency Monetary
    compute='_compute_tax_amount_currency' currency_field='currency_id' precompute=True store=True string='Total Untaxed Amount In Currency'
  • vendor_id Many2one → res.partner
    comodel_name='res.partner' string='Vendor'
Public methods (17)
  • action_approve_duplicates(self)
  • action_get_attachment_view(self)
  • action_show_same_receipt_expense_ids(self)
  • action_split_wizard(self)
  • action_submit_expenses(self)
  • action_view_sheet(self)
  • attach_document(self, **kwargs)
    When an attachment is uploaded as a receipt, set it as the main attachment.
  • check_amount_not_zero(self, vals)
  • create(self, vals_list)
    @api.model_create_multi
  • create_expense_from_attachments(self, attachment_ids=None, view_type='list')
    @api.model
    Create the expenses from files. :return: An action redirecting to hr.expense list view.
  • get_empty_list_help(self, help_message)
    @api.model
  • get_expense_attachments(self)
  • get_expense_dashboard(self)
    @api.model
  • get_expenses_to_submit(self)
  • message_new(self, msg_dict, custom_values=None)
    @api.model
  • unlink(self)
  • write(self, vals)

New fields (2)
  • expense_ids Many2many → hr.expense
    readonly=True args: 'hr.expense'
  • sheet_ids Many2many → hr.expense.sheet
    args: 'hr.expense.sheet'
Public methods (3)
  • action_approve(self)
  • action_refuse(self)
  • default_get(self, fields)
    @api.model

New fields (2)
  • reason Char
    required=True string='Reason'
  • sheet_ids Many2many → hr.expense.sheet
    args: 'hr.expense.sheet'
Public methods (2)
  • action_refuse(self)
  • default_get(self, fields)
    @api.model

New fields (33)
  • account_move_ids One2many → account.move
    comodel_name='account.move' inverse_name='expense_sheet_id' readonly=True string='Journal Entries'
  • accounting_date Date
    help='Specify the bill date of the related vendor bill.' string='Expense Report Date'
  • amount_residual Monetary
    compute='_compute_from_account_move_ids' currency_field='company_currency_id' readonly=True store=True string='Amount Due'
  • approval_date Datetime
    readonly=True string='Approval Date'
  • approval_state Selection
    copy=False selection=[('submit', 'Submitted'), ('approve', 'Approved'), ('cancel', 'Refused')]
  • attachment_ids One2many → ir.attachment
    comodel_name='ir.attachment' domain="[('res_model', '=', 'hr.expense.sheet')]" inverse_name='res_id' string='Attachments of expenses'
  • can_approve Boolean
    compute='_compute_can_approve' string='Can Approve'
  • can_reset Boolean
    compute='_compute_can_reset' string='Can Reset'
  • cannot_approve_reason Char
    compute='_compute_can_approve' string='Cannot Approve Reason'
  • company_currency_id Many2one → res.currency
    comodel_name='res.currency' related='company_id.currency_id' string='Report Company Currency'
  • company_id Many2one → res.company
    comodel_name='res.company' default=<expr> readonly=True required=True string='Company'
  • currency_id Many2one → res.currency
    comodel_name='res.currency' compute='_compute_currency_id' readonly=True store=True string='Currency'
  • department_id Many2one → hr.department
    comodel_name='hr.department' copy=False related='employee_id.department_id' store=True string='Department'
  • employee_id Many2one → hr.employee
    check_company=True comodel_name='hr.employee' default=_default_employee_id domain=[('filter_for_expense', '=', True)] readonly=True required=True string='Employee' tracking=True
  • employee_journal_id Many2one → account.journal
    check_company=True comodel_name='account.journal' default=_default_journal_id domain=[('type', '=', 'purchase')] help='The journal used when the expense is paid by employee.' string='Journal'
  • expense_line_ids One2many → hr.expense
    comodel_name='hr.expense' copy=False inverse_name='sheet_id' string='Expense Lines'
  • is_editable Boolean
    compute='_compute_is_editable' string='Expense Lines Are Editable By Current User'
  • is_multiple_currency Boolean
    compute='_compute_is_multiple_currency' string='Handle lines with different currencies'
  • journal_id Many2one → account.journal
    check_company=True comodel_name='account.journal' compute='_compute_journal_id' store=True string='Expense Journal'
  • message_main_attachment_id Many2one
    compute='_compute_main_attachment' store=True
  • name Char
    required=True string='Expense Report Summary' tracking=True
  • nb_account_move Integer
    compute='_compute_nb_account_move' string='Number of Journal Entries'
  • nb_expense Integer
    compute='_compute_nb_expense' string='Number of Expenses'
  • payment_method_line_id Many2one → account.payment.method.line
    comodel_name='account.payment.method.line' compute='_compute_payment_method_line_id' domain="[('id', 'in', selectable_payment_method_line_ids)]" help='The payment method used when the expense is paid by the company.' readonly=False store=True string='Payment Method'
  • payment_mode Selection
    readonly=True related='expense_line_ids.payment_mode' string='Paid By' tracking=True
  • payment_state Selection
    compute='_compute_from_account_move_ids' copy=False readonly=True selection=<expr> store=True string='Payment Status' tracking=True
  • product_ids Many2many → product.product
    check_company=True comodel_name='product.product' compute='_compute_product_ids' search='_search_product_ids' string='Categories'
  • selectable_payment_method_line_ids Many2many → account.payment.method.line
    comodel_name='account.payment.method.line' compute='_compute_selectable_payment_method_line_ids'
  • state Selection
    compute='_compute_state' copy=False default='draft' index=True readonly=True required=True selection=[('draft', 'To Submit'), ('submit', 'Submitted'), ('approve', 'Approved'), ('post', 'Posted'), ('done', 'Done'), ('cancel', 'Refused')] store=True string='Status' tracking=True
  • total_amount Monetary
    compute='_compute_amount' currency_field='company_currency_id' readonly=True store=True string='Total' tracking=True
  • total_tax_amount Monetary
    compute='_compute_amount' currency_field='company_currency_id' readonly=True store=True string='Taxes'
  • untaxed_amount Monetary
    compute='_compute_amount' currency_field='company_currency_id' readonly=True store=True string='Untaxed Amount'
  • user_id Many2one → res.users
    comodel_name='res.users' compute='_compute_from_employee_id' copy=False domain=<expr> readonly=True store=True string='Manager' tracking=True
Public methods (13)
  • action_approve_expense_sheets(self)
  • action_open_account_moves(self)
  • action_open_expense_view(self)
  • action_refuse_expense_sheets(self)
  • action_register_payment(self)
    Open the account.payment.register wizard to pay the selected journal entries. There can be more than one bank_account_id in the expense sheet when registering payment for multiple expenses. The default_partner_bank_id is set only if there is one available, if more than one the field is left empty. :return: An action opening the account.payment.register wizard.
  • action_reset_expense_sheets(self)
  • action_sheet_move_post(self)
  • action_submit_sheet(self)
  • activity_update(self)
  • create(self, vals_list)
    @api.model_create_multi
  • set_to_paid(self)
  • set_to_posted(self)
  • write(self, values)

New fields (12)
  • company_id Many2one → res.company
    comodel_name='res.company'
  • currency_id Many2one → res.currency
    comodel_name='res.currency'
  • employee_id Many2one → hr.employee
    comodel_name='hr.employee' required=True string='Employee'
  • expense_id Many2one → hr.expense
    comodel_name='hr.expense' string='Expense'
  • name Char
    required=True string='Description'
  • product_has_cost Boolean
    compute='_compute_from_product_id' store=True string='Is product with non zero cost selected'
  • product_has_tax Boolean
    compute='_compute_product_has_tax' string='Whether tax is defined on a selected product'
  • product_id Many2one → product.product
    check_company=True comodel_name='product.product' domain=[('can_be_expensed', '=', True)] required=True string='Product'
  • tax_amount_currency Monetary
    compute='_compute_tax_amount_currency' string='Tax amount in Currency'
  • tax_ids Many2many → account.tax
    check_company=True comodel_name='account.tax' domain="[('type_tax_use', '=', 'purchase')]"
  • total_amount_currency Monetary
    compute='_compute_from_product_id' readonly=False required=True store=True string='Total In Currency'
  • wizard_id Many2one → hr.expense.split.wizard
    comodel_name='hr.expense.split.wizard'
Public methods (1)
  • default_get(self, fields)

New fields (7)
  • currency_id Many2one → res.currency
    comodel_name='res.currency' related='expense_id.currency_id'
  • expense_id Many2one → hr.expense
    comodel_name='hr.expense' required=True string='Expense'
  • expense_split_line_ids One2many → hr.expense.split
    comodel_name='hr.expense.split' inverse_name='wizard_id'
  • split_possible Boolean
    compute='_compute_split_possible' help='The sum of after split shut remain the same'
  • tax_amount_currency Monetary
    compute='_compute_tax_amount_currency' currency_field='currency_id' string='Taxes'
  • total_amount_currency Monetary
    compute='_compute_total_amount_currency' currency_field='currency_id' string='Total Amount'
  • total_amount_currency_original Monetary
    currency_field='currency_id' help='Total amount of the original Expense that we are splitting' related='expense_id.total_amount_currency' string='Total amount original'
Public methods (1)
  • action_split_expense(self)

New fields (0)

No new fields.

Public methods (0)

No public methods.

New fields (0)

No new fields.

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

New fields (1)
  • standard_price_update_warning Char
    compute='_compute_standard_price_update_warning'
Public methods (1)
  • write(self, vals)

New fields (1)
  • can_be_expensed Boolean
    compute='_compute_can_be_expensed' help='Specify whether the product can be selected in an expense.' readonly=False store=True string='Expenses'
Public methods (1)
  • default_get(self, fields)
    @api.model

New fields (3)
  • company_expense_allowed_payment_method_line_ids Many2many → account.payment.method.line
    check_company=True domain="[('payment_type', '=', 'outbound'), ('journal_id', '!=', False), ('journal_id.active', '=', True)]" string='Payment methods available for expenses paid by company' args: 'account.payment.method.line'
  • expense_journal_id Many2one → account.journal
    check_company=True domain="[('type', '=', 'purchase')]" help="The company's default journal used when an employee expense is created." string='Default Expense Journal' args: 'account.journal'
  • expense_outstanding_account_id Many2one → account.account
    check_company=True domain="[('account_type', '=', 'asset_current'), ('reconcile', '=', True)]" help='The account used to record the outstanding amount of the company expenses.' string='Outstanding Account' args: 'account.account'
Public methods (0)

No public methods.

New fields (8)
  • company_expense_allowed_payment_method_line_ids Many2many → account.payment.method.line
    check_company=True comodel_name='account.payment.method.line' readonly=False related='company_id.company_expense_allowed_payment_method_line_ids'
  • expense_journal_id Many2one → account.journal
    check_company=True domain="[('type', '=', 'purchase')]" readonly=False related='company_id.expense_journal_id' args: 'account.journal'
  • expense_outstanding_account_id Many2one → account.account
    check_company=True comodel_name='account.account' domain="[('account_type', '=', 'asset_current'), ('reconcile', '=', True)]" readonly=False related='company_id.expense_outstanding_account_id'
  • hr_expense_alias_domain_id Many2one → mail.alias.domain
    comodel_name='mail.alias.domain' compute='_compute_hr_expense_alias_domain_id' inverse='_inverse_hr_expense_alias_domain_id' readonly=False
  • hr_expense_alias_prefix Char
    compute='_compute_hr_expense_alias_prefix' readonly=False store=True args: 'Default Alias Name for Expenses'
  • hr_expense_use_mailgateway Boolean
    config_parameter='hr_expense.use_mailgateway' string='Let your employees record expenses by email'
  • module_hr_expense_extract Boolean
    string='Send bills to OCR to generate expenses'
  • module_hr_payroll_expense Boolean
    string='Reimburse Expenses in Payslip'
Public methods (2)
  • get_values(self)
    @api.model
  • set_values(self)

New fields (1)
  • expense_manager_id Many2one
    readonly=False related='employee_id.expense_manager_id'
Public methods (1)
  • SELF_READABLE_FIELDS(self)
    @property
REPOSITORY
REPOSITORYodoo/odoo
GIT
GIThttps://github.com/odoo/odoo.git
GIT FOLDER
GIT FOLDERhttps://github.com/odoo/odoo/tree/17.0/hr_expense
VERSION
VERSION 2.0
CATEGORY
CATEGORYHuman Resources/Expenses
LICENSE
LICENSELGPL-3
APPLICATION
APPLICATIONYes
AUTO-INSTALLABLE
AUTO-INSTALLABLENo
AUTHORS
AUTHORS
MAINTAINERS
MAINTAINERS
COMMITTERS
COMMITTERSRaphael Collet, Odoo Translation Bot, Martin Trigaux, Thibault Delavallée, Denis Ledoux, Christophe Simonis, Xavier Morel, qdp-odoo, Géry Debongnie, Aaron Bohy, Jeremy Kersten, Damien Bouvy, Barad Mahendra, Yannick Tivisse, Odoo Online, Laurent Smet, Jorge Pinna Puissant, Alexandre Kühn, Victor Feyens, Andrea Grazioso (agr-odoo), Adrien Dieudonné, Joseph Caburnay, Anh Thao Pham (pta), Samuel Degueldre, Achraf (abz), Brice bib Bartoletti, Nasreddin Boulif (bon), Prakash Prajapati, Tiffany Chang (tic), Rémy Voet (ryv), Gorash, Pierre Paridans, William Braeckman, Kevin Baptiste, Guillaume (guva), Hubert Van de Walle (huvw), Miquel Raïch, sofiagvaladze, Nicolas (vin), william-andre, Víctor Martínez, MerlinGuillaume, Patrick Hoste, Nshimiyimana Séna, Louis Baudoux, Yolann Sabaux, Abdelouahab (abla), John Laterre (jol), gawa-odoo, Hubert Van De Walle, Renaud Thiry, niyasraphy, jbw-odoo, Habib (ayh), Thomas Lefebvre (thle), alsh-odoo, Louis Wicket (wil), Maximilien (malb), Victor Piryns (pivi), Hamza (hisl), Thomas Becquevort (thbe), Gauthier Wala (gawa), Ali Alfie (alal), Pierre-Yves Dufays, Yosua Nicolaus, Antoine Dupuis (andu), Antoine Boonen, Dylan Kiss (dyki), hupo-odoo, Bruno Boi, flvr-odoo, Julien Alardot (jual), FrancoisGe, Michael (mcm), Leloup Loïc (lole), tong-odoo, Brieuc-brd, Nguyễn Đại Dương, Cameron, sesn-odoo, guva-odoo, kdeb@odoo.com, Paul Stroobant, ravb-odoo, dasz, Seif (segn), yosa-odoo, Nathaniel (naja), det, Julien Carion (juca), moerradi, yaih-odoo, mega, SaddemAmine, Louis Gobert, Chrysanthe (chgo), aath-odoo, Louis (wil), asno-odoo, csnauwaert, Mathieu Coutant, Elisabeth Dickinson, Andrea Gidalti, sabr, Mohammed Basioni, German Loredo [Vauxoo], Trinh Ngoc Hung, Maira Salazar (srma), elhayyany, Pierre Lamotte (pila), Pierre Pulinckx (pipu), Bastien PIERRE, pish-odoo, Christihan Laurel [Vauxoo], Pulinckx Pierre (PIPU), bhra-odoo, sawer, igbe, leclerc-leo, Logan Staelens (lost), jond-odoo
WEBSITE
WEBSITEhttps://www.odoo.com/app/expenses
LAST TRACKING UPDATE
LAST TRACKING UPDATE2026-07-06 01:02:07
ODOO DEPENDENCIES
ODOO DEPENDENCIES odoo/odoo:
    - account
    - base_setup
    - base
    - web
    - onboarding
    - product
    - mail
    - bus
    - web_tour
    - uom
    - analytic
    - portal
    - web_editor
    - http_routing
    - auth_signup
    - digest
    - resource
    - hr
    - phone_validation
PYTHON DEPENDENCIES
PYTHON DEPENDENCIES Not have
SYSTEM DEPENDENCIES
SYSTEM DEPENDENCIES Not have
DESCRIPTION
DESCRIPTION
Manage expenses by Employees
============================

This application allows you to manage your employees' daily expenses. It gives you access to your employees’ fee notes and give you the right to complete and validate or refuse the notes. After validation it creates an invoice for the employee.
Employee can encode their own expenses and the validation flow puts it automatically in the accounting after validation by managers.


The whole flow is implemented as:
---------------------------------
* Draft expense
* Submitted by the employee to his manager
* Approved by his manager
* Validation by the accountant and accounting entries creation

This module also uses analytic accounting and is compatible with the invoice on timesheet module so that you are able to automatically re-invoice your customers' expenses if your work by project.
    

Code Analysis

Views touched (45)
XML IDNameModelTypeStatus
account_journal_dashboard_kanban_view_inherit_hr_expense account.journal.dashboard.kanban account.journal xpath Inherits account.account_journal_dashboard_kanban_view
hr_department_view_kanban hr.department.kanban.inherit hr.department data Inherits hr.hr_department_view_kanban
hr_employee_view_form_inherit_expense hr.employee.view.form.expense hr.employee xpath Inherits hr.view_employee_form
hr_expense_approve_duplicate_view_form hr.expense.approve.duplicate form New
hr_expense_kanban_view hr.expense.kanban hr.expense xpath Inherits hr_expense_view_expenses_analysis_kanban
hr_expense_kanban_view_header hr.expense.kanban hr.expense xpath Inherits hr_expense_view_expenses_analysis_kanban
hr_expense_refuse_wizard_view_form hr.expense.refuse.wizard.form hr.expense.refuse.wizard form New
hr_expense_sheet_view_activity hr.expense.sheet.activity hr.expense.sheet activity New
hr_expense_sheet_view_search hr.expense.sheet.view.search hr.expense.sheet search New
hr_expense_sheet_view_search_with_panel hr.expense.sheet.view.search.with.panel hr.expense.sheet xpath Inherits hr_expense_sheet_view_search
hr_expense_split Expense split hr.expense.split.wizard form New
hr_expense_template_refuse_reason hr_expense_template_refuse_reason ir.ui.view qweb New
hr_expense_template_register hr_expense_template_register ir.ui.view qweb New
hr_expense_template_register_no_user hr_expense_template_register_no_user ir.ui.view qweb New
hr_expense_view_activity hr.expense.activity hr.expense activity New
hr_expense_view_expenses_analysis_kanban hr.expense.kanban hr.expense kanban New
hr_expense_view_expenses_analysis_tree hr.expense.tree hr.expense tree New
hr_expense_view_form hr.expense.view.form hr.expense form New
hr_expense_view_form_without_header hr.expense.view.form hr.expense xpath Inherits hr_expense.hr_expense_view_form
hr_expense_view_graph hr.expense.graph hr.expense graph New
hr_expense_view_pivot hr.expense.pivot hr.expense pivot New
hr_expense_view_search hr.expense.view.search hr.expense search New
product_product_expense_categories_tree_view product.product.expense.categories.tree.view product.product tree New
product_product_expense_form_view product.product.expense.form product.product form New
product_product_expense_kanban_view product.product.kanban.expense product.product xpath Inherits product.product_kanban_view
product_product_expense_tree_view product.product.expense.tree product.product tree New
product_template_search_view_inherit_hr_expense product.template.search.view.inherit.hr_expense product.template filter Inherits product.product_template_search_view
report_expense_sheet report_expense_sheet ir.ui.view qweb New
report_expense_sheet_img report_expense_sheet_img ir.ui.view qweb New
res_config_settings_view_form res.config.settings.view.form.inherit.hr.expense res.config.settings xpath Inherits base.res_config_settings_view_form
res_users_view_form_preferences hr.user.preferences.form.inherit.hr.expense res.users xpath Inherits hr.res_users_view_form_profile
view_employee_tree_inherit_expense hr.employee.tree.expense hr.employee xpath Inherits hr.view_employee_tree
view_expenses_tree hr.expense.tree hr.expense xpath Inherits hr_expense_view_expenses_analysis_tree
view_hr_expense_sheet_dashboard_tree_header hr.expense.sheet.dashboard.tree hr.expense.sheet xpath Inherits hr_expense.view_hr_expense_sheet_tree
view_hr_expense_sheet_form hr.expense.sheet.form hr.expense.sheet form New
view_hr_expense_sheet_graph hr.expense.sheet.graph hr.expense.sheet graph New
view_hr_expense_sheet_kanban hr.expense.sheet.kanban hr.expense.sheet kanban New
view_hr_expense_sheet_kanban_header hr.expense.sheet.kanban hr.expense.sheet xpath Inherits view_hr_expense_sheet_kanban
view_hr_expense_sheet_kanban_no_header hr.expense.sheet.kanban hr.expense.sheet xpath Inherits view_hr_expense_sheet_kanban
view_hr_expense_sheet_pivot hr.expense.sheet.pivot hr.expense.sheet pivot New
view_hr_expense_sheet_tree hr.expense.sheet.tree hr.expense.sheet tree New
view_move_form_inherit_expense account.move.form.inherit account.move xpath Inherits account.view_move_form
view_my_expenses_tree hr.expense.tree hr.expense xpath Inherits hr_expense.view_expenses_tree
view_payment_form_inherit_expense account.payment.form.inherit account.payment xpath Inherits account.view_account_payment_form
view_product_hr_expense_form product.template.expense.form product.template div Inherits product.product_template_form_view
Models touched (25)

New fields (0)

No new fields.

Public methods (0)

No public methods.

New fields (1)
  • business_domain Selection
    ondelete={'expense': 'cascade'} selection_add=[('expense', 'Expense')]
Public methods (0)

No public methods.

New fields (0)

No new fields.

Public methods (1)
  • open_expenses_action(self)

New fields (2)
  • expense_sheet_id Many2one → hr.expense.sheet
    comodel_name='hr.expense.sheet' copy=False index='btree_not_null' ondelete='set null'
  • show_commercial_partner_warning Boolean
    compute='_compute_show_commercial_partner_warning'
Public methods (1)
  • action_open_expense_report(self)

New fields (1)
  • expense_id Many2one → hr.expense
    copy=True string='Expense' args: 'hr.expense'
Public methods (0)

No public methods.

New fields (0)

No new fields.

Public methods (1)
  • action_open_expense_report(self)

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)
  • expense_sheets_to_approve_count Integer
    compute='_compute_expense_sheets_to_approve' string='Expenses Reports to Approve'
Public methods (0)

No public methods.

New fields (1)
  • expense_manager_id Many2one → res.users
    comodel_name='res.users' compute='_compute_expense_manager' domain=_group_hr_expense_user_domain help='Select the user responsible for approving "Expenses" of this employee.\nIf empty, the approval is done by an Administrator or Approver (determined in settings/users).' readonly=False store=True string='Expense'
Public methods (0)

No public methods.

New fields (1)
  • filter_for_expense Boolean
    search='_search_filter_for_expense' store=False
Public methods (0)

No public methods.

New fields (1)
  • expense_manager_id Many2one → res.users
    readonly=True args: 'res.users'
Public methods (0)

No public methods.

New fields (36)
  • account_id Many2one → account.account
    check_company=True comodel_name='account.account' compute='_compute_account_id' domain="[('account_type', 'not in', ('asset_receivable', 'liability_payable', 'asset_cash', 'liability_credit_card'))]" help='An expense account is expected' precompute=True readonly=False store=True string='Account'
  • accounting_date Date
    groups='account.group_account_invoice,account.group_account_readonly' related='sheet_id.accounting_date' store=True string='Accounting Date'
  • approved_by Many2one → res.users
    comodel_name='res.users' related='sheet_id.user_id' string='Approved By' tracking=False
  • approved_on Datetime
    related='sheet_id.approval_date' string='Approved On'
  • attachment_ids One2many → ir.attachment
    comodel_name='ir.attachment' domain="[('res_model', '=', 'hr.expense')]" inverse_name='res_id' string='Attachments'
  • company_currency_id Many2one → res.currency
    comodel_name='res.currency' readonly=True related='company_id.currency_id' string='Report Company Currency'
  • company_id Many2one → res.company
    comodel_name='res.company' default=<expr> readonly=True required=True string='Company'
  • currency_id Many2one → res.currency
    comodel_name='res.currency' compute='_compute_currency_id' default=<expr> precompute=True readonly=False required=True store=True string='Currency'
  • currency_rate Float
    compute='_compute_currency_rate' digits=(16, 9) readonly=True tracking=True
  • date Date
    default=fields.Date.context_today string='Expense Date'
  • description Text
    string='Internal Notes'
  • duplicate_expense_ids Many2many → hr.expense
    comodel_name='hr.expense' compute='_compute_duplicate_expense_ids'
  • employee_id Many2one → hr.employee
    check_company=True comodel_name='hr.employee' compute='_compute_employee_id' default=_default_employee_id domain=[('filter_for_expense', '=', True)] precompute=True readonly=False required=True store=True string='Employee' tracking=True
  • is_editable Boolean
    compute='_compute_is_editable' string='Is Editable By Current User'
  • is_multiple_currency Boolean
    compute='_compute_is_multiple_currency' string='Is currency_id different from the company_currency_id'
  • label_currency_rate Char
    compute='_compute_currency_rate' readonly=True
  • message_main_attachment_checksum Char
    related='message_main_attachment_id.checksum'
  • name Char
    compute='_compute_name' copy=True precompute=True readonly=False required=True store=True string='Description'
  • nb_attachment Integer
    compute='_compute_nb_attachment' string='Number of Attachments'
  • payment_mode Selection
    default='own_account' selection=[('own_account', 'Employee (to reimburse)'), ('company_account', 'Company')] string='Paid By' tracking=True
  • price_unit Float
    compute='_compute_price_unit' copy=True digits='Product Price' precompute=True readonly=True required=True store=True string='Unit Price'
  • product_description Html
    compute='_compute_product_description'
  • product_has_cost Boolean
    compute='_compute_from_product'
  • product_has_tax Boolean
    compute='_compute_from_product' string='Whether tax is defined on a selected product'
  • product_id Many2one → product.product
    check_company=True comodel_name='product.product' domain=[('can_be_expensed', '=', True)] ondelete='restrict' string='Category' tracking=True
  • product_uom_category_id Many2one → uom.category
    comodel_name='uom.category' readonly=True related='product_id.uom_id.category_id' string='UoM Category'
  • product_uom_id Many2one → uom.uom
    comodel_name='uom.uom' compute='_compute_uom_id' copy=True domain="[('category_id', '=', product_uom_category_id)]" precompute=True store=True string='Unit of Measure'
  • quantity Float
    default=1 digits='Product Unit of Measure' required=True
  • sheet_id Many2one → hr.expense.sheet
    comodel_name='hr.expense.sheet' copy=False domain="[('employee_id', '=', employee_id), ('company_id', '=', company_id)]" readonly=True string='Expense Report'
  • state Selection
    compute='_compute_state' copy=False default='draft' index=True readonly=True selection=[('draft', 'To Report'), ('reported', 'To Submit'), ('submitted', 'Submitted'), ('approved', 'Approved'), ('done', 'Done'), ('refused', 'Refused')] store=True string='Status'
  • tax_amount Monetary
    compute='_compute_tax_amount' currency_field='company_currency_id' help='Tax amount in company currency' precompute=True store=True string='Tax amount'
  • tax_amount_currency Monetary
    compute='_compute_tax_amount_currency' currency_field='currency_id' help='Tax amount in currency' precompute=True store=True string='Tax amount in Currency'
  • tax_ids Many2many → account.tax
    check_company=True column1='expense_id' column2='tax_id' comodel_name='account.tax' compute='_compute_tax_ids' domain="[('type_tax_use', '=', 'purchase')]" help='Both price-included and price-excluded taxes will behave as price-included taxes for expenses.' precompute=True readonly=False relation='expense_tax' store=True string='Included taxes'
  • total_amount Monetary
    compute='_compute_total_amount' currency_field='company_currency_id' inverse='_inverse_total_amount' precompute=True readonly=False store=True string='Total' tracking=True
  • total_amount_currency Monetary
    compute='_compute_total_amount_currency' currency_field='currency_id' precompute=True readonly=False store=True string='Total In Currency' tracking=True
  • untaxed_amount_currency Monetary
    compute='_compute_tax_amount_currency' currency_field='currency_id' precompute=True store=True string='Total Untaxed Amount In Currency'
Public methods (13)
  • action_approve_duplicates(self)
  • action_get_attachment_view(self)
  • action_split_wizard(self)
  • action_submit_expenses(self)
  • action_view_sheet(self)
  • attach_document(self, **kwargs)
    When an attachment is uploaded as a receipt, set it as the main attachment.
  • create_expense_from_attachments(self, attachment_ids=None, view_type='list')
    Create the expenses from files. :return: An action redirecting to hr.expense tree view.
  • get_empty_list_help(self, help_message)
    @api.model
  • get_expense_dashboard(self)
    @api.model
  • get_expenses_to_submit(self)
  • message_new(self, msg_dict, custom_values=None)
    @api.model
  • unlink(self)
  • write(self, vals)

New fields (2)
  • expense_ids Many2many → hr.expense
    readonly=True args: 'hr.expense'
  • sheet_ids Many2many → hr.expense.sheet
    args: 'hr.expense.sheet'
Public methods (3)
  • action_approve(self)
  • action_refuse(self)
  • default_get(self, fields)
    @api.model

New fields (2)
  • reason Char
    required=True string='Reason'
  • sheet_ids Many2many → hr.expense.sheet
    args: 'hr.expense.sheet'
Public methods (2)
  • action_refuse(self)
  • default_get(self, fields)
    @api.model

New fields (33)
  • account_move_ids One2many → account.move
    comodel_name='account.move' inverse_name='expense_sheet_id' readonly=True string='Journal Entries'
  • accounting_date Date
    compute='_compute_accounting_date' store=True string='Accounting Date'
  • amount_residual Monetary
    compute='_compute_from_account_move_ids' currency_field='company_currency_id' readonly=True store=True string='Amount Due'
  • approval_date Datetime
    readonly=True string='Approval Date'
  • approval_state Selection
    copy=False selection=[('submit', 'Submitted'), ('approve', 'Approved'), ('cancel', 'Refused')]
  • attachment_ids One2many → ir.attachment
    comodel_name='ir.attachment' domain="[('res_model', '=', 'hr.expense.sheet')]" inverse_name='res_id' string='Attachments of expenses'
  • can_approve Boolean
    compute='_compute_can_approve' string='Can Approve'
  • can_reset Boolean
    compute='_compute_can_reset' string='Can Reset'
  • cannot_approve_reason Char
    compute='_compute_can_approve' string='Cannot Approve Reason'
  • company_currency_id Many2one → res.currency
    comodel_name='res.currency' related='company_id.currency_id' string='Report Company Currency'
  • company_id Many2one → res.company
    comodel_name='res.company' default=<expr> readonly=True required=True string='Company'
  • currency_id Many2one → res.currency
    comodel_name='res.currency' compute='_compute_currency_id' readonly=True store=True string='Currency'
  • department_id Many2one → hr.department
    comodel_name='hr.department' copy=False related='employee_id.department_id' store=True string='Department'
  • employee_id Many2one → hr.employee
    check_company=True comodel_name='hr.employee' default=_default_employee_id domain=[('filter_for_expense', '=', True)] readonly=True required=True string='Employee' tracking=True
  • employee_journal_id Many2one → account.journal
    check_company=True comodel_name='account.journal' default=_default_journal_id domain=[('type', '=', 'purchase')] help='The journal used when the expense is paid by employee.' string='Journal'
  • expense_line_ids One2many → hr.expense
    comodel_name='hr.expense' copy=False inverse_name='sheet_id' string='Expense Lines'
  • is_editable Boolean
    compute='_compute_is_editable' string='Expense Lines Are Editable By Current User'
  • is_multiple_currency Boolean
    compute='_compute_is_multiple_currency' string='Handle lines with different currencies'
  • journal_id Many2one → account.journal
    check_company=True comodel_name='account.journal' compute='_compute_journal_id' store=True string='Expense Journal'
  • message_main_attachment_id Many2one
    compute='_compute_main_attachment' store=True
  • name Char
    required=True string='Expense Report Summary' tracking=True
  • nb_account_move Integer
    compute='_compute_nb_account_move' string='Number of Journal Entries'
  • nb_expense Integer
    compute='_compute_nb_expense' string='Number of Expenses'
  • payment_method_line_id Many2one → account.payment.method.line
    comodel_name='account.payment.method.line' compute='_compute_payment_method_line_id' domain="[('id', 'in', selectable_payment_method_line_ids)]" help='The payment method used when the expense is paid by the company.' readonly=False store=True string='Payment Method'
  • payment_mode Selection
    readonly=True related='expense_line_ids.payment_mode' string='Paid By' tracking=True
  • payment_state Selection
    compute='_compute_from_account_move_ids' copy=False readonly=True selection=<expr> store=True string='Payment Status' tracking=True
  • product_ids Many2many → product.product
    check_company=True comodel_name='product.product' compute='_compute_product_ids' search='_search_product_ids' string='Categories'
  • selectable_payment_method_line_ids Many2many → account.payment.method.line
    comodel_name='account.payment.method.line' compute='_compute_selectable_payment_method_line_ids'
  • state Selection
    compute='_compute_state' copy=False default='draft' index=True readonly=True required=True selection=[('draft', 'To Submit'), ('submit', 'Submitted'), ('approve', 'Approved'), ('post', 'Posted'), ('done', 'Done'), ('cancel', 'Refused')] store=True string='Status' tracking=True
  • total_amount Monetary
    compute='_compute_amount' currency_field='company_currency_id' readonly=True store=True string='Total' tracking=True
  • total_tax_amount Monetary
    compute='_compute_amount' currency_field='company_currency_id' readonly=True store=True string='Taxes'
  • untaxed_amount Monetary
    compute='_compute_amount' currency_field='company_currency_id' readonly=True store=True string='Untaxed Amount'
  • user_id Many2one → res.users
    comodel_name='res.users' compute='_compute_from_employee_id' copy=False domain=<expr> readonly=True store=True string='Manager' tracking=True
Public methods (14)
  • action_approve_expense_sheets(self)
  • action_open_account_moves(self)
  • action_open_expense_view(self)
  • action_refuse_expense_sheets(self)
  • action_register_payment(self)
    Open the account.payment.register wizard to pay the selected journal entries. There can be more than one bank_account_id in the expense sheet when registering payment for multiple expenses. The default_partner_bank_id is set only if there is one available, if more than one the field is left empty. :return: An action opening the account.payment.register wizard.
  • action_reset_approval_expense_sheets(self)
  • action_reset_expense_sheets(self)
  • action_sheet_move_create(self)
  • action_submit_sheet(self)
  • activity_update(self)
  • create(self, vals_list)
    @api.model_create_multi
  • set_to_paid(self)
  • set_to_posted(self)
  • write(self, vals)

New fields (12)
  • company_id Many2one → res.company
    comodel_name='res.company'
  • currency_id Many2one → res.currency
    comodel_name='res.currency'
  • employee_id Many2one → hr.employee
    comodel_name='hr.employee' required=True string='Employee'
  • expense_id Many2one → hr.expense
    comodel_name='hr.expense' string='Expense'
  • name Char
    required=True string='Description'
  • product_has_cost Boolean
    compute='_compute_from_product_id' store=True string='Is product with non zero cost selected'
  • product_has_tax Boolean
    compute='_compute_product_has_tax' string='Whether tax is defined on a selected product'
  • product_id Many2one → product.product
    check_company=True comodel_name='product.product' domain=[('can_be_expensed', '=', True)] required=True string='Product'
  • tax_amount_currency Monetary
    compute='_compute_tax_amount_currency' string='Tax amount in Currency'
  • tax_ids Many2many → account.tax
    check_company=True comodel_name='account.tax' domain="[('type_tax_use', '=', 'purchase')]"
  • total_amount_currency Monetary
    compute='_compute_from_product_id' readonly=False required=True store=True string='Total In Currency'
  • wizard_id Many2one → hr.expense.split.wizard
    comodel_name='hr.expense.split.wizard'
Public methods (1)
  • default_get(self, fields)

New fields (7)
  • currency_id Many2one → res.currency
    comodel_name='res.currency' related='expense_id.currency_id'
  • expense_id Many2one → hr.expense
    comodel_name='hr.expense' required=True string='Expense'
  • expense_split_line_ids One2many → hr.expense.split
    comodel_name='hr.expense.split' inverse_name='wizard_id'
  • split_possible Boolean
    compute='_compute_split_possible' help='The sum of after split shut remain the same'
  • tax_amount_currency Monetary
    compute='_compute_tax_amount_currency' currency_field='currency_id' string='Taxes'
  • total_amount_currency Monetary
    compute='_compute_total_amount_currency' currency_field='currency_id' string='Total Amount'
  • total_amount_currency_original Monetary
    currency_field='currency_id' help='Total amount of the original Expense that we are splitting' related='expense_id.total_amount_currency' string='Total amount original'
Public methods (1)
  • action_split_expense(self)

New fields (0)

No new fields.

Public methods (0)

No public methods.

New fields (0)

No new fields.

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

New fields (1)
  • standard_price_update_warning Char
    compute='_compute_standard_price_update_warning'
Public methods (0)

No public methods.

New fields (1)
  • can_be_expensed Boolean
    compute='_compute_can_be_expensed' help='Specify whether the product can be selected in an expense.' readonly=False store=True string='Can be Expensed'
Public methods (1)
  • default_get(self, fields)
    @api.model

New fields (3)
  • company_expense_allowed_payment_method_line_ids Many2many → account.payment.method.line
    check_company=True domain="[('payment_type', '=', 'outbound'), ('journal_id', '!=', False), ('journal_id.active', '=', True)]" string='Payment methods available for expenses paid by company' args: 'account.payment.method.line'
  • expense_journal_id Many2one → account.journal
    check_company=True domain="[('type', '=', 'purchase')]" help="The company's default journal used when an employee expense is created." string='Default Expense Journal' args: 'account.journal'
  • expense_product_id Many2one → product.product
    check_company=True domain="[('can_be_expensed', '=', True)]" string='Default Expense Category' args: 'product.product'
Public methods (0)

No public methods.

New fields (8)
  • company_expense_allowed_payment_method_line_ids Many2many → account.payment.method.line
    check_company=True comodel_name='account.payment.method.line' readonly=False related='company_id.company_expense_allowed_payment_method_line_ids'
  • expense_journal_id Many2one → account.journal
    check_company=True readonly=False related='company_id.expense_journal_id' args: 'account.journal'
  • expense_product_id Many2one → product.product
    check_company=True readonly=False related='company_id.expense_product_id' args: 'product.product'
  • hr_expense_alias_domain_id Many2one → mail.alias.domain
    comodel_name='mail.alias.domain' compute='_compute_hr_expense_alias_domain_id' inverse='_inverse_hr_expense_alias_domain_id' readonly=False
  • hr_expense_alias_prefix Char
    compute='_compute_hr_expense_alias_prefix' readonly=False store=True args: 'Default Alias Name for Expenses'
  • hr_expense_use_mailgateway Boolean
    config_parameter='hr_expense.use_mailgateway' string='Let your employees record expenses by email'
  • module_hr_expense_extract Boolean
    string='Send bills to OCR to generate expenses'
  • module_hr_payroll_expense Boolean
    string='Reimburse Expenses in Payslip'
Public methods (2)
  • get_values(self)
    @api.model
  • set_values(self)

New fields (1)
  • expense_manager_id Many2one
    readonly=False related='employee_id.expense_manager_id'
Public methods (1)
  • SELF_READABLE_FIELDS(self)
    @property
REPOSITORY
REPOSITORYodoo/odoo
GIT
GIThttps://github.com/odoo/odoo.git
GIT FOLDER
GIT FOLDERhttps://github.com/odoo/odoo/tree/16.0/hr_expense
VERSION
VERSION 2.0
CATEGORY
CATEGORYHuman Resources/Expenses
LICENSE
LICENSELGPL-3
APPLICATION
APPLICATIONYes
AUTO-INSTALLABLE
AUTO-INSTALLABLENo
AUTHORS
AUTHORS
MAINTAINERS
MAINTAINERS
COMMITTERS
COMMITTERSRaphael Collet, Odoo Translation Bot, Martin Trigaux, Fabien Pinckaers, Thibault Delavallée, Denis Ledoux, Christophe Simonis, Goffin Simon, qdp-odoo, Aaron Bohy, Jeremy Kersten, GitHub, Damien Bouvy, Barad Mahendra, Yannick Tivisse, Laurent Smet, Jorge Pinna Puissant, wan, Romeo Fragomeli, Victor Feyens, Andrea Grazioso (agr-odoo), oco-odoo, Anh Thao Pham (pta), Achraf (abz), Brice bib Bartoletti, Nasreddin Boulif (bon), Tiffany Chang (tic), Gorash, Pierre Paridans, William Braeckman, Kevin Baptiste, Guillaume (guva), Philippe Wauthy, roen-odoo, Hubert Van de Walle (huvw), sofiagvaladze, Nicolas (vin), william-andre, Paolo (pgi), Víctor Martínez, Florian Damhaut, Merlin (megu), MerlinGuillaume, thcl-odoo, Didier (did), aliya, william, Florian Vranckx, Louis Baudoux, Yolann Sabaux, Abdelouahab (abla), John Laterre (jol), Thibault Libioulle, gawa-odoo, Hubert Van De Walle, Dossogne Bertrand, Roy Le, niyasraphy, Habib (ayh), Thomas Lefebvre (thle), alsh-odoo, Louis Wicket (wil), Maximilien (malb), Victor Piryns (pivi), Hamza (hisl), Thomas Becquevort (thbe), Gauthier Wala (gawa), Ali Alfie (alal), hieu, duongnguyen, Antoine Dupuis (andu), Antoine Boonen, Dylan Kiss (dyki), hupo-odoo, Bruno Boi, flvr-odoo, Julien Alardot (jual), Nam Dao, FrancoisGe, Leloup Loïc (lole), Nguyễn Đại Dương, Leonardo Pavan Rocha, Cameron, sesn-odoo, Paul Stroobant, stefanorigano (SRI), BHanq, ravb-odoo, Dang Van Loc, Seif (segn), yosa-odoo, Aktiv Software, det, mega, SaddemAmine, Martin Trigaux (mat), duongnguyen-viindoo, aath-odoo, csnauwaert, Andrea Gidalti, Ryan Cen, Antoine (anso), Mohammed Basioni, Maira Salazar (srma), Pierre Lamotte (pila), tdetry (det)
WEBSITE
WEBSITEhttps://www.odoo.com/app/expenses
LAST TRACKING UPDATE
LAST TRACKING UPDATE2026-07-06 00:40:49
ODOO DEPENDENCIES
ODOO DEPENDENCIES odoo/odoo:
    - hr_contract
    - hr
    - base_setup
    - base
    - web
    - mail
    - bus
    - web_tour
    - resource
    - account
    - product
    - uom
    - analytic
    - portal
    - web_editor
    - http_routing
    - auth_signup
    - digest
PYTHON DEPENDENCIES
PYTHON DEPENDENCIES Not have
SYSTEM DEPENDENCIES
SYSTEM DEPENDENCIES Not have
DESCRIPTION
DESCRIPTION
Manage expenses by Employees
============================

This application allows you to manage your employees' daily expenses. It gives you access to your employees’ fee notes and give you the right to complete and validate or refuse the notes. After validation it creates an invoice for the employee.
Employee can encode their own expenses and the validation flow puts it automatically in the accounting after validation by managers.


The whole flow is implemented as:
---------------------------------
* Draft expense
* Submitted by the employee to his manager
* Approved by his manager
* Validation by the accountant and accounting entries creation

This module also uses analytic accounting and is compatible with the invoice on timesheet module so that you are able to automatically re-invoice your customers' expenses if your work by project.
    

Code Analysis

Views touched (43)
XML IDNameModelTypeStatus
account_journal_dashboard_kanban_view_inherit_hr_expense account.journal.dashboard.kanban account.journal xpath Inherits account.account_journal_dashboard_kanban_view
hr_department_view_kanban hr.department.kanban.inherit hr.department data Inherits hr.hr_department_view_kanban
hr_employee_view_form_inherit_expense hr.employee.view.form.expense hr.employee xpath Inherits hr.view_employee_form
hr_expense_approve_duplicate_view_form hr.expense.approve.duplicate form New
hr_expense_kanban_view hr.expense.kanban hr.expense xpath Inherits hr_expense_view_expenses_analysis_kanban
hr_expense_kanban_view_header hr.expense.kanban hr.expense xpath Inherits hr_expense_view_expenses_analysis_kanban
hr_expense_refuse_wizard_view_form hr.expense.refuse.wizard.form hr.expense.refuse.wizard form New
hr_expense_sheet_view_activity hr.expense.sheet.activity hr.expense.sheet activity New
hr_expense_sheet_view_search hr.expense.sheet.view.search hr.expense.sheet search New
hr_expense_sheet_view_search_with_panel hr.expense.sheet.view.search.with.panel hr.expense.sheet xpath Inherits hr_expense_sheet_view_search
hr_expense_split Expense split hr.expense.split.wizard form New
hr_expense_template_refuse_reason hr_expense_template_refuse_reason ir.ui.view qweb New
hr_expense_template_register hr_expense_template_register ir.ui.view qweb New
hr_expense_template_register_no_user hr_expense_template_register_no_user ir.ui.view qweb New
hr_expense_view_activity hr.expense.activity hr.expense activity New
hr_expense_view_expenses_analysis_kanban hr.expense.kanban hr.expense kanban New
hr_expense_view_expenses_analysis_tree hr.expense.tree hr.expense tree New
hr_expense_view_form hr.expense.view.form hr.expense form New
hr_expense_view_form_without_header hr.expense.view.form hr.expense xpath Inherits hr_expense.hr_expense_view_form
hr_expense_view_graph hr.expense.graph hr.expense graph New
hr_expense_view_pivot hr.expense.pivot hr.expense pivot New
hr_expense_view_search hr.expense.view.search hr.expense search New
product_product_expense_categories_tree_view product.product.expense.categories.tree.view product.product tree New
product_product_expense_form_view product.product.expense.form product.product form New
product_product_expense_tree_view product.product.expense.tree product.product tree New
product_template_search_view_inherit_hr_expense product.template.search.view.inherit.hr_expense product.template filter Inherits product.product_template_search_view
report_expense_sheet report_expense_sheet ir.ui.view qweb New
res_config_settings_view_form res.config.settings.view.form.inherit.hr.expense res.config.settings xpath Inherits base.res_config_settings_view_form
res_users_view_form_preferences hr.user.preferences.form.inherit.hr.expense res.users xpath Inherits hr.res_users_view_form_profile
view_employee_tree_inherit_expense hr.employee.tree.expense hr.employee xpath Inherits hr.view_employee_tree
view_expenses_tree hr.expense.tree hr.expense xpath Inherits hr_expense_view_expenses_analysis_tree
view_hr_expense_sheet_dashboard_tree_header hr.expense.sheet.dashboard.tree hr.expense.sheet xpath Inherits hr_expense.view_hr_expense_sheet_tree
view_hr_expense_sheet_form hr.expense.sheet.form hr.expense.sheet form New
view_hr_expense_sheet_graph hr.expense.sheet.graph hr.expense.sheet graph New
view_hr_expense_sheet_kanban hr.expense.sheet.kanban hr.expense.sheet kanban New
view_hr_expense_sheet_kanban_header hr.expense.sheet.kanban hr.expense.sheet xpath Inherits view_hr_expense_sheet_kanban
view_hr_expense_sheet_kanban_no_header hr.expense.sheet.kanban hr.expense.sheet xpath Inherits view_hr_expense_sheet_kanban
view_hr_expense_sheet_pivot hr.expense.sheet.pivot hr.expense.sheet pivot New
view_hr_expense_sheet_tree hr.expense.sheet.tree hr.expense.sheet tree New
view_move_form_inherit_expense account.move.form.inherit account.move xpath Inherits account.view_move_form
view_my_expenses_tree hr.expense.tree hr.expense xpath Inherits hr_expense.view_expenses_tree
view_payment_form_inherit_expense account.payment.form.inherit account.payment xpath Inherits account.view_account_payment_form
view_product_hr_expense_form product.template.expense.form product.template div Inherits product.product_template_form_view
Models touched (22)

New fields (0)

No new fields.

Public methods (0)

No public methods.

New fields (1)
  • business_domain Selection
    ondelete={'expense': 'cascade'} selection_add=[('expense', 'Expense')]
Public methods (0)

No public methods.

New fields (0)

No new fields.

Public methods (2)
  • get_journal_dashboard_datas(self)
  • open_expenses_action(self)

New fields (1)
  • expense_sheet_id One2many → hr.expense.sheet
    args: 'hr.expense.sheet', 'account_move_id'
Public methods (3)
  • action_open_expense_report(self)
  • button_draft(self)
  • unlink(self)

New fields (1)
  • expense_id Many2one → hr.expense
    copy=True string='Expense' args: 'hr.expense'
Public methods (1)
  • reconcile(self)

New fields (0)

No new fields.

Public methods (3)
  • action_cancel(self)
  • action_draft(self)
  • action_open_expense_report(self)

New fields (0)

No new fields.

Public methods (0)

No public methods.

New fields (1)
  • expense_sheets_to_approve_count Integer
    compute='_compute_expense_sheets_to_approve' string='Expenses Reports to Approve'
Public methods (0)

No public methods.

New fields (1)
  • expense_manager_id Many2one → res.users
    compute='_compute_expense_manager' domain=_group_hr_expense_user_domain help='Select the user responsible for approving "Expenses" of this employee.\nIf empty, the approval is done by an Administrator or Approver (determined in settings/users).' readonly=False store=True string='Expense' args: 'res.users'
Public methods (0)

No public methods.

New fields (1)
  • expense_manager_id Many2one → res.users
    readonly=True args: 'res.users'
Public methods (0)

No public methods.

New fields (42)
  • account_id Many2one → account.account
    compute='_compute_account_id' domain="[('account_type', 'not in', ('asset_receivable','liability_payable','asset_cash','liability_credit_card')), ('company_id', '=', company_id)]" help='An expense account is expected' precompute=True readonly=False store=True string='Account' args: 'account.account'
  • accounting_date Date
    groups='account.group_account_invoice,account.group_account_readonly' related='sheet_id.accounting_date' store=True string='Accounting Date'
  • amount_residual Monetary
    compute='_compute_amount_residual' string='Amount Due'
  • amount_tax Monetary
    compute='_compute_amount_tax' currency_field='currency_id' help='Tax amount in currency' store=True string='Tax amount in Currency'
  • amount_tax_company Monetary
    compute='_compute_total_amount_company' currency_field='company_currency_id' help='Tax amount in company currency' store=True args: 'Tax amount'
  • approved_by Many2one → res.users
    related='sheet_id.user_id' string='Approved By' tracking=False args: 'res.users'
  • approved_on Datetime
    related='sheet_id.approval_date' string='Approved On'
  • attachment_ids One2many → ir.attachment
    domain="[('res_model', '=', 'hr.expense')]" string='Attachments' args: 'ir.attachment', 'res_id'
  • attachment_number Integer
    compute='_compute_attachment_number' args: 'Number of Attachments'
  • company_currency_id Many2one → res.currency
    readonly=True related='company_id.currency_id' string='Report Company Currency' args: 'res.currency'
  • company_id Many2one → res.company
    default=<expr> readonly=True required=True states={'draft': [('readonly', False)], 'refused': [('readonly', False)]} string='Company' args: 'res.company'
  • currency_id Many2one → res.currency
    compute='_compute_currency_id' default=<expr> readonly=False required=True states={'reported': [('readonly', True)], 'approved': [('readonly', True)], 'done': [('readonly', True)]} store=True string='Currency' args: 'res.currency'
  • currency_rate Float
    compute='_compute_currency_rate'
  • date Date
    default=fields.Date.context_today states={'done': [('readonly', True)]} string='Expense Date'
  • description Text
    readonly=True states={'draft': [('readonly', False)], 'reported': [('readonly', False)], 'refused': [('readonly', False)]} args: 'Internal Notes'
  • duplicate_expense_ids Many2many → hr.expense
    compute='_compute_duplicate_expense_ids' args: 'hr.expense'
  • employee_id Many2one → hr.employee
    check_company=True compute='_compute_employee_id' default=_default_employee_id domain=<expr> readonly=False required=True states={'approved': [('readonly', True)], 'done': [('readonly', True)]} store=True string='Employee' tracking=True args: 'hr.employee'
  • is_editable Boolean
    compute='_compute_is_editable' args: 'Is Editable By Current User'
  • is_ref_editable Boolean
    compute='_compute_is_ref_editable' args: 'Reference Is Editable By Current User'
  • is_refused Boolean
    copy=False readonly=True args: 'Explicitly Refused by manager or accountant'
  • label_convert_rate Char
    compute='_compute_label_convert_rate'
  • name Char
    compute='_compute_name' copy=True precompute=True readonly=False required=True states={'done': [('readonly', True)]} store=True args: 'Description'
  • payment_mode Selection
    default='own_account' states={'done': [('readonly', True)], 'approved': [('readonly', True)], 'reported': [('readonly', True)]} string='Paid By' tracking=True args: [('own_account', 'Employee (to reimburse)'), ('company_account', 'Company')]
  • product_description Html
    compute='_compute_product_description'
  • product_has_cost Boolean
    compute='_compute_product_has_cost' args: 'Is product with non zero cost selected'
  • product_has_tax Boolean
    compute='_compute_product_has_cost' args: 'Whether tax is defined on a selected product'
  • product_id Many2one → product.product
    domain="[('can_be_expensed', '=', True), '|', ('company_id', '=', False), ('company_id', '=', company_id)]" ondelete='restrict' states={'done': [('readonly', True)]} string='Category' tracking=True args: 'product.product'
  • product_uom_category_id Many2one
    readonly=True related='product_id.uom_id.category_id' string='UoM Category'
  • product_uom_id Many2one → uom.uom
    compute='_compute_product_uom_id' copy=True domain="[('category_id', '=', product_uom_category_id)]" precompute=True readonly=True store=True string='Unit of Measure' args: 'uom.uom'
  • quantity Float
    default=1 digits='Product Unit of Measure' required=True states={'done': [('readonly', True)]}
  • reference Char
    args: 'Bill Reference'
  • same_currency Boolean
    compute='_compute_same_currency' args: 'Is currency_id different from the company_currency_id'
  • sample Boolean
  • sheet_id Many2one → hr.expense.sheet
    copy=False domain="[('employee_id', '=', employee_id), ('company_id', '=', company_id)]" readonly=True string='Expense Report' args: 'hr.expense.sheet'
  • sheet_is_editable Boolean
    compute='_compute_sheet_is_editable'
  • state Selection
    compute='_compute_state' copy=False default='draft' index=True readonly=True store=True string='Status' args: [('draft', 'To Submit'), ('reported', 'Submitted'), ('approved', 'Approved'), ('done', 'Done'), ('refused', 'Refused')]
  • tax_ids Many2many → account.tax
    compute='_compute_tax_ids' domain="[('company_id', '=', company_id), ('type_tax_use', '=', 'purchase')]" help='Both price-included and price-excluded taxes will behave as price-included taxes for expenses.' precompute=True readonly=False store=True string='Included taxes' args: 'account.tax', 'expense_tax', 'expense_id', 'tax_id'
  • total_amount Monetary
    compute='_compute_amount' currency_field='currency_id' inverse='_inverse_total_amount' readonly=False store=True tracking=True args: 'Total In Currency'
  • total_amount_company Monetary
    compute='_compute_total_amount_company' currency_field='company_currency_id' store=True args: 'Total'
  • unit_amount Float
    compute='_compute_unit_amount' copy=True digits='Product Price' precompute=True readonly=False required=True states={'done': [('readonly', True)]} store=True args: 'Unit Price'
  • unit_amount_display Float
    compute='_compute_unit_amount_display' digits='Product Price' args: 'Unit Price Display'
  • untaxed_amount Monetary
    compute='_compute_amount_tax' currency_field='currency_id' store=True args: 'Total Untaxed Amount In Currency'
Public methods (15)
  • action_approve_duplicates(self)
  • action_get_attachment_view(self)
  • action_move_create(self)
    main function that is called when trying to create the accounting entries related to an expense
  • action_split_wizard(self)
  • action_submit_expenses(self)
  • action_view_sheet(self)
  • attach_document(self, **kwargs)
  • create_expense_from_attachments(self, attachment_ids=None, view_type='list')
    Create the expenses from files. :return: An action redirecting to hr.expense tree view.
  • get_empty_list_help(self, help_message)
    @api.model
  • get_expense_dashboard(self)
    @api.model
  • get_expenses_to_submit(self)
  • message_new(self, msg_dict, custom_values=None)
    @api.model
  • refuse_expense(self, reason)
  • unlink(self)
  • write(self, vals)

New fields (2)
  • expense_ids Many2many → hr.expense
    readonly=True args: 'hr.expense'
  • sheet_ids Many2many → hr.expense.sheet
    args: 'hr.expense.sheet'
Public methods (3)
  • action_approve(self)
  • action_refuse(self)
  • default_get(self, fields)
    @api.model

New fields (3)
  • hr_expense_ids Many2many → hr.expense
    args: 'hr.expense'
  • hr_expense_sheet_id Many2one → hr.expense.sheet
    args: 'hr.expense.sheet'
  • reason Char
    required=True string='Reason'
Public methods (2)
  • default_get(self, fields)
    @api.model
  • expense_refuse_reason(self)

New fields (30)
  • account_move_id Many2one → account.move
    copy=False ondelete='restrict' readonly=True string='Journal Entry' args: 'account.move'
  • accounting_date Date
    compute='_compute_accounting_date' store=True string='Accounting Date'
  • address_id Many2one → res.partner
    check_company=True compute='_compute_from_employee_id' copy=True readonly=False store=True string='Employee Home Address' args: 'res.partner'
  • amount_residual Monetary
    currency_field='currency_id' related='account_move_id.amount_residual' store=True string='Amount Due'
  • approval_date Datetime
    readonly=True args: 'Approval Date'
  • attachment_ids One2many → ir.attachment
    domain="[('res_model', '=', 'hr.expense.sheet')]" string='Attachments of expenses' args: 'ir.attachment', 'res_id'
  • attachment_number Integer
    compute='_compute_attachment_number' string='Number of Attachments'
  • bank_journal_id Many2one → account.journal
    check_company=True default=_default_bank_journal_id domain="[('type', 'in', ['cash', 'bank']), ('company_id', '=', company_id)]" help='The payment method used when the expense is paid by the company.' states={'done': [('readonly', True)], 'post': [('readonly', True)]} string='Bank Journal' args: 'account.journal'
  • can_approve Boolean
    compute='_compute_can_approve' args: 'Can Approve'
  • can_reset Boolean
    compute='_compute_can_reset' args: 'Can Reset'
  • company_id Many2one → res.company
    default=<expr> readonly=True required=True states={'draft': [('readonly', False)]} string='Company' args: 'res.company'
  • currency_id Many2one → res.currency
    compute='_compute_currency_id' readonly=True states={'draft': [('readonly', False)]} store=True string='Currency' args: 'res.currency'
  • department_id Many2one → hr.department
    compute='_compute_from_employee_id' copy=False readonly=False states={'post': [('readonly', True)], 'done': [('readonly', True)]} store=True string='Department' args: 'hr.department'
  • employee_id Many2one → hr.employee
    check_company=True default=_default_employee_id domain=<expr> readonly=True required=True states={'draft': [('readonly', False)]} string='Employee' tracking=True args: 'hr.employee'
  • expense_line_ids One2many → hr.expense
    copy=False string='Expense Lines' args: 'hr.expense', 'sheet_id'
  • expense_number Integer
    compute='_compute_expense_number' string='Number of Expenses'
  • is_editable Boolean
    compute='_compute_is_editable' args: 'Expense Lines Are Editable By Current User'
  • is_multiple_currency Boolean
    compute='_compute_is_multiple_currency' args: 'Handle lines with different currencies'
  • journal_displayed_id Many2one → account.journal
    compute='_compute_journal_displayed_id' string='Journal' args: 'account.journal'
  • journal_id Many2one → account.journal
    check_company=True default=_default_journal_id domain="[('type', '=', 'purchase'), ('company_id', '=', company_id)]" help='The journal used when the expense is done.' states={'done': [('readonly', True)], 'post': [('readonly', True)]} string='Expense Journal' args: 'account.journal'
  • message_main_attachment_id Many2one
    compute='_compute_main_attachment' store=True
  • name Char
    required=True tracking=True args: 'Expense Report Summary'
  • payment_mode Selection
    readonly=True related='expense_line_ids.payment_mode' string='Paid By' tracking=True
  • payment_state Selection
    compute='_compute_payment_state' copy=False readonly=True store=True string='Payment Status' tracking=True args: <expr>
  • product_ids Many2many → product.product
    compute='_compute_product_ids' search='_search_product_ids' string='Categories' args: 'product.product'
  • state Selection
    copy=False default='draft' index=True readonly=True required=True string='Status' tracking=True args: [('draft', 'Draft'), ('submit', 'Submitted'), ('approve', 'Approved'), ('post', 'Posted'), ('done', 'Done'), ('cancel', 'Refused')]
  • total_amount Monetary
    compute='_compute_amount' currency_field='currency_id' store=True tracking=True args: 'Total Amount'
  • total_amount_taxes Monetary
    compute='_compute_amount' currency_field='currency_id' store=True args: 'Taxes'
  • untaxed_amount Monetary
    compute='_compute_amount' currency_field='currency_id' store=True args: 'Untaxed Amount'
  • user_id Many2one → res.users
    compute='_compute_from_employee_id' copy=False domain=<expr> readonly=True states={'draft': [('readonly', False)]} store=True tracking=True args: 'res.users', 'Manager'
Public methods (15)
  • action_get_attachment_view(self)
  • action_get_expense_view(self)
  • action_open_account_move(self)
  • action_register_payment(self)
    Open the account.payment.register wizard to pay the selected journal entries. There can be more than one bank_account_id in the expense sheet when registering payment for multiple expenses. The default_partner_bank_id is set only if there is one available, if more than one the field is left empty. :return: An action opening the account.payment.register wizard.
  • action_sheet_move_create(self)
  • action_submit_sheet(self)
  • action_unpost(self)
  • activity_update(self)
  • approve_expense_sheets(self)
  • create(self, vals_list)
    @api.model_create_multi
  • paid_expense_sheets(self)
  • refuse_sheet(self, reason)
  • reset_expense_sheets(self)
  • set_to_paid(self)
  • write(self, vals)

New fields (12)
  • amount_tax Monetary
    compute='_compute_amount_tax' string='Tax amount in Currency'
  • company_id Many2one → res.company
    args: 'res.company'
  • currency_id Many2one → res.currency
    args: 'res.currency'
  • employee_id Many2one → hr.employee
    required=True string='Employee' args: 'hr.employee'
  • expense_id Many2one → hr.expense
    string='Expense' args: 'hr.expense'
  • name Char
    required=True args: 'Description'
  • product_has_cost Boolean
    compute='_compute_from_product_id' store=True args: 'Is product with non zero cost selected'
  • product_has_tax Boolean
    compute='_compute_product_has_tax' args: 'Whether tax is defined on a selected product'
  • product_id Many2one → product.product
    domain="[('can_be_expensed', '=', True), '|', ('company_id', '=', False), ('company_id', '=', company_id)]" required=True string='Product' args: 'product.product'
  • tax_ids Many2many → account.tax
    domain="[('company_id', '=', company_id), ('type_tax_use', '=', 'purchase')]" args: 'account.tax'
  • total_amount Monetary
    compute='_compute_from_product_id' readonly=False required=True store=True args: 'Total In Currency'
  • wizard_id Many2one → hr.expense.split.wizard
    args: 'hr.expense.split.wizard'
Public methods (1)
  • default_get(self, fields)

New fields (7)
  • currency_id Many2one → res.currency
    related='expense_id.currency_id' args: 'res.currency'
  • expense_id Many2one → hr.expense
    required=True string='Expense' args: 'hr.expense'
  • expense_split_line_ids One2many → hr.expense.split
    args: 'hr.expense.split', 'wizard_id'
  • split_possible Boolean
    compute='_compute_split_possible' help='The sum of after split shut remain the same'
  • total_amount Monetary
    compute='_compute_total_amount' currency_field='currency_id' args: 'Total Amount'
  • total_amount_original Monetary
    currency_field='currency_id' help='Total amount of the original Expense that we are splitting' related='expense_id.total_amount' args: 'Total amount original'
  • total_amount_taxes Monetary
    compute='_compute_total_amount_taxes' currency_field='currency_id' args: 'Taxes'
Public methods (1)
  • action_split_expense(self)

New fields (0)

No new fields.

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

New fields (1)
  • standard_price_update_warning Char
    compute='_compute_standard_price_update_warning'
Public methods (0)

No public methods.

New fields (1)
  • can_be_expensed Boolean
    compute='_compute_can_be_expensed' help='Specify whether the product can be selected in an expense.' readonly=False store=True string='Can be Expensed'
Public methods (1)
  • default_get(self, fields)
    @api.model

New fields (2)
  • company_expense_journal_id Many2one → account.journal
    check_company=True domain="[('type', 'in', ['cash', 'bank']), ('company_id', '=', company_id)]" help="The company's default journal used when a company expense is created." string='Default Company Expense Journal' args: 'account.journal'
  • expense_journal_id Many2one → account.journal
    check_company=True domain="[('type', '=', 'purchase'), ('company_id', '=', company_id)]" help="The company's default journal used when an employee expense is created." string='Default Expense Journal' args: 'account.journal'
Public methods (0)

No public methods.

New fields (6)
  • company_expense_journal_id Many2one → account.journal
    readonly=False related='company_id.company_expense_journal_id' args: 'account.journal'
  • expense_alias_prefix Char
    compute='_compute_expense_alias_prefix' readonly=False store=True args: 'Default Alias Name for Expenses'
  • expense_journal_id Many2one → account.journal
    readonly=False related='company_id.expense_journal_id' args: 'account.journal'
  • module_hr_expense_extract Boolean
    string='Send bills to OCR to generate expenses'
  • module_hr_payroll_expense Boolean
    string='Reimburse Expenses in Payslip'
  • use_mailgateway Boolean
    config_parameter='hr_expense.use_mailgateway' string='Let your employees record expenses by email'
Public methods (2)
  • get_values(self)
    @api.model
  • set_values(self)

New fields (1)
  • expense_manager_id Many2one
    readonly=False related='employee_id.expense_manager_id'
Public methods (1)
  • SELF_READABLE_FIELDS(self)
    @property
REPOSITORY
REPOSITORYodoo/odoo
GIT
GIThttps://github.com/odoo/odoo.git
GIT FOLDER
GIT FOLDERhttps://github.com/odoo/odoo/tree/15.0/hr_expense
VERSION
VERSION 2.0
CATEGORY
CATEGORYHuman Resources/Expenses
LICENSE
LICENSELGPL-3
APPLICATION
APPLICATIONYes
AUTO-INSTALLABLE
AUTO-INSTALLABLENo
AUTHORS
AUTHORS
MAINTAINERS
MAINTAINERS
COMMITTERS
COMMITTERSRaphael Collet, Odoo Translation Bot, Martin Trigaux, Thibault Delavallée, Christophe Simonis, Xavier Morel, Goffin Simon, Aaron Bohy, GitHub, Barad Mahendra, Yannick Tivisse, Dharmraj Jhala, Laurent Smet, wan, Julien Castiaux, Xavier-Do, Victor Feyens, Andrea Grazioso (agr-odoo), Sébastien Theys, alt-odoo, oco-odoo, Julien Mougenot, Achraf (abz), Brice bib Bartoletti, Adrien Widart, Nasreddin Boulif (bon), Touati Djamel (otd), Kamesh Patel, Swapnesh Shah, Tiffany Chang (tic), IEL, Mathieu Duckerts-Antoine, Pierre Paridans, jbw, jbm-odoo, Aurélien (avd), Kevin Baptiste, Guillaume (guva), roen-odoo, sofiagvaladze, Nicolas (vin), william-andre, Víctor Martínez, Florian Damhaut, Merlin (megu), thcl-odoo, Florian Vranckx, Nshimiyimana Séna, Yolann Sabaux, Florian Gilbert, Jinjiu Liu, Laurent Stukkens (LTU), Michael Mattiello (mcm), dht-odoo, Habib (ayh), Thomas Lefebvre (thle), mehjabinfarsana, alsh-odoo, Adrien Widart (awt), Maximilien (malb), Victor Piryns (pivi), Hamza (hisl), Giacomello Nathan (ngi), Gauthier Wala (gawa), clesgow, hieu, Antoine Boonen, Bruno Boi, ntlquynh2209, lathuat1997, flvr-odoo, Paolo Gatti (pgi), Kairis, Julien Alardot (jual), Nam Dao, Kartik Chavda, tuyenphung, Leonardo Pavan Rocha, Cameron, Paul Stroobant, BHanq, Seif (segn), Aktiv Software, det
WEBSITE
WEBSITEhttps://www.odoo.com/app/expenses
LAST TRACKING UPDATE
LAST TRACKING UPDATE2026-07-06 00:25:28
ODOO DEPENDENCIES
ODOO DEPENDENCIES odoo/odoo:
    - hr_contract
    - hr
    - base_setup
    - base
    - web
    - mail
    - bus
    - web_tour
    - resource
    - account
    - product
    - uom
    - analytic
    - portal
    - web_editor
    - http_routing
    - auth_signup
    - digest
PYTHON DEPENDENCIES
PYTHON DEPENDENCIES Not have
SYSTEM DEPENDENCIES
SYSTEM DEPENDENCIES Not have
DESCRIPTION
DESCRIPTION
Manage expenses by Employees
============================

This application allows you to manage your employees' daily expenses. It gives you access to your employees’ fee notes and give you the right to complete and validate or refuse the notes. After validation it creates an invoice for the employee.
Employee can encode their own expenses and the validation flow puts it automatically in the accounting after validation by managers.


The whole flow is implemented as:
---------------------------------
* Draft expense
* Submitted by the employee to his manager
* Approved by his manager
* Validation by the accountant and accounting entries creation

This module also uses analytic accounting and is compatible with the invoice on timesheet module so that you are able to automatically re-invoice your customers' expenses if your work by project.
    

Code Analysis

Views touched (38)
XML IDNameModelTypeStatus
account_journal_dashboard_kanban_view_inherit_hr_expense account.journal.dashboard.kanban account.journal xpath Inherits account.account_journal_dashboard_kanban_view
hr_department_view_kanban hr.department.kanban.inherit hr.department data Inherits hr.hr_department_view_kanban
hr_employee_view_form_inherit_expense hr.employee.view.form.expense hr.employee xpath Inherits hr.view_employee_form
hr_expense_approve_duplicate_view_form hr.expense.approve.duplicate form New
hr_expense_kanban_view hr.expense.kanban hr.expense xpath Inherits hr_expense_view_expenses_analysis_kanban
hr_expense_kanban_view_header hr.expense.kanban hr.expense xpath Inherits hr_expense_view_expenses_analysis_kanban
hr_expense_refuse_wizard_view_form hr.expense.refuse.wizard.form hr.expense.refuse.wizard form New
hr_expense_sheet_view_activity hr.expense.sheet.activity hr.expense.sheet activity New
hr_expense_sheet_view_search hr.expense.sheet.view.search hr.expense.sheet search New
hr_expense_template_refuse_reason hr_expense_template_refuse_reason ir.ui.view qweb New
hr_expense_template_register hr_expense_template_register ir.ui.view qweb New
hr_expense_template_register_no_user hr_expense_template_register_no_user ir.ui.view qweb New
hr_expense_view_activity hr.expense.activity hr.expense activity New
hr_expense_view_expenses_analysis_kanban hr.expense.kanban hr.expense kanban New
hr_expense_view_expenses_analysis_tree hr.expense.tree hr.expense tree New
hr_expense_view_form hr.expense.view.form hr.expense form New
hr_expense_view_form_without_header hr.expense.view.form hr.expense xpath Inherits hr_expense.hr_expense_view_form
hr_expense_view_graph hr.expense.graph hr.expense graph New
hr_expense_view_pivot hr.expense.pivot hr.expense pivot New
hr_expense_view_search hr.expense.view.search hr.expense search New
product_product_expense_form_view product.product.expense.form product.product form New
product_product_expense_tree_view product.product.expense.tree product.product tree New
product_template_search_view_inherit_hr_expense product.template.search.view.inherit.hr_expense product.template filter Inherits product.product_template_search_view
report_expense_sheet report_expense_sheet ir.ui.view qweb New
res_config_settings_view_form res.config.settings.view.form.inherit.hr.expense res.config.settings xpath Inherits base.res_config_settings_view_form
res_users_view_form_preferences hr.user.preferences.form.inherit.hr.expense res.users xpath Inherits hr.res_users_view_form_profile
view_employee_tree_inherit_expense hr.employee.tree.expense hr.employee xpath Inherits hr.view_employee_tree
view_expenses_tree hr.expense.tree hr.expense xpath Inherits hr_expense_view_expenses_analysis_tree
view_hr_expense_sheet_dashboard_tree hr.expense.sheet.dashboard.tree hr.expense.sheet xpath Inherits hr_expense.view_hr_expense_sheet_tree
view_hr_expense_sheet_dashboard_tree_header hr.expense.sheet.dashboard.tree hr.expense.sheet xpath Inherits hr_expense.view_hr_expense_sheet_tree
view_hr_expense_sheet_form hr.expense.sheet.form hr.expense.sheet form New
view_hr_expense_sheet_graph hr.expense.sheet.graph hr.expense.sheet graph New
view_hr_expense_sheet_kanban hr.expense.sheet.kanban hr.expense.sheet kanban New
view_hr_expense_sheet_kanban_header hr.expense.sheet.kanban hr.expense.sheet xpath Inherits view_hr_expense_sheet_kanban
view_hr_expense_sheet_kanban_no_header hr.expense.sheet.kanban hr.expense.sheet xpath Inherits view_hr_expense_sheet_kanban
view_hr_expense_sheet_pivot hr.expense.sheet.pivot hr.expense.sheet pivot New
view_hr_expense_sheet_tree hr.expense.sheet.tree hr.expense.sheet tree New
view_my_expenses_tree hr.expense.tree hr.expense xpath Inherits hr_expense.view_expenses_tree
Models touched (15)

New fields (0)

No new fields.

Public methods (2)
  • get_journal_dashboard_datas(self)
  • open_expenses_action(self)

New fields (0)

No new fields.

Public methods (0)

No public methods.

New fields (0)

No new fields.

Public methods (2)
  • button_cancel(self)
  • button_draft(self)

New fields (1)
  • expense_id Many2one → hr.expense
    copy=False help='Expense where the move line come from' string='Expense' args: 'hr.expense'
Public methods (1)
  • reconcile(self)

New fields (0)

No new fields.

Public methods (0)

No public methods.

New fields (1)
  • expense_sheets_to_approve_count Integer
    compute='_compute_expense_sheets_to_approve' string='Expenses Reports to Approve'
Public methods (0)

No public methods.

New fields (1)
  • expense_manager_id Many2one → res.users
    compute='_compute_expense_manager' domain=_group_hr_expense_user_domain help='Select the user responsible for approving "Expenses" of this employee.\nIf empty, the approval is done by an Administrator or Approver (determined in settings/users).' readonly=False store=True string='Expense' args: 'res.users'
Public methods (0)

No public methods.

New fields (1)
  • expense_manager_id Many2one → res.users
    readonly=True args: 'res.users'
Public methods (0)

No public methods.

New fields (38)
  • account_id Many2one → account.account
    compute='_compute_from_product_id_company_id' default=_default_account_id domain="[('internal_type', '=', 'other'), ('company_id', '=', company_id)]" help='An expense account is expected' readonly=False store=True string='Account' args: 'account.account'
  • accounting_date Date
    groups='account.group_account_invoice,account.group_account_readonly' related='sheet_id.accounting_date' store=True string='Accounting Date'
  • amount_residual Monetary
    compute='_compute_amount_residual' string='Amount Due'
  • analytic_account_id Many2one → account.analytic.account
    check_company=True string='Analytic Account' args: 'account.analytic.account'
  • analytic_tag_ids Many2many → account.analytic.tag
    domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]" states={'post': [('readonly', True)], 'done': [('readonly', True)]} string='Analytic Tags' args: 'account.analytic.tag'
  • approved_by Many2one → res.users
    related='sheet_id.user_id' string='Approved By' args: 'res.users'
  • approved_on Datetime
    related='sheet_id.approval_date' string='Approved On'
  • attachment_number Integer
    compute='_compute_attachment_number' args: 'Number of Attachments'
  • company_currency_id Many2one → res.currency
    readonly=True related='company_id.currency_id' string='Report Company Currency' args: 'res.currency'
  • company_id Many2one → res.company
    default=<expr> readonly=True required=True states={'draft': [('readonly', False)], 'refused': [('readonly', False)]} string='Company' args: 'res.company'
  • currency_id Many2one → res.currency
    compute='_compute_currency_id' default=<expr> readonly=False required=True states={'reported': [('readonly', True)], 'approved': [('readonly', True)], 'done': [('readonly', True)]} store=True string='Currency' args: 'res.currency'
  • date Date
    default=fields.Date.context_today readonly=True states={'draft': [('readonly', False)], 'reported': [('readonly', False)], 'approved': [('readonly', False)], 'refused': [('readonly', False)]} string='Expense Date'
  • description Text
    readonly=True states={'draft': [('readonly', False)], 'reported': [('readonly', False)], 'refused': [('readonly', False)]} args: 'Notes...'
  • duplicate_expense_ids Many2many → hr.expense
    compute='_compute_duplicate_expense_ids' args: 'hr.expense'
  • employee_id Many2one → hr.employee
    check_company=True compute='_compute_employee_id' default=_default_employee_id domain=<expr> readonly=False required=True states={'approved': [('readonly', True)], 'done': [('readonly', True)]} store=True string='Employee' tracking=True args: 'hr.employee'
  • is_editable Boolean
    compute='_compute_is_editable' args: 'Is Editable By Current User'
  • is_ref_editable Boolean
    compute='_compute_is_ref_editable' args: 'Reference Is Editable By Current User'
  • is_refused Boolean
    copy=False readonly=True args: 'Explicitly Refused by manager or accountant'
  • label_convert_rate Char
    compute='_compute_label_convert_rate'
  • label_total_amount_company Char
    compute='_compute_label_total_amount_company'
  • name Char
    compute='_compute_from_product_id_company_id' copy=True required=True states={'draft': [('readonly', False)], 'reported': [('readonly', False)], 'approved': [('readonly', False)], 'refused': [('readonly', False)]} store=True args: 'Description'
  • payment_mode Selection
    default='own_account' states={'done': [('readonly', True)], 'approved': [('readonly', True)], 'reported': [('readonly', True)]} string='Paid By' tracking=True args: [('own_account', 'Employee (to reimburse)'), ('company_account', 'Company')]
  • product_has_cost Boolean
    compute='_compute_product_has_cost' args: 'Is product with non zero cost selected'
  • product_id Many2one → product.product
    domain="[('can_be_expensed', '=', True), '|', ('company_id', '=', False), ('company_id', '=', company_id)]" ondelete='restrict' readonly=True states={'draft': [('readonly', False)], 'reported': [('readonly', False)], 'approved': [('readonly', False)], 'refused': [('readonly', False)]} string='Product' tracking=True args: 'product.product'
  • product_uom_category_id Many2one
    readonly=True related='product_id.uom_id.category_id' string='UoM Category'
  • product_uom_id Many2one → uom.uom
    compute='_compute_from_product_id_company_id' copy=True default=_default_product_uom_id domain="[('category_id', '=', product_uom_category_id)]" readonly=True store=True string='Unit of Measure' args: 'uom.uom'
  • quantity Float
    default=1 digits='Product Unit of Measure' readonly=True required=True states={'draft': [('readonly', False)], 'reported': [('readonly', False)], 'approved': [('readonly', False)], 'refused': [('readonly', False)]}
  • reference Char
    args: 'Bill Reference'
  • same_currency Boolean
    compute='_compute_same_currency' args: 'Is currency_id different from the company_currency_id'
  • sample Boolean
  • sheet_id Many2one → hr.expense.sheet
    copy=False domain="[('employee_id', '=', employee_id), ('company_id', '=', company_id)]" readonly=True string='Expense Report' args: 'hr.expense.sheet'
  • sheet_is_editable Boolean
    compute='_compute_sheet_is_editable'
  • state Selection
    compute='_compute_state' copy=False default='draft' help='Status of the expense.' index=True readonly=True store=True string='Status' args: [('draft', 'To Submit'), ('reported', 'Submitted'), ('approved', 'Approved'), ('done', 'Paid'), ('refused', 'Refused')]
  • tax_ids Many2many → account.tax
    compute='_compute_from_product_id_company_id' domain="[('company_id', '=', company_id), ('type_tax_use', '=', 'purchase'), ('price_include', '=', True)]" help='The taxes should be "Included In Price"' readonly=False store=True string='Taxes' args: 'account.tax', 'expense_tax', 'expense_id', 'tax_id'
  • total_amount Monetary
    compute='_compute_amount' currency_field='currency_id' readonly=False store=True tracking=True args: 'Total In Currency'
  • total_amount_company Monetary
    compute='_compute_total_amount_company' currency_field='company_currency_id' store=True args: 'Total'
  • unit_amount Float
    compute='_compute_from_product_id_company_id' copy=True digits='Product Price' required=True states={'draft': [('readonly', False)], 'reported': [('readonly', False)], 'approved': [('readonly', False)], 'refused': [('readonly', False)]} store=True args: 'Unit Price'
  • untaxed_amount Float
    compute='_compute_amount_tax' copy=True digits='Account' store=True args: 'Subtotal'
Public methods (12)
  • action_approve_duplicates(self)
  • action_get_attachment_view(self)
  • action_move_create(self)
    main function that is called when trying to create the accounting entries related to an expense
  • action_submit_expenses(self)
  • action_view_sheet(self)
  • attach_document(self, **kwargs)
  • create_expense_from_attachments(self, attachment_ids=None, view_type='tree')
    Create the expenses from files. :return: An action redirecting to hr.expense tree view.
  • get_empty_list_help(self, help_message)
    @api.model
  • get_expense_dashboard(self)
    @api.model
  • message_new(self, msg_dict, custom_values=None)
    @api.model
  • refuse_expense(self, reason)
  • write(self, vals)

New fields (2)
  • expense_ids Many2many → hr.expense
    readonly=True args: 'hr.expense'
  • sheet_ids Many2many → hr.expense.sheet
    args: 'hr.expense.sheet'
Public methods (3)
  • action_approve(self)
  • action_refuse(self)
  • default_get(self, fields)
    @api.model

New fields (3)
  • hr_expense_ids Many2many → hr.expense
    args: 'hr.expense'
  • hr_expense_sheet_id Many2one → hr.expense.sheet
    args: 'hr.expense.sheet'
  • reason Char
    required=True string='Reason'
Public methods (2)
  • default_get(self, fields)
    @api.model
  • expense_refuse_reason(self)

New fields (23)
  • account_move_id Many2one → account.move
    copy=False ondelete='restrict' readonly=True string='Journal Entry' args: 'account.move'
  • accounting_date Date
    args: 'Accounting Date'
  • address_id Many2one → res.partner
    check_company=True compute='_compute_from_employee_id' copy=True readonly=False store=True string='Employee Home Address' args: 'res.partner'
  • amount_residual Monetary
    compute='_compute_amount_residual' currency_field='currency_id' store=True string='Amount Due'
  • approval_date Datetime
    readonly=True args: 'Approval Date'
  • attachment_number Integer
    compute='_compute_attachment_number' string='Number of Attachments'
  • bank_journal_id Many2one → account.journal
    check_company=True default=_default_bank_journal_id domain="[('type', 'in', ['cash', 'bank']), ('company_id', '=', company_id)]" help='The payment method used when the expense is paid by the company.' states={'done': [('readonly', True)], 'post': [('readonly', True)]} string='Bank Journal' args: 'account.journal'
  • can_approve Boolean
    compute='_compute_can_approve' args: 'Can Approve'
  • can_reset Boolean
    compute='_compute_can_reset' args: 'Can Reset'
  • company_id Many2one → res.company
    default=<expr> readonly=True required=True states={'draft': [('readonly', False)]} string='Company' args: 'res.company'
  • currency_id Many2one → res.currency
    default=<expr> readonly=True states={'draft': [('readonly', False)]} string='Currency' args: 'res.currency'
  • department_id Many2one → hr.department
    compute='_compute_from_employee_id' copy=False readonly=False states={'post': [('readonly', True)], 'done': [('readonly', True)]} store=True string='Department' args: 'hr.department'
  • employee_id Many2one → hr.employee
    check_company=True default=_default_employee_id domain=<expr> readonly=True required=True states={'draft': [('readonly', False)]} string='Employee' tracking=True args: 'hr.employee'
  • expense_line_ids One2many → hr.expense
    copy=False string='Expense Lines' args: 'hr.expense', 'sheet_id'
  • is_editable Boolean
    compute='_compute_is_editable' args: 'Expense Lines Are Editable By Current User'
  • is_multiple_currency Boolean
    compute='_compute_is_multiple_currency' args: 'Handle lines with different currencies'
  • journal_id Many2one → account.journal
    check_company=True default=_default_journal_id domain="[('type', '=', 'purchase'), ('company_id', '=', company_id)]" help='The journal used when the expense is done.' states={'done': [('readonly', True)], 'post': [('readonly', True)]} string='Expense Journal' args: 'account.journal'
  • name Char
    required=True tracking=True args: 'Expense Report Summary'
  • payment_mode Selection
    readonly=True related='expense_line_ids.payment_mode' string='Paid By' tracking=True
  • payment_state Selection
    compute='_compute_payment_state' copy=False readonly=True selection=PAYMENT_STATE_SELECTION store=True string='Payment Status' tracking=True
  • state Selection
    copy=False default='draft' help='Expense Report State' index=True readonly=True required=True string='Status' tracking=True args: [('draft', 'Draft'), ('submit', 'Submitted'), ('approve', 'Approved'), ('post', 'Posted'), ('done', 'Done'), ('cancel', 'Refused')]
  • total_amount Monetary
    compute='_compute_amount' currency_field='currency_id' store=True tracking=True args: 'Total Amount'
  • user_id Many2one → res.users
    compute='_compute_from_employee_id' copy=False domain=<expr> readonly=True states={'draft': [('readonly', False)]} store=True tracking=True args: 'res.users', 'Manager'
Public methods (14)
  • action_get_attachment_view(self)
  • action_open_account_move(self)
  • action_register_payment(self)
    Open the account.payment.register wizard to pay the selected journal entries. There can be more than one bank_account_id in the expense sheet when registering payment for multiple expenses. The default_partner_bank_id is set only if there is one available, if more than one the field is left empty. :return: An action opening the account.payment.register wizard.
  • action_sheet_move_create(self)
  • action_submit_sheet(self)
  • action_unpost(self)
  • activity_update(self)
  • approve_expense_sheets(self)
  • create(self, vals)
    @api.model
  • paid_expense_sheets(self)
  • refuse_sheet(self, reason)
  • reset_expense_sheets(self)
  • set_to_paid(self)
  • write(self, vals)

New fields (1)
  • can_be_expensed Boolean
    compute='_compute_can_be_expensed' help='Specify whether the product can be selected in an expense.' readonly=False store=True string='Can be Expensed'
Public methods (1)
  • default_get(self, fields)
    @api.model

New fields (4)
  • expense_alias_prefix Char
    compute='_compute_expense_alias_prefix' readonly=False store=True args: 'Default Alias Name for Expenses'
  • module_hr_expense_extract Boolean
    string='Send bills to OCR to generate expenses'
  • module_hr_payroll_expense Boolean
    string='Reimburse Expenses in Payslip'
  • use_mailgateway Boolean
    config_parameter='hr_expense.use_mailgateway' string='Let your employees record expenses by email'
Public methods (2)
  • get_values(self)
    @api.model
  • set_values(self)

New fields (1)
  • expense_manager_id Many2one
    readonly=False related='employee_id.expense_manager_id'
Public methods (1)
  • SELF_READABLE_FIELDS(self)
    @property
REPOSITORY
REPOSITORYodoo/odoo
GIT
GIThttps://github.com/odoo/odoo.git
GIT FOLDER
GIT FOLDERhttps://github.com/odoo/odoo/tree/14.0/hr_expense
VERSION
VERSION 2.0
CATEGORY
CATEGORYHuman Resources/Expenses
LICENSE
LICENSELGPL-3
APPLICATION
APPLICATIONYes
AUTO-INSTALLABLE
AUTO-INSTALLABLENo
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, qdp-odoo, Aaron Bohy, GitHub, Lucas Perais (lpe), Barad Mahendra, Yannick Tivisse, Laurent Smet, Adrien Dieudonne, wan, mreficent, Julien Castiaux, Xavier-Do, Victor Feyens, Andrea Grazioso (agr-odoo), Sébastien Theys, Nasreddin (bon), alt-odoo, oco-odoo, Julien Mougenot, fw-bot, Adrien Widart, Nasreddin Boulif (bon), Touati Djamel (otd), Swapnesh Shah, IEL, Andrius Laukavičius, Mathieu Duckerts-Antoine, Pierre Paridans, jbw, jbm-odoo, Aurélien (avd), Arnaud Joset, Kevin Baptiste, Guillaume (guva), Xavier BOL (xbo), roen-odoo, jdoutreloux, sofiagvaladze, Jigar Vaghela, Nicolas (vin), william-andre, Florian Damhaut, Merlin (megu), tranngocson1996, william, Nshimiyimana Séna, Yolann Sabaux, Jinjiu Liu, bve-odoo, mehjabinfarsana, alsh-odoo, Giacomello Nathan (ngi), Simon Genin (ges), hieu, lathuat1997, flvr-odoo, Kairis, Julien Alardot (jual), Thomas Carlier, Nguyen Viet Phuong, Nam Dao
WEBSITE
WEBSITEhttps://www.odoo.com/page/expenses
LAST TRACKING UPDATE
LAST TRACKING UPDATE2026-07-06 00:14:29
ODOO DEPENDENCIES
ODOO DEPENDENCIES odoo/odoo:
    - hr_contract
    - hr
    - base_setup
    - base
    - web
    - mail
    - bus
    - web_tour
    - resource
    - account
    - product
    - uom
    - analytic
    - portal
    - web_editor
    - http_routing
    - auth_signup
    - digest
PYTHON DEPENDENCIES
PYTHON DEPENDENCIES Not have
SYSTEM DEPENDENCIES
SYSTEM DEPENDENCIES Not have
DESCRIPTION
DESCRIPTION
Manage expenses by Employees
============================

This application allows you to manage your employees' daily expenses. It gives you access to your employees’ fee notes and give you the right to complete and validate or refuse the notes. After validation it creates an invoice for the employee.
Employee can encode their own expenses and the validation flow puts it automatically in the accounting after validation by managers.


The whole flow is implemented as:
---------------------------------
* Draft expense
* Submitted by the employee to his manager
* Approved by his manager
* Validation by the accountant and accounting entries creation

This module also uses analytic accounting and is compatible with the invoice on timesheet module so that you are able to automatically re-invoice your customers' expenses if your work by project.
    

Code Analysis

Views touched (39)
XML IDNameModelTypeStatus
account_journal_dashboard_kanban_view_inherit_hr_expense account.journal.dashboard.kanban account.journal xpath Inherits account.account_journal_dashboard_kanban_view
assets_backend HR Expense Assets assets ir.ui.view qweb Inherits web.assets_backend
assets_tests HR Expense Assets Tests ir.ui.view qweb Inherits web.assets_tests
hr_department_view_kanban hr.department.kanban.inherit hr.department data Inherits hr.hr_department_view_kanban
hr_employee_view_form_inherit_expense hr.employee.view.form.expense hr.employee xpath Inherits hr.view_employee_form
hr_expense_kanban_view hr.expense.kanban hr.expense xpath Inherits hr_expense_view_expenses_analysis_kanban
hr_expense_kanban_view_header hr.expense.kanban hr.expense xpath Inherits hr_expense_view_expenses_analysis_kanban
hr_expense_refuse_wizard_view_form hr.expense.refuse.wizard.form hr.expense.refuse.wizard form New
hr_expense_sheet_view_activity hr.expense.sheet.activity hr.expense.sheet activity New
hr_expense_sheet_view_search hr.expense.sheet.view.search hr.expense.sheet search New
hr_expense_template_refuse_reason hr_expense_template_refuse_reason ir.ui.view qweb New
hr_expense_template_register hr_expense_template_register ir.ui.view qweb New
hr_expense_template_register_no_user hr_expense_template_register_no_user ir.ui.view qweb New
hr_expense_view_activity hr.expense.activity hr.expense activity New
hr_expense_view_expenses_analysis_kanban hr.expense.kanban hr.expense kanban New
hr_expense_view_expenses_analysis_tree hr.expense.tree hr.expense tree New
hr_expense_view_form hr.expense.view.form hr.expense form New
hr_expense_view_form_without_header hr.expense.view.form hr.expense xpath Inherits hr_expense.hr_expense_view_form
hr_expense_view_graph hr.expense.graph hr.expense graph New
hr_expense_view_pivot hr.expense.pivot hr.expense pivot New
hr_expense_view_search hr.expense.view.search hr.expense search New
product_product_expense_form_view product.product.expense.form product.product form New
product_product_expense_tree_view product.product.expense.tree product.product tree New
report_expense_sheet report_expense_sheet ir.ui.view qweb New
res_config_settings_view_form res.config.settings.view.form.inherit.hr.expense res.config.settings xpath Inherits base.res_config_settings_view_form
res_users_view_form_preferences hr.user.preferences.form.inherit.hr.expense res.users xpath Inherits hr.res_users_view_form_profile
view_employee_tree_inherit_expense hr.employee.tree.expense hr.employee xpath Inherits hr.view_employee_tree
view_expenses_tree hr.expense.tree hr.expense xpath Inherits hr_expense_view_expenses_analysis_tree
view_hr_expense_sheet_dashboard_tree hr.expense.sheet.dashboard.tree hr.expense.sheet xpath Inherits hr_expense.view_hr_expense_sheet_tree
view_hr_expense_sheet_dashboard_tree_header hr.expense.sheet.dashboard.tree hr.expense.sheet xpath Inherits hr_expense.view_hr_expense_sheet_tree
view_hr_expense_sheet_form hr.expense.sheet.form hr.expense.sheet form New
view_hr_expense_sheet_graph hr.expense.sheet.graph hr.expense.sheet graph New
view_hr_expense_sheet_kanban hr.expense.sheet.kanban hr.expense.sheet kanban New
view_hr_expense_sheet_kanban_header hr.expense.sheet.kanban hr.expense.sheet xpath Inherits view_hr_expense_sheet_kanban
view_hr_expense_sheet_kanban_no_header hr.expense.sheet.kanban hr.expense.sheet xpath Inherits view_hr_expense_sheet_kanban
view_hr_expense_sheet_pivot hr.expense.sheet.pivot hr.expense.sheet pivot New
view_hr_expense_sheet_tree hr.expense.sheet.tree hr.expense.sheet tree New
view_my_expenses_tree hr.expense.tree hr.expense xpath Inherits hr_expense.view_expenses_tree
view_product_hr_expense_form product.template.expense.form product.template div Inherits product.product_template_form_view
Models touched (13)

New fields (0)

No new fields.

Public methods (2)
  • get_journal_dashboard_datas(self)
  • open_expenses_action(self)

New fields (0)

No new fields.

Public methods (2)
  • button_cancel(self)
  • button_draft(self)

New fields (1)
  • expense_id Many2one → hr.expense
    copy=False help='Expense where the move line come from' string='Expense' args: 'hr.expense'
Public methods (1)
  • reconcile(self)

New fields (0)

No new fields.

Public methods (0)

No public methods.

New fields (1)
  • expense_sheets_to_approve_count Integer
    compute='_compute_expense_sheets_to_approve' string='Expenses Reports to Approve'
Public methods (0)

No public methods.

New fields (1)
  • expense_manager_id Many2one → res.users
    compute='_compute_expense_manager' domain=_group_hr_expense_user_domain help='Select the user responsible for approving "Expenses" of this employee.\nIf empty, the approval is done by an Administrator or Approver (determined in settings/users).' readonly=False store=True string='Expense' args: 'res.users'
Public methods (0)

No public methods.

New fields (1)
  • expense_manager_id Many2one → res.users
    readonly=True args: 'res.users'
Public methods (0)

No public methods.

New fields (30)
  • account_id Many2one → account.account
    compute='_compute_from_product_id_company_id' default=_default_account_id domain="[('internal_type', '=', 'other'), ('company_id', '=', company_id)]" help='An expense account is expected' readonly=False store=True string='Account' args: 'account.account'
  • accounting_date Date
    groups='account.group_account_invoice,account.group_account_readonly' related='sheet_id.accounting_date' store=True string='Accounting Date'
  • amount_residual Monetary
    compute='_compute_amount_residual' compute_sudo=True string='Amount Due'
  • analytic_account_id Many2one → account.analytic.account
    check_company=True string='Analytic Account' args: 'account.analytic.account'
  • analytic_tag_ids Many2many → account.analytic.tag
    domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]" states={'post': [('readonly', True)], 'done': [('readonly', True)]} string='Analytic Tags' args: 'account.analytic.tag'
  • attachment_number Integer
    compute='_compute_attachment_number' args: 'Number of Attachments'
  • company_currency_id Many2one → res.currency
    readonly=False related='sheet_id.currency_id' store=True string='Report Company Currency' args: 'res.currency'
  • company_id Many2one → res.company
    default=<expr> readonly=True required=True states={'draft': [('readonly', False)], 'refused': [('readonly', False)]} string='Company' args: 'res.company'
  • currency_id Many2one → res.currency
    default=<expr> readonly=True states={'draft': [('readonly', False)], 'refused': [('readonly', False)]} string='Currency' args: 'res.currency'
  • date Date
    default=fields.Date.context_today readonly=True states={'draft': [('readonly', False)], 'reported': [('readonly', False)], 'refused': [('readonly', False)]} string='Expense Date'
  • description Text
    readonly=True states={'draft': [('readonly', False)], 'reported': [('readonly', False)], 'refused': [('readonly', False)]} args: 'Notes...'
  • employee_id Many2one → hr.employee
    check_company=True compute='_compute_employee_id' default=_default_employee_id domain=<expr> readonly=False required=True states={'approved': [('readonly', True)], 'done': [('readonly', True)]} store=True string='Employee' tracking=True args: 'hr.employee'
  • is_editable Boolean
    compute='_compute_is_editable' args: 'Is Editable By Current User'
  • is_ref_editable Boolean
    compute='_compute_is_ref_editable' args: 'Reference Is Editable By Current User'
  • is_refused Boolean
    copy=False readonly=True args: 'Explicitly Refused by manager or accountant'
  • name Char
    compute='_compute_from_product_id_company_id' copy=True required=True states={'draft': [('readonly', False)], 'reported': [('readonly', False)], 'refused': [('readonly', False)]} store=True args: 'Description'
  • payment_mode Selection
    default='own_account' states={'done': [('readonly', True)], 'approved': [('readonly', True)], 'reported': [('readonly', True)]} string='Paid By' tracking=True args: [('own_account', 'Employee (to reimburse)'), ('company_account', 'Company')]
  • product_id Many2one → product.product
    domain="[('can_be_expensed', '=', True), '|', ('company_id', '=', False), ('company_id', '=', company_id)]" ondelete='restrict' readonly=True states={'draft': [('readonly', False)], 'reported': [('readonly', False)], 'refused': [('readonly', False)]} string='Product' tracking=True args: 'product.product'
  • product_uom_category_id Many2one
    readonly=True related='product_id.uom_id.category_id'
  • product_uom_id Many2one → uom.uom
    compute='_compute_from_product_id_company_id' copy=True default=_default_product_uom_id domain="[('category_id', '=', product_uom_category_id)]" states={'draft': [('readonly', False)], 'refused': [('readonly', False)]} store=True string='Unit of Measure' args: 'uom.uom'
  • quantity Float
    default=1 digits='Product Unit of Measure' readonly=True required=True states={'draft': [('readonly', False)], 'reported': [('readonly', False)], 'refused': [('readonly', False)]}
  • reference Char
    args: 'Bill Reference'
  • sample Boolean
  • sheet_id Many2one → hr.expense.sheet
    copy=False domain="[('employee_id', '=', employee_id), ('company_id', '=', company_id)]" readonly=True string='Expense Report' args: 'hr.expense.sheet'
  • state Selection
    compute='_compute_state' copy=False default='draft' help='Status of the expense.' index=True readonly=True store=True string='Status' args: [('draft', 'To Submit'), ('reported', 'Submitted'), ('approved', 'Approved'), ('done', 'Paid'), ('refused', 'Refused')]
  • tax_ids Many2many → account.tax
    compute='_compute_from_product_id_company_id' domain="[('company_id', '=', company_id), ('type_tax_use', '=', 'purchase')]" readonly=False store=True string='Taxes' args: 'account.tax', 'expense_tax', 'expense_id', 'tax_id'
  • total_amount Monetary
    compute='_compute_amount' currency_field='currency_id' store=True tracking=True args: 'Total'
  • total_amount_company Monetary
    compute='_compute_total_amount_company' currency_field='company_currency_id' store=True args: 'Total (Company Currency)'
  • unit_amount Float
    compute='_compute_from_product_id_company_id' copy=True digits='Product Price' required=True states={'draft': [('readonly', False)], 'reported': [('readonly', False)], 'refused': [('readonly', False)]} store=True args: 'Unit Price'
  • untaxed_amount Float
    compute='_compute_amount' digits='Account' store=True args: 'Subtotal'
Public methods (11)
  • action_get_attachment_view(self)
  • action_move_create(self)
    main function that is called when trying to create the accounting entries related to an expense
  • action_submit_expenses(self)
  • action_view_sheet(self)
  • create_expense_from_attachments(self, attachment_ids=None, view_type='tree')
    Create the expenses from files. :return: An action redirecting to hr.expense tree/form view.
  • get_empty_list_help(self, help_message)
    @api.model
  • get_expense_dashboard(self)
    @api.model
  • message_new(self, msg_dict, custom_values=None)
    @api.model
  • refuse_expense(self, reason)
  • unlink(self)
  • write(self, vals)

New fields (3)
  • hr_expense_ids Many2many → hr.expense
    args: 'hr.expense'
  • hr_expense_sheet_id Many2one → hr.expense.sheet
    args: 'hr.expense.sheet'
  • reason Char
    required=True string='Reason'
Public methods (2)
  • default_get(self, fields)
    @api.model
  • expense_refuse_reason(self)

New fields (19)
  • account_move_id Many2one → account.move
    copy=False ondelete='restrict' readonly=True string='Journal Entry' args: 'account.move'
  • accounting_date Date
    args: 'Accounting Date'
  • address_id Many2one → res.partner
    check_company=True compute='_compute_from_employee_id' copy=True readonly=False store=True string='Employee Home Address' args: 'res.partner'
  • amount_residual Monetary
    compute='_compute_amount_residual' currency_field='currency_id' store=True string='Amount Due'
  • attachment_number Integer
    compute='_compute_attachment_number' string='Number of Attachments'
  • bank_journal_id Many2one → account.journal
    check_company=True default=_default_bank_journal_id domain="[('type', 'in', ['cash', 'bank']), ('company_id', '=', company_id)]" help='The payment method used when the expense is paid by the company.' states={'done': [('readonly', True)], 'post': [('readonly', True)]} string='Bank Journal' args: 'account.journal'
  • can_reset Boolean
    compute='_compute_can_reset' args: 'Can Reset'
  • company_id Many2one → res.company
    default=<expr> readonly=True required=True states={'draft': [('readonly', False)]} string='Company' args: 'res.company'
  • currency_id Many2one → res.currency
    default=<expr> readonly=True states={'draft': [('readonly', False)]} string='Currency' args: 'res.currency'
  • department_id Many2one → hr.department
    compute='_compute_from_employee_id' copy=False readonly=False states={'post': [('readonly', True)], 'done': [('readonly', True)]} store=True string='Department' args: 'hr.department'
  • employee_id Many2one → hr.employee
    check_company=True default=_default_employee_id domain=<expr> readonly=True required=True states={'draft': [('readonly', False)]} string='Employee' tracking=True args: 'hr.employee'
  • expense_line_ids One2many → hr.expense
    comodel_name='hr.expense' copy=False inverse_name='sheet_id' states={'post': [('readonly', True)], 'done': [('readonly', True)], 'cancel': [('readonly', True)]} string='Expense Lines'
  • is_multiple_currency Boolean
    compute='_compute_is_multiple_currency' args: 'Handle lines with different currencies'
  • journal_id Many2one → account.journal
    check_company=True default=_default_journal_id domain="[('type', '=', 'purchase'), ('company_id', '=', company_id)]" help='The journal used when the expense is done.' states={'done': [('readonly', True)], 'post': [('readonly', True)]} string='Expense Journal' args: 'account.journal'
  • name Char
    required=True tracking=True args: 'Expense Report Summary'
  • payment_mode Selection
    default='own_account' readonly=True related='expense_line_ids.payment_mode' string='Paid By' tracking=True
  • state Selection
    copy=False default='draft' help='Expense Report State' index=True readonly=True required=True string='Status' tracking=True args: [('draft', 'Draft'), ('submit', 'Submitted'), ('approve', 'Approved'), ('post', 'Posted'), ('done', 'Paid'), ('cancel', 'Refused')]
  • total_amount Monetary
    compute='_compute_amount' currency_field='currency_id' store=True tracking=True args: 'Total Amount'
  • user_id Many2one → res.users
    compute='_compute_from_employee_id' copy=False domain=<expr> readonly=True states={'draft': [('readonly', False)]} store=True tracking=True args: 'res.users', 'Manager'
Public methods (12)
  • action_get_attachment_view(self)
  • action_register_payment(self)
    Open the account.payment.register wizard to pay the selected journal entries. :return: An action opening the account.payment.register wizard.
  • action_sheet_move_create(self)
  • action_submit_sheet(self)
  • activity_update(self)
  • approve_expense_sheets(self)
  • create(self, vals)
    @api.model
  • paid_expense_sheets(self)
  • refuse_sheet(self, reason)
  • reset_expense_sheets(self)
  • set_to_paid(self)
  • unlink(self)

New fields (1)
  • can_be_expensed Boolean
    compute='_compute_can_be_expensed' help='Specify whether the product can be selected in an expense.' readonly=False store=True string='Can be Expensed'
Public methods (1)
  • default_get(self, fields)
    @api.model

New fields (4)
  • expense_alias_prefix Char
    compute='_compute_expense_alias_prefix' readonly=False store=True args: 'Default Alias Name for Expenses'
  • module_hr_expense_extract Boolean
    string='Send bills to OCR to generate expenses'
  • module_hr_payroll_expense Boolean
    string='Reimburse Expenses in Payslip'
  • use_mailgateway Boolean
    config_parameter='hr_expense.use_mailgateway' string='Let your employees record expenses by email'
Public methods (2)
  • get_values(self)
    @api.model
  • set_values(self)

New fields (1)
  • expense_manager_id Many2one
    readonly=False related='employee_id.expense_manager_id'
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/13.0/hr_expense
VERSION
VERSION 2.0
CATEGORY
CATEGORYAccounting/Expenses
LICENSE
LICENSELGPL-3
APPLICATION
APPLICATIONYes
AUTO-INSTALLABLE
AUTO-INSTALLABLENo
AUTHORS
AUTHORS
MAINTAINERS
MAINTAINERS
COMMITTERS
COMMITTERSRaphael Collet, Odoo Translation Bot, Martin Trigaux, Olivier Dony, Thibault Delavallée, Denis Ledoux, Christophe Simonis, Xavier Morel, Goffin Simon, Mohammed Shekha, Nicolas Martinelli, qdp-odoo, Jeremy Kersten, GitHub, Damien Bouvy, Olivier Colson, Yannick Tivisse, Martin Geubelle, Odoo Online, Laurent Smet, jem-odoo, Adrien Dieudonne, RomainLibert, Jorge Pinna Puissant, Alexandre Kühn, Adrian Torres, wan, Xavier-Do, Romeo Fragomeli, Victor Feyens, Andrea Grazioso (agr-odoo), Sébastien Theys, Nasreddin (bon), alt-odoo, oco-odoo, Florent de Labarre, fw-bot, Anh Thao Pham (pta), Lucas Lefèvre, Adrien Widart, IEL, Bhavesh Odedra, odoo, qmo-odoo, jdoutreloux, william-andre, Florian Damhaut, tranngocson1996, Ankita Raval, MaxLarue, Jinjiu Liu, Nimesh Jethva
WEBSITE
WEBSITEhttps://www.odoo.com/page/expenses
LAST TRACKING UPDATE
LAST TRACKING UPDATE2026-07-06 00:06:42
ODOO DEPENDENCIES
ODOO DEPENDENCIES odoo/odoo:
    - hr_contract
    - hr
    - base_setup
    - base
    - web
    - mail
    - bus
    - web_tour
    - resource
    - mail_bot
    - account
    - product
    - uom
    - analytic
    - portal
    - web_editor
    - http_routing
    - auth_signup
    - digest
PYTHON DEPENDENCIES
PYTHON DEPENDENCIES Not have
SYSTEM DEPENDENCIES
SYSTEM DEPENDENCIES Not have
DESCRIPTION
DESCRIPTION
Manage expenses by Employees
============================

This application allows you to manage your employees' daily expenses. It gives you access to your employees’ fee notes and give you the right to complete and validate or refuse the notes. After validation it creates an invoice for the employee.
Employee can encode their own expenses and the validation flow puts it automatically in the accounting after validation by managers.


The whole flow is implemented as:
---------------------------------
* Draft expense
* Submitted by the employee to his manager
* Approved by his manager
* Validation by the accountant and accounting entries creation

This module also uses analytic accounting and is compatible with the invoice on timesheet module so that you are able to automatically re-invoice your customers' expenses if your work by project.
    

Code Analysis

Views touched (31)
XML IDNameModelTypeStatus
account_journal_dashboard_kanban_view_inherit_hr_expense account.journal.dashboard.kanban account.journal xpath Inherits account.account_journal_dashboard_kanban_view
assets_backend HR Expense Assets assets ir.ui.view qweb Inherits web.assets_backend
assets_tests HR Expense Assets Tests ir.ui.view qweb Inherits web.assets_tests
hr_department_view_kanban hr.department.kanban.inherit hr.department data Inherits hr.hr_department_view_kanban
hr_employee_view_form_inherit_expense hr.employee.view.form.expense hr.employee xpath Inherits hr.view_employee_form
hr_expense_kanban_view hr.expense.kanban hr.expense kanban New
hr_expense_refuse_wizard_view_form hr.expense.refuse.wizard.form hr.expense.refuse.wizard form New
hr_expense_sheet_register_payment_view_form hr.expense.sheet.register.payment.wizard.form hr.expense.sheet.register.payment.wizard form New
hr_expense_sheet_view_activity hr.expense.sheet.activity hr.expense.sheet activity New
hr_expense_sheet_view_search hr.expense.sheet.view.search hr.expense.sheet search New
hr_expense_template_refuse_reason hr_expense_template_refuse_reason ir.ui.view qweb New
hr_expense_template_register hr_expense_template_register ir.ui.view qweb New
hr_expense_template_register_no_user hr_expense_template_register_no_user ir.ui.view qweb New
hr_expense_view_activity hr.expense.activity hr.expense activity New
hr_expense_view_form hr.expense.view.form hr.expense form New
hr_expense_view_form_without_header hr.expense.view.form hr.expense xpath Inherits hr_expense.hr_expense_view_form
hr_expense_view_graph hr.expense.graph hr.expense graph New
hr_expense_view_pivot hr.expense.pivot hr.expense pivot New
hr_expense_view_search hr.expense.view.search hr.expense search New
product_product_expense_form_view product.product.expense.form product.product form New
product_product_expense_tree_view product.product.expense.tree product.product tree New
report_expense_sheet report_expense_sheet ir.ui.view qweb New
res_config_settings_view_form res.config.settings.view.form.inherit.hr.expense res.config.settings xpath Inherits base.res_config_settings_view_form
res_users_view_form_preferences hr.user.preferences.form.inherit.hr.expense res.users xpath Inherits hr.res_users_view_form_profile
view_expenses_tree hr.expense.tree hr.expense tree New
view_hr_expense_sheet_form hr.expense.sheet.form hr.expense.sheet form New
view_hr_expense_sheet_graph hr.expense.sheet.graph hr.expense.sheet graph New
view_hr_expense_sheet_kanban hr.expense.sheet.kanban hr.expense.sheet kanban New
view_hr_expense_sheet_pivot hr.expense.sheet.pivot hr.expense.sheet pivot New
view_hr_expense_sheet_tree hr.expense.sheet.tree hr.expense.sheet tree New
view_product_hr_expense_form product.template.expense.form product.template div Inherits product.product_template_form_view
Models touched (13)

New fields (0)

No new fields.

Public methods (2)
  • get_journal_dashboard_datas(self)
  • open_expenses_action(self)

New fields (0)

No new fields.

Public methods (1)
  • button_cancel(self)

New fields (1)
  • expense_id Many2one → hr.expense
    copy=False help='Expense where the move line come from' string='Expense' args: 'hr.expense'
Public methods (1)
  • reconcile(self, writeoff_acc_id=False, writeoff_journal_id=False)

New fields (1)
  • expense_sheets_to_approve_count Integer
    compute='_compute_expense_sheets_to_approve' string='Expenses Reports to Approve'
Public methods (0)

No public methods.

New fields (1)
  • expense_manager_id Many2one → res.users
    domain=_group_hr_expense_user_domain help='User responsible of expense approval. Should be Expense approver.' string='Expense' args: 'res.users'
Public methods (0)

No public methods.

New fields (1)
  • expense_manager_id Many2one → res.users
    readonly=True args: 'res.users'
Public methods (0)

No public methods.

New fields (27)
  • account_id Many2one → account.account
    default=_default_account_id domain="[('internal_type', '=', 'other'), ('company_id', '=', company_id)]" help='An expense account is expected' string='Account' args: 'account.account'
  • analytic_account_id Many2one → account.analytic.account
    check_company=True string='Analytic Account' args: 'account.analytic.account'
  • analytic_tag_ids Many2many → account.analytic.tag
    domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]" states={'approved': [('readonly', True)], 'done': [('readonly', True)]} string='Analytic Tags' args: 'account.analytic.tag'
  • attachment_number Integer
    compute='_compute_attachment_number' args: 'Number of Attachments'
  • company_currency_id Many2one → res.currency
    readonly=False related='sheet_id.currency_id' store=True string='Report Company Currency' args: 'res.currency'
  • company_id Many2one → res.company
    default=<expr> readonly=True required=True states={'draft': [('readonly', False)], 'refused': [('readonly', False)]} string='Company' args: 'res.company'
  • currency_id Many2one → res.currency
    default=<expr> readonly=True states={'draft': [('readonly', False)], 'refused': [('readonly', False)]} string='Currency' args: 'res.currency'
  • date Date
    default=fields.Date.context_today readonly=True states={'draft': [('readonly', False)], 'reported': [('readonly', False)], 'refused': [('readonly', False)]} string='Date'
  • description Text
    readonly=True states={'draft': [('readonly', False)], 'reported': [('readonly', False)], 'refused': [('readonly', False)]} args: 'Notes...'
  • employee_id Many2one → hr.employee
    check_company=True default=_default_employee_id domain=<expr> readonly=True required=True states={'draft': [('readonly', False)], 'reported': [('readonly', False)], 'refused': [('readonly', False)]} string='Employee' args: 'hr.employee'
  • is_editable Boolean
    compute='_compute_is_editable' args: 'Is Editable By Current User'
  • is_ref_editable Boolean
    compute='_compute_is_ref_editable' args: 'Reference Is Editable By Current User'
  • is_refused Boolean
    copy=False readonly=True args: 'Explicitely Refused by manager or acccountant'
  • name Char
    readonly=True required=True states={'draft': [('readonly', False)], 'reported': [('readonly', False)], 'refused': [('readonly', False)]} args: 'Description'
  • payment_mode Selection
    default='own_account' states={'done': [('readonly', True)], 'approved': [('readonly', True)], 'reported': [('readonly', True)]} string='Paid By' args: [('own_account', 'Employee (to reimburse)'), ('company_account', 'Company')]
  • product_id Many2one → product.product
    domain="[('can_be_expensed', '=', True), '|', ('company_id', '=', False), ('company_id', '=', company_id)]" ondelete='restrict' readonly=True states={'draft': [('readonly', False)], 'reported': [('readonly', False)], 'refused': [('readonly', False)]} string='Product' args: 'product.product'
  • product_uom_category_id Many2one
    readonly=True related='product_id.uom_id.category_id'
  • product_uom_id Many2one → uom.uom
    default=_default_product_uom_id domain="[('category_id', '=', product_uom_category_id)]" readonly=True states={'draft': [('readonly', False)], 'refused': [('readonly', False)]} string='Unit of Measure' args: 'uom.uom'
  • quantity Float
    default=1 digits='Product Unit of Measure' readonly=True required=True states={'draft': [('readonly', False)], 'reported': [('readonly', False)], 'refused': [('readonly', False)]}
  • reference Char
    args: 'Bill Reference'
  • sheet_id Many2one → hr.expense.sheet
    copy=False domain="[('employee_id', '=', employee_id), ('company_id', '=', company_id)]" readonly=True string='Expense Report' args: 'hr.expense.sheet'
  • state Selection
    compute='_compute_state' copy=False help='Status of the expense.' index=True readonly=True store=True string='Status' args: [('draft', 'To Submit'), ('reported', 'Submitted'), ('approved', 'Approved'), ('done', 'Paid'), ('refused', 'Refused')]
  • tax_ids Many2many → account.tax
    domain="[('company_id', '=', company_id), ('type_tax_use', '=', 'purchase')]" string='Taxes' args: 'account.tax', 'expense_tax', 'expense_id', 'tax_id'
  • total_amount Monetary
    compute='_compute_amount' currency_field='currency_id' store=True args: 'Total'
  • total_amount_company Monetary
    compute='_compute_total_amount_company' currency_field='company_currency_id' store=True args: 'Total (Company Currency)'
  • unit_amount Float
    digits='Product Price' readonly=True required=True states={'draft': [('readonly', False)], 'reported': [('readonly', False)], 'refused': [('readonly', False)]} args: 'Unit Price'
  • untaxed_amount Float
    compute='_compute_amount' digits='Account' store=True args: 'Subtotal'
Public methods (10)
  • action_get_attachment_view(self)
  • action_move_create(self)
    main function that is called when trying to create the accounting entries related to an expense
  • action_submit_expenses(self)
  • action_view_sheet(self)
  • create_expense_from_attachments(self, attachment_ids=None)
    Create the expenses from files. :return: An action redirecting to hr.expense tree/form view.
  • get_empty_list_help(self, help_message)
    @api.model
  • message_new(self, msg_dict, custom_values=None)
    @api.model
  • refuse_expense(self, reason)
  • unlink(self)
  • write(self, vals)

New fields (3)
  • hr_expense_ids Many2many → hr.expense
    args: 'hr.expense'
  • hr_expense_sheet_id Many2one → hr.expense.sheet
    args: 'hr.expense.sheet'
  • reason Char
    required=True string='Reason'
Public methods (2)
  • default_get(self, fields)
    @api.model
  • expense_refuse_reason(self)

New fields (18)
  • account_move_id Many2one → account.move
    copy=False ondelete='restrict' readonly=True string='Journal Entry' args: 'account.move'
  • accounting_date Date
    args: 'Date'
  • address_id Many2one → res.partner
    check_company=True string='Employee Home Address' args: 'res.partner'
  • attachment_number Integer
    compute='_compute_attachment_number' string='Number of Attachments'
  • bank_journal_id Many2one → account.journal
    check_company=True default=_default_bank_journal_id domain="[('type', 'in', ['cash', 'bank']), ('company_id', '=', company_id)]" help='The payment method used when the expense is paid by the company.' states={'done': [('readonly', True)], 'post': [('readonly', True)]} string='Bank Journal' args: 'account.journal'
  • can_reset Boolean
    compute='_compute_can_reset' args: 'Can Reset'
  • company_id Many2one → res.company
    default=<expr> readonly=True required=True states={'draft': [('readonly', False)]} string='Company' args: 'res.company'
  • currency_id Many2one → res.currency
    default=<expr> readonly=True states={'draft': [('readonly', False)]} string='Currency' args: 'res.currency'
  • department_id Many2one → hr.department
    states={'post': [('readonly', True)], 'done': [('readonly', True)]} string='Department' args: 'hr.department'
  • employee_id Many2one → hr.employee
    check_company=True default=_default_employee_id readonly=True required=True states={'draft': [('readonly', False)]} string='Employee' args: 'hr.employee'
  • expense_line_ids One2many → hr.expense
    copy=False string='Expense Lines' args: 'hr.expense', 'sheet_id'
  • is_multiple_currency Boolean
    compute='_compute_is_multiple_currency' args: 'Handle lines with different currencies'
  • journal_id Many2one → account.journal
    check_company=True default=_default_journal_id domain="[('type', '=', 'purchase'), ('company_id', '=', company_id)]" help='The journal used when the expense is done.' states={'done': [('readonly', True)], 'post': [('readonly', True)]} string='Expense Journal' args: 'account.journal'
  • name Char
    required=True args: 'Expense Report Summary'
  • payment_mode Selection
    default='own_account' readonly=True related='expense_line_ids.payment_mode' string='Paid By'
  • state Selection
    copy=False default='draft' help='Expense Report State' index=True readonly=True required=True string='Status' tracking=True args: [('draft', 'Draft'), ('submit', 'Submitted'), ('approve', 'Approved'), ('post', 'Posted'), ('done', 'Paid'), ('cancel', 'Refused')]
  • total_amount Monetary
    compute='_compute_amount' currency_field='currency_id' store=True args: 'Total Amount'
  • user_id Many2one → res.users
    copy=False readonly=True states={'draft': [('readonly', False)]} tracking=True args: 'res.users', 'Manager'
Public methods (11)
  • action_get_attachment_view(self)
  • action_sheet_move_create(self)
  • action_submit_sheet(self)
  • activity_update(self)
  • approve_expense_sheets(self)
  • create(self, vals)
    @api.model
  • paid_expense_sheets(self)
  • refuse_sheet(self, reason)
  • reset_expense_sheets(self)
  • set_to_paid(self)
  • unlink(self)

New fields (13)
  • amount Monetary
    required=True string='Payment Amount'
  • communication Char
    string='Memo'
  • company_id Many2one → res.company
    readonly=True related='expense_sheet_id.company_id' string='Company' args: 'res.company'
  • currency_id Many2one → res.currency
    default=<expr> required=True string='Currency' args: 'res.currency'
  • expense_sheet_id Many2one → hr.expense.sheet
    required=True string='Expense Report' args: 'hr.expense.sheet'
  • hide_payment_method Boolean
    compute='_compute_hide_payment_method' help="Technical field used to hide the payment method if the selected journal has only one available which is 'manual'"
  • journal_id Many2one → account.journal
    domain="[('type', 'in', ('bank', 'cash')), ('company_id', '=', company_id)]" required=True string='Payment Method' args: 'account.journal'
  • partner_bank_account_id Many2one → res.partner.bank
    domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]" string='Recipient Bank Account' args: 'res.partner.bank'
  • partner_id Many2one → res.partner
    domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]" required=True string='Partner' args: 'res.partner'
  • payment_date Date
    default=fields.Date.context_today required=True string='Payment Date'
  • payment_method_id Many2one → account.payment.method
    required=True string='Payment Type' args: 'account.payment.method'
  • require_partner_bank_account Boolean
    compute='_compute_show_partner_bank' help='Technical field used to know whether the field `partner_bank_account_id` needs to be required or not in the payments form views'
  • show_partner_bank_account Boolean
    compute='_compute_show_partner_bank' help='Technical field used to know whether the field `partner_bank_account_id` needs to be displayed or not in the payments form views'
Public methods (2)
  • default_get(self, fields)
    @api.model
  • expense_post_payment(self)

New fields (1)
  • can_be_expensed Boolean
    help='Specify whether the product can be selected in an expense.' string='Can be Expensed'
Public methods (1)
  • default_get(self, fields)
    @api.model

New fields (3)
  • expense_alias_prefix Char
    args: 'Default Alias Name for Expenses'
  • module_hr_payroll_expense Boolean
    string='Reimburse Expenses in Payslip'
  • use_mailgateway Boolean
    config_parameter='hr_expense.use_mailgateway' string='Let your employees record expenses by email'
Public methods (2)
  • get_values(self)
    @api.model
  • set_values(self)

New fields (1)
  • expense_manager_id Many2one
    readonly=False related='employee_id.expense_manager_id'
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/12.0/hr_expense
VERSION
VERSION 2.0
CATEGORY
CATEGORYHuman Resources
LICENSE
LICENSELGPL-3
APPLICATION
APPLICATIONYes
AUTO-INSTALLABLE
AUTO-INSTALLABLENo
AUTHORS
AUTHORS
MAINTAINERS
MAINTAINERS
COMMITTERS
COMMITTERSRaphael Collet, Odoo Translation Bot, Christophe Matthieu, Martin Trigaux, Fabien Pinckaers, Thibault Delavallée, Denis Ledoux, Christophe Simonis, Xavier Morel, Goffin Simon, Simon Lejeune, Nicolas Martinelli, Jérome Maes, Richard Mathot, qdp-odoo, Géry Debongnie, GitHub, Lucas Perais (lpe), Olivier Colson, Yannick Tivisse, Alexandre Kühn, qsm-odoo, Martin Geubelle, Toufik Benjaa, jem-odoo, RomainLibert, Christophe Monniez, Nans Lefebvre, Alexandre Kühn, jev-odoo, fda-odoo, Adrian Torres, mreficent, XavierDo, Xavier-Do, Luis Torres, david, Alex Tuyls
WEBSITE
WEBSITEhttps://www.odoo.com/page/expenses
LAST TRACKING UPDATE
LAST TRACKING UPDATE2026-07-05 23:57:01
ODOO DEPENDENCIES
ODOO DEPENDENCIES odoo/odoo:
    - hr_contract
    - hr
    - base_setup
    - base
    - web
    - mail
    - bus
    - web_tour
    - resource
    - account
    - product
    - decimal_precision
    - uom
    - analytic
    - portal
    - http_routing
    - digest
PYTHON DEPENDENCIES
PYTHON DEPENDENCIES Not have
SYSTEM DEPENDENCIES
SYSTEM DEPENDENCIES Not have
DESCRIPTION
DESCRIPTION
Manage expenses by Employees
============================

This application allows you to manage your employees' daily expenses. It gives you access to your employees’ fee notes and give you the right to complete and validate or refuse the notes. After validation it creates an invoice for the employee.
Employee can encode their own expenses and the validation flow puts it automatically in the accounting after validation by managers.


The whole flow is implemented as:
---------------------------------
* Draft expense
* Submitted by the employee to his manager
* Approved by his manager
* Validation by the accountant and accounting entries creation

This module also uses analytic accounting and is compatible with the invoice on timesheet module so that you are able to automatically re-invoice your customers' expenses if your work by project.
    

Code Analysis

Views touched (25)
XML IDNameModelTypeStatus
account_journal_dashboard_kanban_view_inherit_hr_expense account.journal.dashboard.kanban account.journal xpath Inherits account.account_journal_dashboard_kanban_view
assets_backend_expense hr_expense assets ir.ui.view qweb Inherits web.assets_backend
hr_department_view_kanban hr.department.kanban.inherit hr.department data Inherits hr.hr_department_view_kanban
hr_employee_view_form_inherit_expense hr.employee.view.form.expense hr.employee xpath Inherits hr.view_employee_form
hr_expense_kanban_view hr.expense.kanban hr.expense kanban New
hr_expense_refuse_wizard_view_form hr.expense.refuse.wizard.form hr.expense.refuse.wizard form New
hr_expense_sheet_register_payment_view_form hr.expense.sheet.register.payment.wizard.form hr.expense.sheet.register.payment.wizard form New
hr_expense_template_refuse_reason hr_expense_template_refuse_reason ir.ui.view qweb New
hr_expense_view_form hr.expense.view.form hr.expense form New
hr_expense_view_form_without_header hr.expense.view.form hr.expense xpath Inherits hr_expense.hr_expense_view_form
hr_expense_view_graph hr.expense.graph hr.expense graph New
hr_expense_view_pivot hr.expense.pivot hr.expense pivot New
product_product_expense_form_view product.product.expense.form product.product form New
product_product_expense_tree_view product.product.expense.tree product.product tree New
report_expense_sheet report_expense_sheet ir.ui.view qweb New
res_config_settings_view_form res.config.settings.view.form.inherit.hr.expense res.config.settings xpath Inherits base.res_config_settings_view_form
view_expenses_tree hr.expense.tree hr.expense tree New
view_hr_expense_filter hr.expense.filter hr.expense search New
view_hr_expense_sheet_filter hr.expense.sheet.filter hr.expense.sheet search New
view_hr_expense_sheet_form hr.expense.sheet.form hr.expense.sheet form New
view_hr_expense_sheet_graph hr.expense.sheet.graph hr.expense.sheet graph New
view_hr_expense_sheet_kanban hr.expense.sheet.kanban hr.expense.sheet kanban New
view_hr_expense_sheet_pivot hr.expense.sheet.pivot hr.expense.sheet pivot New
view_hr_expense_sheet_tree hr.expense.sheet.tree hr.expense.sheet tree New
view_product_hr_expense_form product.template.expense.form product.template div Inherits product.product_template_form_view
Models touched (10)

New fields (0)

No new fields.

Public methods (2)
  • get_journal_dashboard_datas(self)
    @api.multi
  • open_expenses_action(self)
    @api.multi

New fields (1)
  • expense_id Many2one → hr.expense
    copy=False help='Expense where the move line come from' string='Expense' args: 'hr.expense'
Public methods (1)
  • reconcile(self, writeoff_acc_id=False, writeoff_journal_id=False)
    @api.multi

New fields (1)
  • expense_sheets_to_approve_count Integer
    compute='_compute_expense_sheets_to_approve' string='Expenses Reports to Approve'
Public methods (0)

No public methods.

New fields (1)
  • expense_manager_id Many2one → res.users
    domain=<expr> help='User responsible of expense approval. Should be Expense Officer or Manager.' string='Expense Responsible' args: 'res.users'
Public methods (0)

No public methods.

New fields (24)
  • account_id Many2one → account.account
    default=_default_account_id help='An expense account is expected' states={'post': [('readonly', True)], 'done': [('readonly', True)]} string='Account' args: 'account.account'
  • analytic_account_id Many2one → account.analytic.account
    oldname='analytic_account' states={'post': [('readonly', True)], 'done': [('readonly', True)]} string='Analytic Account' args: 'account.analytic.account'
  • analytic_tag_ids Many2many → account.analytic.tag
    states={'post': [('readonly', True)], 'done': [('readonly', True)]} string='Analytic Tags' args: 'account.analytic.tag'
  • attachment_number Integer
    compute='_compute_attachment_number' args: 'Number of Attachments'
  • company_currency_id Many2one → res.currency
    readonly=False related='sheet_id.currency_id' store=True string='Report Company Currency' args: 'res.currency'
  • company_id Many2one → res.company
    default=<expr> readonly=True states={'draft': [('readonly', False)], 'refused': [('readonly', False)]} string='Company' args: 'res.company'
  • currency_id Many2one → res.currency
    default=<expr> readonly=True states={'draft': [('readonly', False)], 'refused': [('readonly', False)]} string='Currency' args: 'res.currency'
  • date Date
    default=fields.Date.context_today readonly=True states={'draft': [('readonly', False)], 'reported': [('readonly', False)], 'refused': [('readonly', False)]} string='Date'
  • description Text
    readonly=True states={'draft': [('readonly', False)], 'reported': [('readonly', False)], 'refused': [('readonly', False)]} args: 'Notes...'
  • employee_id Many2one → hr.employee
    default=_default_employee_id domain=<expr> readonly=True required=True states={'draft': [('readonly', False)], 'reported': [('readonly', False)], 'refused': [('readonly', False)]} string='Employee' args: 'hr.employee'
  • is_refused Boolean
    copy=False readonly=True args: 'Explicitely Refused by manager or acccountant'
  • name Char
    readonly=True required=True states={'draft': [('readonly', False)], 'reported': [('readonly', False)], 'refused': [('readonly', False)]} args: 'Description'
  • payment_mode Selection
    default='own_account' states={'done': [('readonly', True)], 'approved': [('readonly', True)], 'reported': [('readonly', True)]} string='Paid By' args: [('own_account', 'Employee (to reimburse)'), ('company_account', 'Company')]
  • product_id Many2one → product.product
    domain=[('can_be_expensed', '=', True)] readonly=True required=True states={'draft': [('readonly', False)], 'reported': [('readonly', False)], 'refused': [('readonly', False)]} string='Product' args: 'product.product'
  • product_uom_id Many2one → uom.uom
    default=_default_product_uom_id readonly=True required=True states={'draft': [('readonly', False)], 'refused': [('readonly', False)]} string='Unit of Measure' args: 'uom.uom'
  • quantity Float
    default=1 digits=dp.get_precision('Product Unit of Measure') readonly=True required=True states={'draft': [('readonly', False)], 'reported': [('readonly', False)], 'refused': [('readonly', False)]}
  • reference Char
    args: 'Bill Reference'
  • sheet_id Many2one → hr.expense.sheet
    copy=False readonly=True string='Expense Report' args: 'hr.expense.sheet'
  • state Selection
    compute='_compute_state' copy=False help='Status of the expense.' index=True readonly=True store=True string='Status' args: [('draft', 'To Submit'), ('reported', 'Submitted'), ('approved', 'Approved'), ('done', 'Paid'), ('refused', 'Refused')]
  • tax_ids Many2many → account.tax
    states={'done': [('readonly', True)], 'post': [('readonly', True)]} string='Taxes' args: 'account.tax', 'expense_tax', 'expense_id', 'tax_id'
  • total_amount Monetary
    compute='_compute_amount' currency_field='currency_id' digits=dp.get_precision('Account') store=True args: 'Total'
  • total_amount_company Monetary
    compute='_compute_total_amount_company' currency_field='company_currency_id' digits=dp.get_precision('Account') store=True args: 'Total (Company Currency)'
  • unit_amount Float
    digits=dp.get_precision('Product Price') readonly=True required=True states={'draft': [('readonly', False)], 'reported': [('readonly', False)], 'refused': [('readonly', False)]} args: 'Unit Price'
  • untaxed_amount Float
    compute='_compute_amount' digits=dp.get_precision('Account') store=True args: 'Subtotal'
Public methods (8)
  • action_get_attachment_view(self)
    @api.multi
  • action_move_create(self)
    @api.multi
    main function that is called when trying to create the accounting entries related to an expense
  • action_submit_expenses(self)
    @api.multi
  • action_view_sheet(self)
    @api.multi
  • get_empty_list_help(self, help_message)
    @api.model
  • message_new(self, msg_dict, custom_values=None)
    @api.model
  • refuse_expense(self, reason)
    @api.multi
  • unlink(self)
    @api.multi

New fields (3)
  • hr_expense_ids Many2many → hr.expense
    args: 'hr.expense'
  • hr_expense_sheet_id Many2one → hr.expense.sheet
    args: 'hr.expense.sheet'
  • reason Char
    required=True string='Reason'
Public methods (2)
  • default_get(self, fields)
    @api.model
  • expense_refuse_reason(self)
    @api.multi

New fields (18)
  • account_move_id Many2one → account.move
    copy=False ondelete='restrict' string='Journal Entry' args: 'account.move'
  • accounting_date Date
    args: 'Date'
  • address_id Many2one → res.partner
    string='Employee Home Address' args: 'res.partner'
  • attachment_number Integer
    compute='_compute_attachment_number' string='Number of Attachments'
  • bank_journal_id Many2one → account.journal
    default=_default_bank_journal_id help='The payment method used when the expense is paid by the company.' states={'done': [('readonly', True)], 'post': [('readonly', True)]} string='Bank Journal' args: 'account.journal'
  • can_reset Boolean
    compute='_compute_can_reset' args: 'Can Reset'
  • company_id Many2one → res.company
    default=<expr> readonly=True states={'draft': [('readonly', False)]} string='Company' args: 'res.company'
  • currency_id Many2one → res.currency
    default=<expr> readonly=True states={'draft': [('readonly', False)]} string='Currency' args: 'res.currency'
  • department_id Many2one → hr.department
    states={'post': [('readonly', True)], 'done': [('readonly', True)]} string='Department' args: 'hr.department'
  • employee_id Many2one → hr.employee
    default=<expr> readonly=True required=True states={'draft': [('readonly', False)]} string='Employee' args: 'hr.employee'
  • expense_line_ids One2many → hr.expense
    copy=False states={'approve': [('readonly', True)], 'done': [('readonly', True)], 'post': [('readonly', True)]} string='Expense Lines' args: 'hr.expense', 'sheet_id'
  • is_multiple_currency Boolean
    compute='_compute_is_multiple_currency' args: 'Handle lines with different currencies'
  • journal_id Many2one → account.journal
    default=_default_journal_id help='The journal used when the expense is done.' states={'done': [('readonly', True)], 'post': [('readonly', True)]} string='Expense Journal' args: 'account.journal'
  • name Char
    required=True args: 'Expense Report Summary'
  • payment_mode Selection
    default='own_account' readonly=True related='expense_line_ids.payment_mode' string='Paid By' args: [('own_account', 'Employee (to reimburse)'), ('company_account', 'Company')]
  • state Selection
    copy=False default='draft' help='Expense Report State' index=True readonly=True required=True string='Status' track_visibility='onchange' args: [('draft', 'Draft'), ('submit', 'Submitted'), ('approve', 'Approved'), ('post', 'Posted'), ('done', 'Paid'), ('cancel', 'Refused')]
  • total_amount Monetary
    compute='_compute_amount' currency_field='currency_id' digits=dp.get_precision('Account') store=True args: 'Total Amount'
  • user_id Many2one → res.users
    copy=False oldname='responsible_id' readonly=True states={'draft': [('readonly', False)]} track_visibility='onchange' args: 'res.users', 'Manager'
Public methods (11)
  • action_get_attachment_view(self)
    @api.multi
  • action_sheet_move_create(self)
    @api.multi
  • action_submit_sheet(self)
    @api.multi
  • activity_update(self)
  • approve_expense_sheets(self)
    @api.multi
  • create(self, vals)
    @api.model
  • paid_expense_sheets(self)
    @api.multi
  • refuse_sheet(self, reason)
    @api.multi
  • reset_expense_sheets(self)
    @api.multi
  • set_to_paid(self)
    @api.multi
  • unlink(self)
    @api.multi

New fields (11)
  • amount Monetary
    required=True string='Payment Amount'
  • communication Char
    string='Memo'
  • company_id Many2one → res.company
    readonly=True related='journal_id.company_id' required=True string='Company' args: 'res.company'
  • currency_id Many2one → res.currency
    default=<expr> required=True string='Currency' args: 'res.currency'
  • hide_payment_method Boolean
    compute='_compute_hide_payment_method' help="Technical field used to hide the payment method if the selected journal has only one available which is 'manual'"
  • journal_id Many2one → account.journal
    domain=[('type', 'in', ('bank', 'cash'))] required=True string='Payment Method' args: 'account.journal'
  • partner_bank_account_id Many2one → res.partner.bank
    string='Recipient Bank Account' args: 'res.partner.bank'
  • partner_id Many2one → res.partner
    default=_default_partner_id required=True string='Partner' args: 'res.partner'
  • payment_date Date
    default=fields.Date.context_today required=True string='Payment Date'
  • payment_method_id Many2one → account.payment.method
    required=True string='Payment Type' args: 'account.payment.method'
  • show_partner_bank_account Boolean
    compute='_compute_show_partner_bank' help='Technical field used to know whether the field `partner_bank_account_id` needs to be displayed or not in the payments form views'
Public methods (1)
  • expense_post_payment(self)
    @api.multi

New fields (1)
  • can_be_expensed Boolean
    help='Specify whether the product can be selected in an expense.' string='Can be Expensed'
Public methods (1)
  • create(self, vals)
    @api.model

New fields (2)
  • expense_alias_prefix Char
    args: 'Default Alias Name for Expenses'
  • use_mailgateway Boolean
    config_parameter='hr_expense.use_mailgateway' string='Let your employees record expenses by email'
Public methods (2)
  • get_values(self)
    @api.model
  • set_values(self)
    @api.multi
REPOSITORY
REPOSITORYodoo/odoo
GIT
GIThttps://github.com/odoo/odoo.git
GIT FOLDER
GIT FOLDERhttps://github.com/odoo/odoo/tree/11.0/hr_expense
VERSION
VERSION 2.0
CATEGORY
CATEGORYHuman Resources
LICENSE
LICENSELGPL-3
APPLICATION
APPLICATIONYes
AUTO-INSTALLABLE
AUTO-INSTALLABLENo
AUTHORS
AUTHORS
MAINTAINERS
MAINTAINERS
COMMITTERS
COMMITTERSRaphael Collet, Odoo Translation Bot, Martin Trigaux, Fabien Pinckaers, Olivier Dony, Thibault Delavallée, Christophe Simonis, Xavier Morel, Goffin Simon, Nicolas Martinelli, Nicolas Lempereur, Jérome Maes, qdp-odoo, Aaron Bohy, GitHub, Lucas Perais (lpe), Olivier Colson, Yannick Tivisse, qsm-odoo, Martin Geubelle, Odoo Online, Laurent Smet, Adrien Dieudonne
WEBSITE
WEBSITEhttps://www.odoo.com/page/expenses
LAST TRACKING UPDATE
LAST TRACKING UPDATE2026-07-05 23:53:43
ODOO DEPENDENCIES
ODOO DEPENDENCIES odoo/odoo:
    - hr_contract
    - hr
    - base_setup
    - base
    - web
    - mail
    - bus
    - web_tour
    - resource
    - account
    - product
    - decimal_precision
    - analytic
    - web_planner
    - portal
    - http_routing
PYTHON DEPENDENCIES
PYTHON DEPENDENCIES Not have
SYSTEM DEPENDENCIES
SYSTEM DEPENDENCIES Not have
DESCRIPTION
DESCRIPTION
Manage expenses by Employees
============================

This application allows you to manage your employees' daily expenses. It gives you access to your employees’ fee notes and give you the right to complete and validate or refuse the notes. After validation it creates an invoice for the employee.
Employee can encode their own expenses and the validation flow puts it automatically in the accounting after validation by managers.


The whole flow is implemented as:
---------------------------------
* Draft expense
* Submitted by the employee to his manager
* Approved by his manager
* Validation by the accountant and accounting entries creation

This module also uses analytic accounting and is compatible with the invoice on timesheet module so that you are able to automatically re-invoice your customers' expenses if your work by project.
    

Code Analysis

Views touched (24)
XML IDNameModelTypeStatus
assets_backend_expense hr_expense assets ir.ui.view qweb Inherits web.assets_backend
hr_department_view_kanban hr.department.kanban.inherit hr.department data Inherits hr.hr_department_view_kanban
hr_expense_form_view hr.expense.form hr.expense form New
hr_expense_kanban_view hr.expense.kanban hr.expense kanban New
hr_expense_planner hr_expense_planner ir.ui.view qweb New
hr_expense_refuse_wizard_view_form hr.expense.refuse.wizard.form hr.expense.refuse.wizard form New
hr_expense_sheet_register_payment_view_form hr.expense.sheet.register.payment.wizard.form hr.expense.sheet.register.payment.wizard form New
hr_expense_template_refuse_reason hr_expense_template_refuse_reason ir.ui.view qweb New
hr_expense_view_graph hr.expense.graph hr.expense graph New
hr_expense_view_pivot hr.expense.pivot hr.expense pivot New
product_product_expense_form_view product.product.expense.form product.product form New
product_product_expense_tree_view product.product.expense.tree product.product tree New
report_expense report_expense ir.ui.view qweb New
report_expense_sheet report_expense_sheet ir.ui.view qweb New
res_config_settings_view_form res.config.settings.view.form.inherit.hr.expense res.config.settings xpath Inherits base.res_config_settings_view_form
view_expenses_tree hr.expense.tree hr.expense tree New
view_hr_expense_filter hr.expense.filter hr.expense search New
view_hr_expense_sheet_filter hr.expense.sheet.filter hr.expense.sheet search New
view_hr_expense_sheet_form hr.expense.sheet.form hr.expense.sheet form New
view_hr_expense_sheet_graph hr.expense.sheet.graph hr.expense.sheet graph New
view_hr_expense_sheet_kanban hr.expense.sheet.kanban hr.expense.sheet kanban New
view_hr_expense_sheet_pivot hr.expense.sheet.pivot hr.expense.sheet pivot New
view_hr_expense_sheet_tree hr.expense.sheet.tree hr.expense.sheet tree New
view_product_hr_expense_form product.template.expense.form product.template div Inherits product.product_template_form_view
Models touched (9)

New fields (1)
  • expense_id Many2one → hr.expense
    copy=False help='Expense where the move line come from' string='Expense' args: 'hr.expense'
Public methods (1)
  • reconcile(self, writeoff_acc_id=False, writeoff_journal_id=False)
    @api.multi

New fields (1)
  • expense_sheets_to_approve_count Integer
    compute='_compute_expense_sheets_to_approve' string='Expenses Reports to Approve'
Public methods (0)

No public methods.

New fields (21)
  • account_id Many2one → account.account
    default=<expr> help='An expense account is expected' states={'post': [('readonly', True)], 'done': [('readonly', True)]} string='Account' args: 'account.account'
  • analytic_account_id Many2one → account.analytic.account
    oldname='analytic_account' states={'post': [('readonly', True)], 'done': [('readonly', True)]} string='Analytic Account' args: 'account.analytic.account'
  • attachment_number Integer
    compute='_compute_attachment_number' string='Number of Attachments'
  • company_id Many2one → res.company
    default=<expr> readonly=True states={'draft': [('readonly', False)], 'refused': [('readonly', False)]} string='Company' args: 'res.company'
  • currency_id Many2one → res.currency
    default=<expr> readonly=True states={'draft': [('readonly', False)], 'refused': [('readonly', False)]} string='Currency' args: 'res.currency'
  • date Date
    default=fields.Date.context_today readonly=True states={'draft': [('readonly', False)], 'refused': [('readonly', False)]} string='Expense Date'
  • description Text
  • employee_id Many2one → hr.employee
    default=<expr> readonly=True required=True states={'draft': [('readonly', False)], 'refused': [('readonly', False)]} string='Employee' args: 'hr.employee'
  • is_refused Boolean
    copy=False readonly=True string='Explicitely Refused by manager or acccountant'
  • name Char
    readonly=True required=True states={'draft': [('readonly', False)], 'refused': [('readonly', False)]} string='Expense Description'
  • payment_mode Selection
    default='own_account' states={'done': [('readonly', True)], 'post': [('readonly', True)], 'submitted': [('readonly', True)]} string='Payment By' args: [('own_account', 'Employee (to reimburse)'), ('company_account', 'Company')]
  • product_id Many2one → product.product
    domain=[('can_be_expensed', '=', True)] readonly=True required=True states={'draft': [('readonly', False)], 'refused': [('readonly', False)]} string='Product' args: 'product.product'
  • product_uom_id Many2one → product.uom
    default=<expr> readonly=True required=True states={'draft': [('readonly', False)], 'refused': [('readonly', False)]} string='Unit of Measure' args: 'product.uom'
  • quantity Float
    default=1 digits=dp.get_precision('Product Unit of Measure') readonly=True required=True states={'draft': [('readonly', False)], 'refused': [('readonly', False)]}
  • reference Char
    string='Bill Reference'
  • sheet_id Many2one → hr.expense.sheet
    copy=False readonly=True string='Expense Report' args: 'hr.expense.sheet'
  • state Selection
    compute='_compute_state' copy=False help='Status of the expense.' index=True readonly=True store=True string='Status' args: [('draft', 'To Submit'), ('reported', 'Reported'), ('done', 'Posted'), ('refused', 'Refused')]
  • tax_ids Many2many → account.tax
    states={'done': [('readonly', True)], 'post': [('readonly', True)]} string='Taxes' args: 'account.tax', 'expense_tax', 'expense_id', 'tax_id'
  • total_amount Float
    compute='_compute_amount' digits=dp.get_precision('Account') store=True string='Total'
  • unit_amount Float
    digits=dp.get_precision('Product Price') readonly=True required=True states={'draft': [('readonly', False)], 'refused': [('readonly', False)]} string='Unit Price'
  • untaxed_amount Float
    compute='_compute_amount' digits=dp.get_precision('Account') store=True string='Subtotal'
Public methods (8)
  • action_get_attachment_view(self)
    @api.multi
  • action_move_create(self)
    @api.multi
    main function that is called when trying to create the accounting entries related to an expense
  • get_empty_list_help(self, help_message)
    @api.model
  • message_new(self, msg_dict, custom_values=None)
    @api.model
  • refuse_expense(self, reason)
    @api.multi
  • submit_expenses(self)
    @api.multi
  • unlink(self)
    @api.multi
  • view_sheet(self)
    @api.multi

New fields (3)
  • hr_expense_ids Many2many → hr.expense
    args: 'hr.expense'
  • hr_expense_sheet_id Many2one → hr.expense.sheet
    args: 'hr.expense.sheet'
  • reason Char
    required=True string='Reason'
Public methods (2)
  • default_get(self, fields)
    @api.model
  • expense_refuse_reason(self)
    @api.multi

New fields (16)
  • account_move_id Many2one → account.move
    copy=False ondelete='restrict' string='Journal Entry' args: 'account.move'
  • accounting_date Date
    string='Date'
  • address_id Many2one → res.partner
    string='Employee Home Address' args: 'res.partner'
  • attachment_number Integer
    compute='_compute_attachment_number' string='Number of Attachments'
  • bank_journal_id Many2one → account.journal
    default=<expr> help='The payment method used when the expense is paid by the company.' states={'done': [('readonly', True)], 'post': [('readonly', True)]} string='Bank Journal' args: 'account.journal'
  • company_id Many2one → res.company
    default=<expr> readonly=True states={'submit': [('readonly', False)]} string='Company' args: 'res.company'
  • currency_id Many2one → res.currency
    default=<expr> readonly=True states={'submit': [('readonly', False)]} string='Currency' args: 'res.currency'
  • department_id Many2one → hr.department
    states={'post': [('readonly', True)], 'done': [('readonly', True)]} string='Department' args: 'hr.department'
  • employee_id Many2one → hr.employee
    default=<expr> readonly=True required=True states={'submit': [('readonly', False)]} string='Employee' args: 'hr.employee'
  • expense_line_ids One2many → hr.expense
    copy=False states={'approve': [('readonly', True)], 'done': [('readonly', True)], 'post': [('readonly', True)]} string='Expense Lines' args: 'hr.expense', 'sheet_id'
  • journal_id Many2one → account.journal
    default=<expr> help='The journal used when the expense is done.' states={'done': [('readonly', True)], 'post': [('readonly', True)]} string='Expense Journal' args: 'account.journal'
  • name Char
    required=True string='Expense Report Summary'
  • payment_mode Selection
    default='own_account' readonly=True related='expense_line_ids.payment_mode' string='Payment By' args: [('own_account', 'Employee (to reimburse)'), ('company_account', 'Company')]
  • responsible_id Many2one → res.users
    copy=False readonly=True states={'submit': [('readonly', False)], 'submit': [('readonly', False)]} args: 'res.users', 'Validation By'
  • state Selection
    copy=False default='submit' help='Expense Report State' index=True readonly=True required=True string='Status' track_visibility='onchange' args: [('submit', 'Submitted'), ('approve', 'Approved'), ('post', 'Posted'), ('done', 'Paid'), ('cancel', 'Refused')]
  • total_amount Float
    compute='_compute_amount' digits=dp.get_precision('Account') store=True string='Total Amount'
Public methods (11)
  • action_get_attachment_view(self)
    @api.multi
  • action_sheet_move_create(self)
    @api.multi
  • approve_expense_sheets(self)
    @api.multi
  • check_consistency(self)
    @api.multi
  • create(self, vals)
    @api.model
  • paid_expense_sheets(self)
    @api.multi
  • refuse_sheet(self, reason)
    @api.multi
  • reset_expense_sheets(self)
    @api.multi
  • set_to_paid(self)
    @api.multi
  • unlink(self)
    @api.multi
  • write(self, vals)
    @api.multi

New fields (9)
  • amount Monetary
    required=True string='Payment Amount'
  • communication Char
    string='Memo'
  • company_id Many2one → res.company
    readonly=True related='journal_id.company_id' required=True string='Company' args: 'res.company'
  • currency_id Many2one → res.currency
    default=<expr> required=True string='Currency' args: 'res.currency'
  • hide_payment_method Boolean
    compute='_compute_hide_payment_method' help="Technical field used to hide the payment method if the selected journal has only one available which is 'manual'"
  • journal_id Many2one → account.journal
    domain=[('type', 'in', ('bank', 'cash'))] required=True string='Payment Method' args: 'account.journal'
  • partner_id Many2one → res.partner
    default=_default_partner_id required=True string='Partner' args: 'res.partner'
  • payment_date Date
    default=fields.Date.context_today required=True string='Payment Date'
  • payment_method_id Many2one → account.payment.method
    required=True string='Payment Type' args: 'account.payment.method'
Public methods (1)
  • expense_post_payment(self)
    @api.multi

New fields (1)
  • can_be_expensed Boolean
    help='Specify whether the product can be selected in an HR expense.' string='Can be Expensed'
Public methods (1)
  • create(self, vals)
    @api.model

New fields (3)
  • expense_alias_prefix Char
    args: 'Default Alias Name for Expenses'
  • module_sale_management Boolean
    string='Customer Billing'
  • use_mailgateway Boolean
    string='Let your employees record expenses by email'
Public methods (2)
  • get_values(self)
    @api.model
  • set_values(self)
    @api.multi

New fields (0)

No new fields.

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/10.0/hr_expense
VERSION
VERSION 2.0
CATEGORY
CATEGORYHuman Resources
LICENSE
LICENSELGPL-3
APPLICATION
APPLICATIONYes
AUTO-INSTALLABLE
AUTO-INSTALLABLENo
AUTHORS
AUTHORS
MAINTAINERS
MAINTAINERS
COMMITTERS
COMMITTERSOdoo Translation Bot, Christophe Matthieu, Martin Trigaux, Fabien Pinckaers, Olivier Dony, Thibault Delavallée, Denis Ledoux, Christophe Simonis, Goffin Simon, Cedric Snauwaert, Nicolas Martinelli, Nicolas Lempereur, Julien Legros, Richard Mathot, qdp-odoo, Géry Debongnie, Aaron Bohy, Jeremy Kersten, Lucas Perais (lpe), Yannick Tivisse, qsm-odoo, Martin Geubelle, Pierre Masereel, Romain Derie, Denis Vermylen (dve), jem-odoo, kujiu
WEBSITE
WEBSITEhttps://www.odoo.com/page/expenses
LAST TRACKING UPDATE
LAST TRACKING UPDATE2026-07-05 23:50:48
ODOO DEPENDENCIES
ODOO DEPENDENCIES odoo/odoo:
    - hr_contract
    - base_action_rule
    - base
    - resource
    - mail
    - base_setup
    - web_kanban
    - web
    - bus
    - web_tour
    - hr
    - account_accountant
    - account
    - product
    - decimal_precision
    - report
    - analytic
    - web_planner
PYTHON DEPENDENCIES
PYTHON DEPENDENCIES Not have
SYSTEM DEPENDENCIES
SYSTEM DEPENDENCIES Not have
DESCRIPTION
DESCRIPTION
Manage expenses by Employees
============================

This application allows you to manage your employees' daily expenses. It gives you access to your employees’ fee notes and give you the right to complete and validate or refuse the notes. After validation it creates an invoice for the employee.
Employee can encode their own expenses and the validation flow puts it automatically in the accounting after validation by managers.


The whole flow is implemented as:
---------------------------------
* Draft expense
* Submitted by the employee to his manager
* Approved by his manager
* Validation by the accountant and accounting entries creation

This module also uses analytic accounting and is compatible with the invoice on timesheet module so that you are able to automatically re-invoice your customers' expenses if your work by project.
    

Code Analysis

Views touched (22)
XML IDNameModelTypeStatus
assets_backend_expense hr_expense assets ir.ui.view qweb Inherits web.assets_backend
hr_department_view_kanban hr.department.kanban.inherit hr.department data Inherits hr.hr_department_view_kanban
hr_expense_form_view hr.expense.form hr.expense form New
hr_expense_kanban_view hr.expense.kanban hr.expense kanban New
hr_expense_planner hr_expense_planner ir.ui.view qweb New
hr_expense_refuse_wizard_view_form hr.expense.refuse.wizard.form hr.expense.refuse.wizard form New
hr_expense_register_payment_view_form hr.expense.register.payment.wizard.form hr.expense.register.payment.wizard form New
hr_expense_view_graph hr.expense.graph hr.expense graph New
hr_expense_view_pivot hr.expense.pivot hr.expense pivot New
product_product_expense_form_view product.product.expense.form product.product form New
report_expense report_expense ir.ui.view qweb New
report_expense_sheet report_expense_sheet ir.ui.view qweb New
view_expenses_tree hr.expense.tree hr.expense tree New
view_hr_expense_configuration Configure Expense hr.expense.config.settings form New
view_hr_expense_filter hr.expense.filter hr.expense search New
view_hr_expense_sheet_filter hr.expense.sheet.filter hr.expense.sheet search New
view_hr_expense_sheet_form hr.expense.sheet.form hr.expense.sheet form New
view_hr_expense_sheet_graph hr.expense.sheet.graph hr.expense.sheet graph New
view_hr_expense_sheet_kanban hr.expense.sheet.kanban hr.expense.sheet kanban New
view_hr_expense_sheet_pivot hr.expense.sheet.pivot hr.expense.sheet pivot New
view_hr_expense_sheet_tree hr.expense.sheet.tree hr.expense.sheet tree New
view_product_hr_expense_form product.template.expense.form product.template div Inherits product.product_template_form_view
Models touched (9)

New fields (0)

No new fields.

Public methods (1)
  • reconcile(self, writeoff_acc_id=False, writeoff_journal_id=False)
    @api.multi

New fields (1)
  • expense_sheets_to_approve_count Integer
    compute='_compute_expense_sheets_to_approve' string='Expenses Reports to Approve'
Public methods (0)

No public methods.

New fields (20)
  • account_id Many2one → account.account
    default=<expr> help='An expense account is expected' states={'post': [('readonly', True)], 'done': [('readonly', True)]} string='Account' args: 'account.account'
  • analytic_account_id Many2one → account.analytic.account
    oldname='analytic_account' states={'post': [('readonly', True)], 'done': [('readonly', True)]} string='Analytic Account' args: 'account.analytic.account'
  • attachment_number Integer
    compute='_compute_attachment_number' string='Number of Attachments'
  • company_id Many2one → res.company
    default=<expr> readonly=True states={'draft': [('readonly', False)], 'refused': [('readonly', False)]} string='Company' args: 'res.company'
  • currency_id Many2one → res.currency
    default=<expr> readonly=True states={'draft': [('readonly', False)], 'refused': [('readonly', False)]} string='Currency' args: 'res.currency'
  • date Date
    default=fields.Date.context_today readonly=True states={'draft': [('readonly', False)], 'refused': [('readonly', False)]} string='Date'
  • description Text
  • employee_id Many2one → hr.employee
    default=<expr> readonly=True required=True states={'draft': [('readonly', False)], 'refused': [('readonly', False)]} string='Employee' args: 'hr.employee'
  • name Char
    readonly=True required=True states={'draft': [('readonly', False)], 'refused': [('readonly', False)]} string='Expense Description'
  • payment_mode Selection
    default='own_account' states={'done': [('readonly', True)], 'post': [('readonly', True)]} string='Payment By' args: [('own_account', 'Employee (to reimburse)'), ('company_account', 'Company')]
  • product_id Many2one → product.product
    domain=[('can_be_expensed', '=', True)] readonly=True required=True states={'draft': [('readonly', False)], 'refused': [('readonly', False)]} string='Product' args: 'product.product'
  • product_uom_id Many2one → product.uom
    default=<expr> readonly=True required=True states={'draft': [('readonly', False)], 'refused': [('readonly', False)]} string='Unit of Measure' args: 'product.uom'
  • quantity Float
    default=1 digits=dp.get_precision('Product Unit of Measure') readonly=True required=True states={'draft': [('readonly', False)], 'refused': [('readonly', False)]}
  • reference Char
    string='Bill Reference'
  • sheet_id Many2one → hr.expense.sheet
    copy=False readonly=True string='Expense Report' args: 'hr.expense.sheet'
  • state Selection
    compute='_compute_state' copy=False help='Status of the expense.' index=True readonly=True store=True string='Status' args: [('draft', 'To Submit'), ('reported', 'Reported'), ('done', 'Posted'), ('refused', 'Refused')]
  • tax_ids Many2many → account.tax
    states={'done': [('readonly', True)], 'post': [('readonly', True)]} string='Taxes' args: 'account.tax', 'expense_tax', 'expense_id', 'tax_id'
  • total_amount Float
    compute='_compute_amount' digits=dp.get_precision('Account') store=True string='Total'
  • unit_amount Float
    digits=dp.get_precision('Product Price') readonly=True required=True states={'draft': [('readonly', False)], 'refused': [('readonly', False)]} string='Unit Price'
  • untaxed_amount Float
    compute='_compute_amount' digits=dp.get_precision('Account') store=True string='Subtotal'
Public methods (7)
  • action_get_attachment_view(self)
    @api.multi
  • action_move_create(self)
    @api.multi
    main function that is called when trying to create the accounting entries related to an expense
  • get_empty_list_help(self, help_message)
    @api.model
  • message_new(self, msg_dict, custom_values=None)
    @api.model
  • submit_expenses(self)
    @api.multi
  • unlink(self)
    @api.multi
  • view_sheet(self)
    @api.multi

New fields (2)
  • alias_domain Char
    default=<expr> args: 'Alias Domain'
  • alias_prefix Char
    args: 'Default Alias Name for Expenses'
Public methods (2)
  • get_default_alias_prefix(self, fields)
    @api.model
  • set_default_alias_prefix(self)
    @api.multi

New fields (1)
  • description Char
    required=True string='Reason'
Public methods (1)
  • expense_refuse_reason(self)
    @api.multi

New fields (9)
  • amount Monetary
    required=True string='Payment Amount'
  • communication Char
    string='Memo'
  • company_id Many2one → res.company
    readonly=True related='journal_id.company_id' required=True string='Company' args: 'res.company'
  • currency_id Many2one → res.currency
    default=<expr> required=True string='Currency' args: 'res.currency'
  • hide_payment_method Boolean
    compute='_compute_hide_payment_method' help="Technical field used to hide the payment method if the selected journal has only one available which is 'manual'"
  • journal_id Many2one → account.journal
    domain=[('type', 'in', ('bank', 'cash'))] required=True string='Payment Method' args: 'account.journal'
  • partner_id Many2one → res.partner
    default=_default_partner_id required=True string='Partner' args: 'res.partner'
  • payment_date Date
    default=fields.Date.context_today required=True string='Payment Date'
  • payment_method_id Many2one → account.payment.method
    required=True string='Payment Type' args: 'account.payment.method'
Public methods (2)
  • expense_post_payment(self)
    @api.multi
  • get_payment_vals(self)
    Hook for extension

New fields (16)
  • account_move_id Many2one → account.move
    copy=False string='Journal Entry' args: 'account.move'
  • accounting_date Date
    string='Accounting Date'
  • address_id Many2one → res.partner
    string='Employee Home Address' args: 'res.partner'
  • attachment_number Integer
    compute='_compute_attachment_number' string='Number of Attachments'
  • bank_journal_id Many2one → account.journal
    default=<expr> help='The payment method used when the expense is paid by the company.' states={'done': [('readonly', True)], 'post': [('readonly', True)]} string='Bank Journal' args: 'account.journal'
  • company_id Many2one → res.company
    default=<expr> readonly=True states={'submit': [('readonly', False)]} string='Company' args: 'res.company'
  • currency_id Many2one → res.currency
    default=<expr> readonly=True states={'submit': [('readonly', False)]} string='Currency' args: 'res.currency'
  • department_id Many2one → hr.department
    states={'post': [('readonly', True)], 'done': [('readonly', True)]} string='Department' args: 'hr.department'
  • employee_id Many2one → hr.employee
    default=<expr> readonly=True required=True states={'submit': [('readonly', False)]} string='Employee' args: 'hr.employee'
  • expense_line_ids One2many → hr.expense
    copy=False states={'approve': [('readonly', True)], 'done': [('readonly', True)], 'post': [('readonly', True)]} string='Expense Lines' args: 'hr.expense', 'sheet_id'
  • journal_id Many2one → account.journal
    default=<expr> help='The journal used when the expense is done.' states={'done': [('readonly', True)], 'post': [('readonly', True)]} string='Expense Journal' args: 'account.journal'
  • name Char
    required=True string='Expense Report Summary'
  • payment_mode Selection
    default='own_account' readonly=True related='expense_line_ids.payment_mode' string='Payment By' args: [('own_account', 'Employee (to reimburse)'), ('company_account', 'Company')]
  • responsible_id Many2one → res.users
    copy=False readonly=True states={'submit': [('readonly', False)], 'submit': [('readonly', False)]} args: 'res.users', 'Validation By'
  • state Selection
    copy=False default='submit' help='Expense Report State' index=True readonly=True required=True string='Status' track_visibility='onchange' args: [('submit', 'Submitted'), ('approve', 'Approved'), ('post', 'Posted'), ('done', 'Paid'), ('cancel', 'Refused')]
  • total_amount Float
    compute='_compute_amount' digits=dp.get_precision('Account') store=True string='Total Amount'
Public methods (12)
  • action_get_attachment_view(self)
    @api.multi
  • action_open_journal_entries(self)
    @api.multi
  • action_sheet_move_create(self)
    @api.multi
  • approve_expense_sheets(self)
    @api.multi
  • check_consistency(self)
    @api.multi
  • create(self, vals)
    @api.model
  • paid_expense_sheets(self)
    @api.multi
  • refuse_expenses(self, reason)
    @api.multi
  • reset_expense_sheets(self)
    @api.multi
  • set_to_paid(self)
    @api.multi
  • unlink(self)
    @api.multi
  • write(self, vals)
    @api.multi

New fields (1)
  • can_be_expensed Boolean
    help='Specify whether the product can be selected in an HR expense.' string='Can be Expensed'
Public methods (1)
  • create(self, vals)
    @api.model

New fields (0)

No new fields.

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/9.0/hr_expense
VERSION
VERSION 2.0
CATEGORY
CATEGORYHuman Resources
LICENSE
LICENSELGPL-3
APPLICATION
APPLICATIONYes
AUTO-INSTALLABLE
AUTO-INSTALLABLENo
AUTHORS
AUTHORS
MAINTAINERS
MAINTAINERS
COMMITTERS
COMMITTERSOdoo Translation Bot, Martin Trigaux, Fabien Pinckaers, Antony Lesuisse, Olivier Dony, Thibault Delavallée, Josse Colpaert, Denis Ledoux, Fabien Meghazi, Christophe Simonis, Cedric Snauwaert, Nicolas Martinelli, Jérome Maes, Richard Mathot, qdp-odoo, Géry Debongnie, Damien Bouvy, Yannick Tivisse, Cédric Snauwaert, Martin Geubelle, Quentin De Paoli
WEBSITE
WEBSITEhttps://www.odoo.com/page/expenses
LAST TRACKING UPDATE
LAST TRACKING UPDATE2026-07-05 23:48:30
ODOO DEPENDENCIES
ODOO DEPENDENCIES odoo/odoo:
    - hr_contract
    - base_action_rule
    - base
    - resource
    - mail
    - base_setup
    - web_kanban
    - web
    - bus
    - hr
    - web_tip
    - account_accountant
    - account
    - product
    - decimal_precision
    - report
    - analytic
    - web_planner
PYTHON DEPENDENCIES
PYTHON DEPENDENCIES Not have
SYSTEM DEPENDENCIES
SYSTEM DEPENDENCIES Not have
DESCRIPTION
DESCRIPTION
Manage expenses by Employees
============================

This application allows you to manage your employees' daily expenses. It gives you access to your employees’ fee notes and give you the right to complete and validate or refuse the notes. After validation it creates an invoice for the employee.
Employee can encode their own expenses and the validation flow puts it automatically in the accounting after validation by managers.


The whole flow is implemented as:
---------------------------------
* Draft expense
* Submitted by the employee to his manager
* Approved by his manager
* Validation by the accountant and accounting entries creation

This module also uses analytic accounting and is compatible with the invoice on timesheet module so that you are able to automatically re-invoice your customers' expenses if your work by project.
    

Code Analysis

Views touched (11)
XML IDNameModelTypeStatus
hr_department_view_kanban hr.department.kanban.inherit hr.department data Inherits hr.hr_department_view_kanban
hr_expense_form_view hr.expense.form hr.expense form New
hr_expense_kanban_view hr.expense.kanban hr.expense kanban New
hr_expense_my_tree my.hr.expense.tree hr.expense tree New
hr_expense_refuse_wizard_view_form hr.expense.refuse.wizard.form hr.expense.refuse.wizard form New
hr_expense_view_graph hr.expense.graph hr.expense graph New
hr_expense_view_pivot hr.expense.pivot hr.expense pivot New
report_expense report_expense ir.ui.view qweb New
view_expenses_tree hr.expense.tree hr.expense tree New
view_hr_expense_filter hr.expense.filter hr.expense search New
view_product_hr_expense_form product.template.expense.form product.template div Inherits product.product_template_form_view
Models touched (6)

New fields (0)

No new fields.

Public methods (1)
  • reconcile(self, writeoff_acc_id=False, writeoff_journal_id=False)
    @api.multi

New fields (1)
  • expense_to_approve_count Integer
    compute='_compute_expense_to_approve' string='Expenses to Approve'
Public methods (0)

No public methods.

New fields (21)
  • account_move_id Many2one → account.move
    copy=False string='Journal Entry' track_visibility='onchange' args: 'account.move'
  • analytic_account_id Many2one → account.analytic.account
    domain=[('account_type', '=', 'normal')] oldname='analytic_account' states={'post': [('readonly', True)], 'done': [('readonly', True)]} string='Analytic Account' args: 'account.analytic.account'
  • attachment_number Integer
    compute='_compute_attachment_number' string='Number of Attachments'
  • bank_journal_id Many2one → account.journal
    default=<expr> help='The payment method used when the expense is paid by the company.' states={'done': [('readonly', True)], 'post': [('readonly', True)]} string='Bank Journal' args: 'account.journal'
  • company_id Many2one → res.company
    default=<expr> readonly=True states={'draft': [('readonly', False)]} string='Company' args: 'res.company'
  • currency_id Many2one → res.currency
    default=<expr> readonly=True states={'draft': [('readonly', False)]} string='Currency' args: 'res.currency'
  • date Date
    default=fields.Date.context_today readonly=True states={'draft': [('readonly', False)]} string='Date'
  • department_id Many2one → hr.department
    states={'post': [('readonly', True)], 'done': [('readonly', True)]} string='Department' args: 'hr.department'
  • description Text
  • employee_id Many2one → hr.employee
    default=<expr> readonly=True required=True states={'draft': [('readonly', False)]} string='Employee' args: 'hr.employee'
  • journal_id Many2one → account.journal
    default=<expr> help='The journal used when the expense is done.' states={'done': [('readonly', True)], 'post': [('readonly', True)]} string='Expense Journal' args: 'account.journal'
  • name Char
    readonly=True required=True states={'draft': [('readonly', False)]} string='Expense Description'
  • payment_mode Selection
    default='own_account' states={'done': [('readonly', True)], 'post': [('readonly', True)]} string='Payment By' args: [('own_account', 'Employee (to reimburse)'), ('company_account', 'Company')]
  • product_id Many2one → product.product
    domain=[('can_be_expensed', '=', True)] readonly=True required=True states={'draft': [('readonly', False)]} string='Product' args: 'product.product'
  • product_uom_id Many2one → product.uom
    default=<expr> readonly=True required=True states={'draft': [('readonly', False)]} string='Unit of Measure' args: 'product.uom'
  • quantity Float
    default=1 digits=dp.get_precision('Product Unit of Measure') readonly=True required=True states={'draft': [('readonly', False)]}
  • state Selection
    copy=False default='draft' help="When the expense request is created the status is 'To Submit'.\n It is submitted by the employee and request is sent to manager, the status is 'Submitted'. \nIf the manager approve it, the status is 'Approved'.\n If the accountant genrate the accounting entries for the expense request, the status is 'Waiting Payment'." index=True readonly=True required=True string='Status' track_visibility='onchange' args: [('draft', 'To Submit'), ('submit', 'Submitted'), ('approve', 'Approved'), ('post', 'Waiting Payment'), ('done', 'Paid'), ('cancel', 'Refused')]
  • tax_ids Many2many → account.tax
    states={'done': [('readonly', True)], 'post': [('readonly', True)]} string='Taxes' args: 'account.tax', 'expense_tax', 'expense_id', 'tax_id'
  • total_amount Float
    compute='_compute_amount' digits=dp.get_precision('Account') store=True string='Total'
  • unit_amount Float
    digits=dp.get_precision('Product Price') readonly=True required=True states={'draft': [('readonly', False)]} string='Unit Price'
  • untaxed_amount Float
    compute='_compute_amount' digits=dp.get_precision('Account') store=True string='Subtotal'
Public methods (10)
  • action_get_attachment_view(self)
    @api.multi
  • action_move_create(self)
    @api.multi
    main function that is called when trying to create the accounting entries related to an expense
  • approve_expenses(self)
    @api.multi
  • create(self, vals)
    @api.model
  • paid_expenses(self)
    @api.multi
  • refuse_expenses(self, reason)
    @api.multi
  • reset_expenses(self)
    @api.multi
  • submit_expenses(self)
    @api.multi
  • unlink(self)
    @api.multi
  • write(self, vals)
    @api.multi

New fields (1)
  • description Char
    required=True string='Reason'
Public methods (1)
  • expense_refuse_reason(self)
    @api.multi

New fields (1)
  • can_be_expensed Boolean
    help='Specify whether the product can be selected in an HR expense.'
Public methods (0)

No public methods.

New fields (0)

No new fields.

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/8.0/hr_expense
VERSION
VERSION 1.0
CATEGORY
CATEGORYHuman Resources
LICENSE
LICENSELGPL-3
APPLICATION
APPLICATIONYes
AUTO-INSTALLABLE
AUTO-INSTALLABLENo
AUTHORS
AUTHORSOpenERP SA
MAINTAINERS
MAINTAINERSOpenERP SA
COMMITTERS
COMMITTERSRaphael Collet, Stephane Wirtel, Odoo Translation Bot, Christophe Matthieu, Martin Trigaux, Fabien Pinckaers, Antony Lesuisse, Olivier Dony, Tejas Tank, Thibault Delavallée, Vo Minh Thu, Josse Colpaert, Launchpad Translations on behalf of openerp, Quentin (OpenERP), Denis Ledoux, Fabien Meghazi, ggh-openerp, Christophe Simonis, Xavier Morel, Goffin Simon, Simon Lejeune, Frédéric van der Essen, Vishmita, Richard Mathot, Géry Debongnie, Aaron Bohy, Vishmita Jadeja (openerp), Richard Mathot (OpenERP), ged-odoo, Gery Debongnie, Randhir Mayatra rma-openerp, Darshan Kalola (OpenERP), Darshan Kalola, Sunil Sharma (OpenERP), Rakesh Sindhav (OpenERP)
WEBSITE
WEBSITEhttps://www.odoo.com/page/expenses
LAST TRACKING UPDATE
LAST TRACKING UPDATE2026-07-05 23:45:39
ODOO DEPENDENCIES
ODOO DEPENDENCIES odoo/odoo:
    - hr
    - base_setup
    - base
    - web_kanban
    - web
    - mail
    - resource
    - board
    - account_accountant
    - account_voucher
    - account
    - product
    - decimal_precision
    - report
    - analytic
    - edi
    - email_template
PYTHON DEPENDENCIES
PYTHON DEPENDENCIES Not have
SYSTEM DEPENDENCIES
SYSTEM DEPENDENCIES Not have
DESCRIPTION
DESCRIPTION
Manage expenses by Employees
============================

This application allows you to manage your employees' daily expenses. It gives you access to your employees’ fee notes and give you the right to complete and validate or refuse the notes. After validation it creates an invoice for the employee.
Employee can encode their own expenses and the validation flow puts it automatically in the accounting after validation by managers.


The whole flow is implemented as:
---------------------------------
* Draft expense
* Confirmation of the sheet by the employee
* Validation by his manager
* Validation by the accountant and accounting entries creation

This module also uses analytic accounting and is compatible with the invoice on timesheet module so that you are able to automatically re-invoice your customers' expenses if your work by project.
    

Code Analysis

Views touched (10)
XML IDNameModelTypeStatus
product_expense_installer_tree_view product.product.tree product.product tree New
report_expense report_expense ir.ui.view qweb New
view_editable_expenses_tree hr.expense.expense.tree hr.expense.expense tree New
view_expenses_form hr.expense.form hr.expense.expense form New
view_expenses_line_tree hr.expense.line.tree hr.expense.line tree New
view_expenses_tree hr.expense.expense.tree hr.expense.expense tree New
view_hr_expense_filter hr.expense.expense.filter hr.expense.expense search New
view_hr_expense_report_graph hr.expense.report.graph hr.expense.report graph New
view_hr_expense_report_search hr.expense.report.search hr.expense.report search New
view_product_hr_expense_form product.template.expense.form product.template div Inherits product.product_template_form_view
Models touched (0)

No models found for this module.

REPOSITORY
REPOSITORYodoo/odoo
GIT
GIThttps://github.com/odoo/odoo.git
GIT FOLDER
GIT FOLDERhttps://github.com/odoo/odoo/tree/7.0/hr_expense
VERSION
VERSION 1.0
CATEGORY
CATEGORYHuman Resources
LICENSE
LICENSELGPL-3
APPLICATION
APPLICATIONYes
AUTO-INSTALLABLE
AUTO-INSTALLABLENo
AUTHORS
AUTHORSOpenERP SA
MAINTAINERS
MAINTAINERSOpenERP SA
COMMITTERS
COMMITTERSRaphael Collet, Jagdish Panchal (Open ERP), Twinkle Christian (OpenERP), Cecile Tonglet, Rucha (Open ERP), Atul Patel (OpenERP), Randhir Mayatra (OpenERP), Odoo Translation Bot, Turkesh Patel (Open ERP), Christophe Matthieu, Pinakin Nayi (OpenERP), Benoit Guillot, DBR (OpenERP), Harry (OpenERP), Antonin Bourguignon, pankita shah (Open ERP), Martin Trigaux, Ajay Chauhan (OpenERP), Foram Katharotiya (OpenERP), Bhumika (OpenERP), Purnendu Singh (OpenERP), Saurang Suthar (OpenERP), Fabien Pinckaers, Antony Lesuisse, Meera Trambadia (OpenERP), Saurang Suthar, Olivier Dony, Rajesh Prajapati (OpenERP), Divyesh Makwana (Open ERP), Thibault Delavallée, Vo Minh Thu, Hardik, Josse Colpaert, pso (OpenERP), Sbh (Openerp), Amit Patel (OpenERP), Launchpad Translations on behalf of openerp, vta vta@openerp.com, Quentin (OpenERP), Kuldeep Joshi (OpenERP), Nimesh (Open ERP), ara@tinyerp.com, Mayur Maheshwari (OpenERP), Jigar Amin - OpenERP, Sanjay Gohel (Open ERP), Alexis de Lattre, help, Fabien Meghazi, Jiten (OpenERP), Denis Ledoux dle@openerp.com, Khushboo Bhatt (Open ERP), dle@openerp.com, Bhumi Thakkar (Open ERP), Amit Patel, Frédéric van der Essen, Gilles Major, Vijaykumar Baladaniya, Yannick Vaucher, Ravish (Open ERP), Anand Patel (OpenERP), Amit Dodiya (OpenERP), Jean-Christophe VASSORT, Juan Rial
WEBSITE
WEBSITEhttp://www.openerp.com
LAST TRACKING UPDATE
LAST TRACKING UPDATE2026-07-05 23:43:09
ODOO DEPENDENCIES
ODOO DEPENDENCIES odoo/odoo:
    - hr
    - base_setup
    - base
    - web_kanban
    - web
    - mail
    - resource
    - process
    - board
    - account_voucher
    - account
    - product
    - decimal_precision
    - analytic
    - edi
    - email_template
    - account_accountant
PYTHON DEPENDENCIES
PYTHON DEPENDENCIES Not have
SYSTEM DEPENDENCIES
SYSTEM DEPENDENCIES Not have
DESCRIPTION
DESCRIPTION
Manage expenses by Employees
============================

This application allows you to manage your employees' daily expenses. It gives you access to your employees’ fee notes and give you the right to complete and validate or refuse the notes. After validation it creates an invoice for the employee.
Employee can encode their own expenses and the validation flow puts it automatically in the accounting after validation by managers.


The whole flow is implemented as:
---------------------------------
* Draft expense
* Confirmation of the sheet by the employee
* Validation by his manager
* Validation by the accountant and receipt creation

This module also uses analytic accounting and is compatible with the invoice on timesheet module so that you are able to automatically re-invoice your customers' expenses if your work by project.
    

Code Analysis

Views touched (11)
XML IDNameModelTypeStatus
board_hr_expense_form board.hr.expense.form board.board xpath Inherits hr.board_hr_form
product_expense_installer_tree_view product.product.tree product.product tree New
view_editable_expenses_tree hr.expense.expense.tree hr.expense.expense tree New
view_expenses_form hr.expense.form hr.expense.expense form New
view_expenses_line_tree hr.expense.line.tree hr.expense.line tree New
view_expenses_tree hr.expense.expense.tree hr.expense.expense tree New
view_hr_expense_filter hr.expense.expense.filter hr.expense.expense search New
view_hr_expense_report_graph hr.expense.report.graph hr.expense.report graph New
view_hr_expense_report_search hr.expense.report.search hr.expense.report search New
view_hr_expense_report_tree hr.expense.report.tree hr.expense.report tree New
view_product_hr_expense_form product.product.expense.form product.product div Inherits product.product_normal_form_view
Models touched (0)

No models found for this module.

REPOSITORY
REPOSITORYodoo/odoo
GIT
GIThttps://github.com/odoo/odoo.git
GIT FOLDER
GIT FOLDERhttps://github.com/odoo/odoo/tree/6.1/hr_expense
VERSION
VERSION 1.0
CATEGORY
CATEGORYHuman Resources
LICENSE
LICENSELGPL-3
APPLICATION
APPLICATIONYes
AUTO-INSTALLABLE
AUTO-INSTALLABLENo
AUTHORS
AUTHORSOpenERP SA
MAINTAINERS
MAINTAINERSOpenERP SA
COMMITTERS
COMMITTERSRaphael Collet, Jagdish Panchal (Open ERP), Rucha (Open ERP), Stephane Wirtel, Harry (OpenERP), Fabien Pinckaers, Antony Lesuisse, Meera Trambadia (OpenERP), Olivier Dony, Vo Minh Thu, Launchpad Translations on behalf of openerp, Quentin (OpenERP), Mayur Maheshwari (OpenERP), Jigar Amin - OpenERP, Alexis de Lattre, vro, olt@tinyerp.com, niv-openerp, Xavier ALT, Christophe Simonis, Ujjvala Collins (OpenERP), Xavier Morel, Minh Tran, Jean-Christophe VASSORT, Vaibhav (OpenERP), Carlos Vásquez, ksa (Open ERP), Els Van Vossel, Israel Fermin Montilla, Rifakat Haradwala (Open ERP), Hardik Ansodariy (OpenERP), ssu, rch-openerp, Bharat (OpenERP), ARA (OpenERP), aag (OpenERP), mtr, Mustufa Rangwala, skh, Atik Agewan (OpenERP)
WEBSITE
WEBSITEhttp://www.openerp.com
LAST TRACKING UPDATE
LAST TRACKING UPDATE2026-07-05 23:41:35
ODOO DEPENDENCIES
ODOO DEPENDENCIES odoo/odoo:
    - hr
    - base_setup
    - base
    - mail
    - base_tools
    - resource
    - process
    - board
    - account
    - product
    - decimal_precision
    - analytic
    - edi
    - email_template
PYTHON DEPENDENCIES
PYTHON DEPENDENCIES Not have
SYSTEM DEPENDENCIES
SYSTEM DEPENDENCIES Not have
DESCRIPTION
DESCRIPTION
This module aims to manage employee's expenses.
===============================================

The whole workflow is implemented:
    * Draft expense
    * Confirmation of the sheet by the employee
    * Validation by his manager
    * Validation by the accountant and invoice creation
    * Payment of the invoice to the employee

This module also uses the analytic accounting and is compatible with
the invoice on timesheet module so that you will be able to automatically
re-invoice your customer's expenses if your work by project.
    

Code Analysis

Views touched (12)
XML IDNameModelTypeStatus
board_hr_expense_form board.hr.expense.form board.board xpath Inherits hr.board_hr_form
board_hr_manager_expense_form board.hr.manager.expense.form board.board xpath Inherits hr.board_hr_manager_form
product_expense_installer_tree_view product.product.tree product.product tree New
view_editable_expenses_tree hr.expense.expense.tree hr.expense.expense tree New
view_expenses_form hr.expense.form hr.expense.expense form New
view_expenses_line_tree hr.expense.line.tree hr.expense.line tree New
view_expenses_tree hr.expense.expense.tree hr.expense.expense tree New
view_hr_expense_filter hr.expense.expense.filter hr.expense.expense search New
view_hr_expense_report_graph hr.expense.report.graph hr.expense.report graph New
view_hr_expense_report_search hr.expense.report.search hr.expense.report search New
view_hr_expense_report_tree hr.expense.report.tree hr.expense.report tree New
view_product_hr_expense_form product.product.expense.form product.product field Inherits product.product_normal_form_view
Models touched (0)

No models found for this module.