Repository
odoo/odoo · module folder
Module version
2.1
Category
Human Resources/Expenses
Folder size
6.43 MB
License
LGPL-3
Application
Yes
Auto-installable
No
Website
https://www.odoo.com/app/expenses
Last tracking update
2026-08-15 22:44:17
Authors
Odoo S.A.
Maintainers
Odoo S.A.
Committers
Xavier Morel, Raphael Collet, Thibault Delavallée, Odoo Translation Bot, 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, Louis Wicket (wil), Gaetan Vanden Bergh (gavb), Maximilien (malb), Victor Piryns (pivi), Thomas Becquevort (thbe), Mahamadasif Ansari, Gauthier Wala (gawa), hupo-odoo, Dylan Kiss (dyki), Antoine Dupuis (andu), Julien Alardot (jual), Chong Wang (cwg), Xavier Bol (xbo), Arnaud Sibille, omra-odoo, Nicolas Viseur (vin), guva-odoo, maad-odoo, Paul Stroobant, Seif (segn), kcv-odoo, mega, Louis Gobert, hatr-odoo, aamo-odoo, Patricia Daloze (pda), Abdelrahman Mahmoud (amah), gteboul, sabr, Saif (segn), Victor Miguel Armenta Carrillo, Mohammad Abdulmoneim (abdu), Pierre Lamotte (pila), Pierre Pulinckx, Bastien PIERRE, Michaël Mattiello, pish-odoo, Christihan Laurel [Vauxoo], Krzysztof Magusiak (krma), krip-odoo, Meet, sagu-odoo, Hadi El Yakhni, bhra-odoo, mojem, bhna-odoo, agbr-odoo, igbe, Dhwani Patel, Jeanne Delneste, h4818, leclerc-leo, malb, Amr Elkhatieb, jond-odoo, rapa, Mohamed Barakat, saurabh, smjo-odoo, Mauricio Rubio Haro, chpa-odoo, Oussema Heni, 3amo-magdy, Mathias Mathy, Léo Leclerc
Odoo dependencies
Python dependencies
None
System dependencies
None
Required by
fieldservice_expense, hr_expense_analytic_tag, l10n_din5008_expense, project_hr_expense, sale_expense, tms_expense
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 kanban Inherits hr.hr_department_view_kanban
hr_employee_search_view hr.employee.search.view hr.employee search Inherits hr.view_employee_filter
hr_employee_view_form_inherit_expense hr.employee.view.form.expense hr.employee form 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 kanban Inherits hr_expense_view_expenses_analysis_kanban
hr_expense_kanban_view_header hr.expense.kanban hr.expense kanban Inherits hr_expense_view_expenses_analysis_kanban
hr_expense_kanban_view_minimal hr.expense.kanban hr.expense kanban 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 form 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 search 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 kanban 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 search 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 form Inherits base.res_config_settings_view_form
view_employee_tree_inherit_expense hr.employee.list.expense hr.employee list Inherits hr.view_employee_tree
view_expenses_tree hr.expense.list hr.expense list Inherits hr_expense_view_expenses_analysis_tree
view_move_form_inherit_expense account.move.form.inherit account.move form 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 list Inherits hr_expense.view_expenses_tree
view_payment_form_inherit_expense account.payment.form.inherit account.payment form Inherits account.view_account_payment_form
view_product_hr_expense_form product.template.expense.form product.template form Inherits product.product_template_form_view
HTTP endpoints (0)

No HTTP endpoints found for this module.

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,hr_expense.group_hr_expense_manager' 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)

Loading…

Loading…

Loading…

Loading…

Loading…

Loading…

Loading…

Loading…

Loading…

Loading…

Loading…

Loading…

Loading…