Employee expense management in Odoo is simple conceptually but produces accounting errors reliably when the product configuration or approval workflow is misconfigured. A single wrong account on an expense product category means every expense of that type posts to the wrong ledger - and those errors often aren't caught until the external auditor reviews the accounts three months later.
This guide walks through the correct configuration, the approval and payment flow, and the traps to avoid.
The data model#
Expenses live in two models:
hr.expense- a single expense line (one receipt or mileage claim)hr.expense.sheet- an expense report containing one or more expense lines
Employees create individual expenses and group them into a sheet before submitting. Approvers review and approve the sheet, not individual expense lines. Accounting posts journal entries from the sheet, not from individual expenses.
hr.expense key fields#
| Field | Purpose |
|---|---|
name | Description (e.g., "Client dinner - Paris") |
product_id | Expense product (determines account, tax, and limits) |
date | Expense date (affects which period it posts to) |
total_amount | Amount in the expense currency |
currency_id | Expense currency (can differ from company currency) |
quantity | Used for mileage (quantity = km, product price = rate per km) |
employee_id | Who is claiming this expense |
sheet_id | Parent expense sheet |
company_id | Company for multi-company environments |
Expense products and accounts#
Every expense is categorized through a product (product.template with can_be_expensed = True). The product determines:
- Expense account - the P&L account the expense posts to (e.g., 6200 Travel, 6210 Meals, 6300 Office Supplies)
- Tax - input VAT or similar recoverable tax applied to the expense
- Default unit of measure - for mileage, use a "km" or "miles" UoM and set the product price to the reimbursement rate per unit
The account is set on the product's Accounting tab under Expense Account. If left blank, Odoo falls back to the account configured in Accounting → Configuration → Settings → Default Accounts → Expense.
Configuring expense products correctly is the most important part of expenses setup. Walk through every category your company reimburses, create a product for each, and explicitly set the correct account. Do not rely on the fallback account - it will map all uncategorized expenses to the same ledger line.
Mileage expenses#
For mileage reimbursement, create a product:
- Name: "Mileage"
- Price: 0.30 (reimbursement rate per km)
- UoM: km (or miles)
- Expense account: the vehicle/travel expense account
When an employee creates a mileage expense, they enter the distance as the quantity. Odoo computes quantity × price = reimbursement amount. If you need different rates for different vehicle types or countries, create a separate product per rate.
Submission and approval workflow#
The expense workflow:
Draft expense → Submitted sheet → Approved → Posted → Paid- Employee creates expenses (hr.expense, state=draft) and groups them into a sheet.
- Employee submits the sheet - state changes to
submit. Triggers a notification to the employee's manager (or the team leader if no direct manager). - Manager approves or refuses - on approval, state →
approve. On refusal, the employee can correct and resubmit. - Accountant posts the journal entry - state →
post. The expense amounts are debited to the expense accounts and credited to the employee payable account. - Accountant registers payment - the employee payable is settled (bank transfer, next payslip inclusion, or manual payment).
Approval routing#
The approver is determined by the employee's manager (employee_id.parent_id). If the employee has no manager, Odoo assigns no default approver - the sheet stays in submit state indefinitely unless someone approves it manually.
For policy-based approval (e.g., expenses above €500 require director approval), Odoo does not have a native multi-level approval flow. The standard workaround is to use two approval stages via a custom module, or to enforce the policy procedurally (expense policy document + manager training).
Accounting integration#
When the accountant posts an approved sheet, Odoo creates a journal entry:
Dr 6200 Travel Expenses [total expense amount]
Cr 2140 Employee Payables [same amount]The employee payable account is configured in Accounting → Configuration → Settings → Employee Payable Account. This is typically a dedicated liability account per employee or a single pooled "Employees to pay" account, depending on your chart of accounts.
Multi-currency: if the expense is in a foreign currency, Odoo records the expense at the exchange rate on the expense date. The journal entry uses the company currency equivalent. Exchange differences are posted to the currency gain/loss account at settlement.
Payment methods#
Odoo supports three payment paths:
Direct payment - the company pays the employee directly (bank transfer). The accountant registers the payment against the employee payable line, which reconciles the liability.
Company card - the expense is paid by a company credit card. The liability is owed to the card issuer, not the employee. Configure a separate payment mode for company card expenses so the payable account maps to the card liability account instead of an employee account.
Payslip - expense reimbursement is included in the monthly payslip as a salary input. Requires the HR Payroll module and a salary rule that reads the employee's pending expense reimbursements.
Policy controls#
Expense limits - Odoo does not have a native per-category spend limit. You can add a limit in the warning-only sense by using a scheduled action or automation rule that flags sheets above a threshold for additional review.
Receipt requirement - the Attach receipt field on the expense product marks whether a receipt attachment is required before submission. If enabled and no attachment is present, Odoo warns but does not block submission by default in all versions. Validate your version's behavior and consider a custom validation if hard blocking is required.
Date range - employees can submit expenses for any date, including past periods that are already locked in accounting. If you lock periods (Accounting → Configuration → Settings → Lock Date), posted journal entries cannot be altered, but the expense creation itself is not blocked. Coordinate with your accountant on expense submission cutoffs.
Common mistakes#
Wrong expense account on a product. All expenses of that type post to the wrong ledger. Fix immediately when caught - backdated journal entry corrections require accountant access and create audit trail noise.
Employee has no manager set. Submitted sheet has no approver and no notifications go out. The sheet ages in submit state until someone notices. Enforce manager assignment as part of employee onboarding.
Multi-currency expenses with wrong date. The exchange rate is locked to the expense date. If an employee submits a January expense in March, the rate is the January rate, which may differ significantly from the current rate. The journal entry is correct, but the payment may create a small exchange difference that the accountant needs to reconcile.
Company card expenses posted to employee payable. The company doesn't owe the employee - it owes the card issuer. Using the wrong account inflates employee payables and understates card liabilities. Create a separate payment method type for card expenses.
For HR payroll integration where expense reimbursements flow through payslips, see the HR and payroll guide. For analytic account tracking of expense categories, see the project management and timesheets guide.

