TIP: You can type at any time to perform a new search.
Expenses
hr_expense · odoo/odoo
🛠 Migration considerations
- Raw `cr.execute()` INSERT/UPDATE/DELETE bypasses the ORM (no compute/constrains/tracking/mail): `UPDATE product_template SET can_be_expensed = false WHERE type NOT IN ('consu', 'service')` - re-check the table/column names still match after upgrading. migration-raw-sql-write · source
Found by automated static analysis: patterns worth a look before/after upgrading this module to a newer Odoo version.
- 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 ID | Name | Model | Type | Status |
|---|---|---|---|---|
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_domainSelectionondelete={'expense': 'cascade'}selection_add=[('expense', 'Expense')]
No public methods.
New fields (2)
-
expense_idsOne2many → hr.expensecomodel_name='hr.expense'inverse_name='account_move_id' -
nb_expensesIntegercompute='_compute_nb_expenses'compute_sudo=Truestring='Number of Expenses'
-
action_open_expense(self) -
button_cancel(self)
New fields (1)
-
expense_idMany2one → hr.expensecopy=Trueindex='btree_not_null'string='Expense' args: 'hr.expense'
No public methods.
New fields (1)
-
expense_idsOne2manyrelated='move_id.expense_ids'
-
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_countIntegercompute='_compute_expenses_to_approve_count'string='Expenses to Approve'
No public methods.
New fields (2)
-
expense_manager_idMany2one → res.userscomodel_name='res.users'compute='_compute_expense_manager'domain=_group_hr_expense_user_domainhelp='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=Falsestore=Truestring='Expense Approver' -
filter_for_expenseBooleangroups='hr.group_hr_user,hr_expense.group_hr_expense_manager'search='_search_filter_for_expense'store=False
No public methods.
New fields (1)
-
expense_manager_idMany2one → res.usersreadonly=True args: 'res.users'
No public methods.
New fields (1)
-
filter_for_expenseBooleangroups='hr.group_hr_user'search='_search_filter_for_expense'store=False
No public methods.
New fields (47)
-
account_idMany2one → account.accountcheck_company=Truecomodel_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=Truereadonly=Falsestore=Truestring='Account' -
account_move_idMany2one → account.movecomodel_name='account.move'copy=Falseindex='btree_not_null'readonly=Truestring='Journal Entry' -
amount_residualMonetarycurrency_field='company_currency_id'readonly=Truerelated='account_move_id.amount_residual'string='Amount Due' -
approval_dateDatetimereadonly=Truestring='Approval Date' -
approval_stateSelectioncopy=Falsereadonly=Trueselection=EXPENSE_APPROVAL_STATE -
attachment_idsOne2many → ir.attachmentcomodel_name='ir.attachment'domain=[('res_model', '=', 'hr.expense')]inverse_name='res_id'string='Attachments' -
can_approveBooleancompute='_compute_can_approve'readonly=Truestring='Can Approve' -
can_resetBooleancompute='_compute_can_reset'readonly=Truestring='Can Reset' -
company_currency_idMany2one → res.currencycomodel_name='res.currency'readonly=Truerelated='company_id.currency_id'string='Report Company Currency' -
company_idMany2one → res.companycomodel_name='res.company'default=<expr>readonly=Truerequired=Truestring='Company' -
currency_idMany2one → res.currencycomodel_name='res.currency'compute='_compute_currency_id'default=<expr>precompute=Truereadonly=Falserequired=Truestore=Truestring='Currency' -
currency_rateFloatcompute='_compute_currency_rate'digits=(16, 9)readonly=Truetracking=True -
dateDatedefault=fields.Date.context_todaystring='Expense Date' -
department_idMany2one → hr.departmentcomodel_name='hr.department'compute='_compute_from_employee_id'copy=Falsestore=Truestring='Department' -
descriptionTextstring='Internal Notes' -
duplicate_expense_idsMany2many → hr.expensecomodel_name='hr.expense'compute='_compute_duplicate_expense_ids' -
employee_idMany2one → hr.employeecheck_company=Truecomodel_name='hr.employee'compute='_compute_employee_id'default=_default_employee_iddomain=[('filter_for_expense', '=', True)]index=Trueprecompute=Truereadonly=Falserequired=Truestore=Truestring='Employee'tracking=True -
former_sheet_idIntegerstring='Former Report' -
is_editableBooleancompute='_compute_is_editable'readonly=Truestring='Is Editable By Current User' -
is_multiple_currencyBooleancompute='_compute_is_multiple_currency'string='Is currency_id different from the company_currency_id' -
journal_idMany2one → account.journalcomodel_name='account.journal'readonly=Truerelated='payment_method_line_id.journal_id' -
label_currency_rateCharcompute='_compute_currency_rate'readonly=True -
manager_idMany2one → res.userscomodel_name='res.users'compute='_compute_from_employee_id'copy=Falsedomain=<expr>store=Truestring='Manager'tracking=True -
message_main_attachment_checksumCharrelated='message_main_attachment_id.checksum' -
nameCharcompute='_compute_name'copy=Trueprecompute=Truereadonly=Falserequired=Truestore=Truestring='Description' -
nb_attachmentIntegercompute='_compute_nb_attachment'string='Number of Attachments' -
payment_method_line_idMany2one → account.payment.method.linecomodel_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=Falsestore=Truestring='Payment Method' -
payment_modeSelectiondefault='own_account'required=Trueselection=[('own_account', 'Employee (to reimburse)'), ('company_account', 'Company')]string='Paid By'tracking=True -
price_unitFloatcompute='_compute_price_unit'copy=Truemin_display_digits='Product Price'precompute=Truereadonly=Truerequired=Truestore=Truestring='Unit Price' -
product_descriptionHtmlcompute='_compute_product_description' -
product_has_costBooleancompute='_compute_from_product' -
product_has_taxBooleancompute='_compute_from_product'string='Whether tax is defined on a selected product' -
product_idMany2one → product.productcheck_company=Truecomodel_name='product.product'domain=[('can_be_expensed', '=', True)]ondelete='restrict'string='Category'tracking=True -
product_uom_idMany2one → uom.uomcomodel_name='uom.uom'compute='_compute_uom_id'copy=Trueprecompute=Truestore=Truestring='Unit' -
quantityFloatdefault=1digits='Product Unit'required=True -
same_receipt_expense_idsMany2many → hr.expensecomodel_name='hr.expense'compute='_compute_same_receipt_expense_ids' -
selectable_payment_method_line_idsMany2many → account.payment.method.linecomodel_name='account.payment.method.line'compute='_compute_selectable_payment_method_line_ids'compute_sudo=True -
split_expense_origin_idMany2one → hr.expensecomodel_name='hr.expense'help='Original expense from a split.'string='Origin Split Expense' -
stateSelectioncompute='_compute_state'copy=Falsedefault='draft'index=Truereadonly=Trueselection=[('draft', 'Draft'), ('submitted', 'Submitted'), ('approved', 'Approved'), ('posted', 'Posted'), ('in_payment', 'In Payment'), ('paid', 'Paid'), ('refused', 'Refused')]store=Truestring='Status'tracking=True -
tax_amountMonetarycompute='_compute_tax_amount'currency_field='company_currency_id'help='Tax amount in company currency'precompute=Truestore=Truestring='Tax amount' -
tax_amount_currencyMonetarycompute='_compute_tax_amount_currency'currency_field='currency_id'help='Tax amount in currency'precompute=Truestore=Truestring='Tax amount in Currency' -
tax_idsMany2many → account.taxcheck_company=Truecolumn1='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=Truereadonly=Falserelation='expense_tax'store=Truestring='Included taxes' -
total_amountMonetarycompute='_compute_total_amount'currency_field='company_currency_id'inverse='_inverse_total_amount'precompute=Truereadonly=Falsestore=Truestring='Total'tracking=True -
total_amount_currencyMonetarycompute='_compute_total_amount_currency'currency_field='currency_id'precompute=Truereadonly=Falsestore=Truestring='Total In Currency'tracking=True -
untaxed_amountMonetarycompute='_compute_tax_amount'currency_field='currency_id'precompute=Truestore=Truestring='Total Untaxed Amount' -
untaxed_amount_currencyMonetarycompute='_compute_tax_amount_currency'currency_field='currency_id'precompute=Truestore=Truestring='Total Untaxed Amount In Currency' -
vendor_idMany2one → res.partnercomodel_name='res.partner'string='Vendor'
-
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.modelCreate 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_idsMany2many → hr.expensereadonly=True args: 'hr.expense'
-
action_approve(self) -
action_refuse(self) -
default_get(self, fields)@api.model
New fields (3)
-
accounting_dateDatedefault=fields.Date.context_todayhelp='Specify the bill date of the related vendor bill.'string='Accounting Date' -
company_idMany2one → res.companycomodel_name='res.company'default=<expr>readonly=Truestring='Company' -
employee_journal_idMany2one → account.journalcheck_company=Truecomodel_name='account.journal'default=_default_journal_iddomain=[('type', '=', 'purchase')]help='The journal used when the expense is paid by employee.'string='Journal'
-
action_post_entry(self)
New fields (2)
-
expense_idsMany2many → hr.expensecomodel_name='hr.expense' -
reasonCharrequired=Truestring='Reason'
-
action_refuse(self) -
default_get(self, fields)@api.model
New fields (15)
-
approval_dateDatetimereadonly=Truestring='Approval Date' -
approval_stateSelectioncopy=Falsereadonly=Trueselection=EXPENSE_APPROVAL_STATE -
company_idMany2one → res.companycomodel_name='res.company' -
currency_idMany2one → res.currencycomodel_name='res.currency' -
employee_idMany2one → hr.employeecomodel_name='hr.employee'required=Truestring='Employee' -
expense_idMany2one → hr.expensecomodel_name='hr.expense'string='Expense' -
manager_idMany2one → res.userscomodel_name='res.users'domain=<expr>readonly=Truestring='Manager' -
nameCharrequired=Truestring='Description' -
product_has_costBooleancompute='_compute_from_product_id'store=Truestring='Is product with non zero cost selected' -
product_has_taxBooleancompute='_compute_product_has_tax'string='Whether tax is defined on a selected product' -
product_idMany2one → product.productcheck_company=Truecomodel_name='product.product'domain=[('can_be_expensed', '=', True)]required=Truestring='Product' -
tax_amount_currencyMonetarycompute='_compute_tax_amount_currency'string='Tax amount in Currency' -
tax_idsMany2many → account.taxcheck_company=Truecomodel_name='account.tax'domain="[('type_tax_use', '=', 'purchase')]" -
total_amount_currencyMonetarycompute='_compute_from_product_id'readonly=Falserequired=Truestore=Truestring='Total In Currency' -
wizard_idMany2one → hr.expense.split.wizardcomodel_name='hr.expense.split.wizard'
-
default_get(self, fields)@api.model
New fields (7)
-
currency_idMany2one → res.currencycomodel_name='res.currency'related='expense_id.currency_id' -
expense_idMany2one → hr.expensecomodel_name='hr.expense'required=Truestring='Expense' -
expense_split_line_idsOne2many → hr.expense.splitcomodel_name='hr.expense.split'inverse_name='wizard_id' -
split_possibleBooleancompute='_compute_split_possible'help='The sum of after split shut remain the same' -
tax_amount_currencyMonetarycompute='_compute_tax_amount_currency'currency_field='currency_id'string='Taxes' -
total_amount_currencyMonetarycompute='_compute_total_amount_currency'currency_field='currency_id'string='Total Amount' -
total_amount_currency_originalMonetarycurrency_field='currency_id'help='Total amount of the original Expense that we are splitting'related='expense_id.total_amount_currency'string='Total amount original'
-
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_warningCharcompute='_compute_standard_price_update_warning'
-
write(self, vals)
New fields (1)
-
can_be_expensedBooleancompute='_compute_can_be_expensed'help='Specify whether the product can be selected in an expense.'readonly=Falsestore=Truestring='Expenses'
-
default_get(self, fields)@api.model
New fields (2)
-
company_expense_allowed_payment_method_line_idsMany2many → account.payment.method.linecheck_company=Truedomain="[('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_idMany2one → account.journalcheck_company=Truedomain="[('type', '=', 'purchase')]"help="The company's default journal used when an employee expense is created."string='Default Expense Journal' args: 'account.journal'
No public methods.
New fields (8)
-
company_expense_allowed_payment_method_line_idsMany2many → account.payment.method.linecheck_company=Truecomodel_name='account.payment.method.line'readonly=Falserelated='company_id.company_expense_allowed_payment_method_line_ids' -
expense_journal_idMany2one → account.journalcheck_company=Truedomain="[('type', '=', 'purchase')]"readonly=Falserelated='company_id.expense_journal_id' args: 'account.journal' -
hr_expense_alias_domain_idMany2one → mail.alias.domaincomodel_name='mail.alias.domain'compute='_compute_hr_expense_alias_domain_id'inverse='_inverse_hr_expense_alias_domain_id'readonly=False -
hr_expense_alias_prefixCharcompute='_compute_hr_expense_alias_prefix'readonly=Falsestore=True args: 'Default Alias Name for Expenses' -
hr_expense_use_mailgatewayBooleanconfig_parameter='hr_expense.use_mailgateway'string='Let your employees record expenses by email' -
module_hr_expense_extractBooleanstring='Send bills to OCR to generate expenses' -
module_hr_expense_stripeBooleanstring='Link your stripe issuing account to manage company credit cards for your employees through Odoo' -
module_hr_payroll_expenseBooleanstring='Reimburse Expenses in Payslip'
-
get_values(self)@api.model -
set_values(self)
Loading…
Loading…
Loading…
Loading…
Loading…
Loading…
Loading…
Loading…
Loading…
Loading…
Loading…
Loading…
Loading…