Way4Tech
๐Ÿ’ณ HR

Odoo Expenses Implementation

Employee expense claims, approval and reimbursement.

Odoo Expenses captures claims from a phone, routes them for approval and posts the accounting entry. Reimbursement through payroll or as a supplier payment is a configuration decision, not a limitation.

Odoo Expenses is two records rather than one. The individual claim is an hr.expense, and the report that carries it through submission, approval and posting is an hr.expense.sheet. Almost every problem on an expenses project is a problem with that second record: who owns it, who may approve it, which journal it posts to and which partner ends up in the payable. Get that spine right and the phone part is trivial.

The app ships in Community, so the claim, the approval and the journal entry cost nothing extra. What sits behind the Enterprise paywall is the part buyers assume is standard: receipt scanning, reimbursement inside a payslip, and any approval chain more elaborate than manager then finance. Knowing which of those you genuinely need settles the edition question before anyone starts counting users.

Volume is the other thing that gets underestimated. A hundred staff filing two claims a month is 2,400 claims a year, and if every claim goes in on its own report that is 2,400 journal entries, each carrying a tax line, an analytic account and a receipt an auditor may ask to see. Batching a week or a month of claims onto one report is the cheapest lever you have on that number. The design work is making the rest flow through to the ledger without a finance clerk retyping anything, and keeping the balance owed to employees visible on its own.

Who Odoo Expenses is for

  • → Companies with field sales, engineers or site staff who spend on their own cards and currently claim on a spreadsheet with receipts stapled to it.
  • → Professional services firms that need billable travel and subsistence pushed back onto a customer order, at cost or at a marked up rate.
  • → Groups issuing corporate cards who want the spend coded and reconciled at source instead of landing on finance as a monthly statement.
  • → Employers in the GCC and South Asia where reimbursement has to move through a banking channel and be evidenced for tax.
  • → It is not a procurement tool, so recurring supplier spend belongs in Purchase and vendor bills, not in an employee claim.
  • → It is a poor fit where every claim needs per diem maths by grade and destination, because Odoo has no per diem engine and that becomes development.

Key features

  • ✓ Submit from mobile with a photo
  • ✓ Multi-step approval
  • ✓ Company card versus employee paid
  • ✓ Post to accounting automatically
  • ✓ Reimburse via payroll or payment
  • ✓ Re-invoice expenses to a customer

What Odoo Expenses actually does

Capture on a phone, and by email

An employee opens Expenses in a browser on a phone or in the Odoo mobile app, photographs the receipt and files the claim against an expense category. There is also an incoming mail alias. The employee forwards the receipt and Odoo raises a draft expense on their employee record, taking the category reference and the amount from the subject line and keeping the attachment. Reading the figures off the image itself is Enterprise, through expense digitisation, which consumes prepaid credits per document scanned. In Community the employee types the figures.

Paid by employee versus paid by company

Every expense carries a Paid By choice with exactly two values. Employee to reimburse creates a liability to that person and posts against the work contact on their employee record, so a missing work contact stops the posting with an error that names the employee. Company means the money has already gone, usually on a corporate card, and the credit lands on the outstanding account set on the payment method or on the company default for expenses. If neither is configured, posting refuses and sends you to the settings. Getting this one field wrong is the most common cause of a wrong balance sheet, because the claim either invents a debt to an employee or hides one.

Approval that matches the delegation of authority

The report is submitted by the employee, approved by their manager or by an approver group, then posted by someone with accounting rights. That is two human steps out of the box. The default approver is the employee's manager, and where that manager has no user account Odoo falls back to the department manager, which is worth checking on site teams whose supervisor never logs in. Approval limits by amount, a second signature above a threshold, or routing by department are not native. They are built with automation rules on the sheet, or by moving the sanction into the Approvals app, which is Enterprise.

Tax, analytic coding and the accounting entry

Taxes default from the Vendor Taxes on the expense category, and only purchase type taxes can be selected. The field on the claim is labelled Included taxes, which tells you the model: the figure the employee types is the gross, and Odoo works the tax back out of it. The expense account comes from the category and falls back to the expense account on its product category, and Odoo will not let you point it at a receivable, payable, bank or credit card account. Each line takes an analytic distribution, so travel lands on the project, department or cost centre it belongs to. Posting the report writes one journal entry through a purchase type journal.

Reimbursement by payment or by payslip

Once posted, a report can be settled from the report itself with Register Payment, which opens the standard payment wizard and writes an ordinary outbound payment. Turning a week of those into one bank file is a separate question, because batch payments are Enterprise, so a Community site pays claims one at a time or rekeys them into the bank portal. The alternative is to clear the balance in payroll, which needs the Enterprise payroll app and its expense bridge, so the approved report appears as a payslip line and clears the payable when the payslip posts. Community has no payroll, so payment is the only route there.

Rebilling to a customer

An expense category can be set to re-invoice at cost or at sales price. Link the expense to a sale order and the amount appears on that order as a delivered line, ready for the next invoice run. This bridge is Community, so a consultancy recovering flights and hotels needs Sales installed but not an Enterprise subscription. Watch the tax: what you recharge is normally the net cost plus your own output tax, not the gross figure the employee paid.

Splitting a receipt, and the duplicate warning

One receipt often covers two things, a hotel bill with dinner on it being the usual case. The split wizard breaks a claim into parts, each with its own category, tax and analytic distribution, which keeps recoverable and blocked tax apart without asking the employee to file twice. Odoo also flags a probable duplicate, and it is worth knowing the exact test: same employee, same category, same date, same amount, same currency, same company. Change any one of those and no warning fires, so the same receipt claimed a day apart goes through. It also fires on genuine repeats such as a daily parking fee. Treat it as a prompt to the approver rather than a control.

How we configure Odoo Expenses

The category list is a chart of accounts in disguise

Expense categories are products with the expensable flag set, and each one points at an account and a tax. The list you agree with finance is what the general ledger will look like for years. Keep it short, because twelve to twenty categories covers most companies. A category with a fixed unit price is also how you carry a mileage rate or a fixed daily allowance, with the employee entering a quantity rather than an amount.

Employees, managers and the payable account

Every claimant needs an employee record with a manager, because the manager is the default approver, and a work contact, because the payable posts against that partner. Odoo refuses to post without one and names the employee in the error, which is the first support ticket on every rollout that skipped it. If everyone sits on the same default payable account, the aged payable shows one number and nobody can see who is owed what. Splitting to a dedicated employee payable account, or one per person, is a short decision that saves a year of reconciliation.

Journals and the corporate card question

Nominate a purchase type journal for expense postings. Company paid claims need one more thing that quietly stops go live: an outstanding account, set on the payment method or as the company default, because that is the credit side of the entry. Then decide whether the card itself is a bank journal with a statement feed or a liability account settled monthly. A feed is better. The posted expense becomes the counterpart the statement line reconciles against, and unclaimed card spend shows as an open line rather than vanishing. Without it, card spend gets captured twice, once by the employee and once by the bookkeeper.

Mileage and per diem without a mileage module

Odoo has no distance lookup and no per diem table. The working pattern is a category priced per kilometre or per day where the employee enters the quantity and Odoo computes the amount. Handle a rate change by adding a new category rather than editing the old one, so historic claims keep the rate that applied. If the rate varies by vehicle class, grade or destination, that is either several categories or a custom field with automation behind it, and Studio itself is Enterprise. Scope it as development either way.

Policy first, then permissions

Write the expense policy before touching the system: what is claimable, what evidence is required, the submission deadline and who approves what. Then map it onto the expense access groups and the approval steps. Most rollouts stall on adoption rather than configuration, so the first month wants a short illustrated guide in the local language and one visible refusal of a non-compliant claim.

Where Odoo Expenses projects go wrong

Tax computed on a tax inclusive total

Odoo assumes the amount an employee claims already includes tax, which is why the field is labelled Included taxes. The tax on the category has to be configured as price included to match. Attach a normally price excluded tax and every claim inflates by the tax rate. The journal entry still balances, so nothing looks broken until a return comes out short. Test it on day one with a single receipt you can check by hand, and repeat that test in every company, because tax records are per company.

Foreign currency claims and the rate that applies

A claim in another currency converts at the rate on the expense date, while the reimbursement moves at the rate on the payment date. The difference posts as an exchange gain or loss on the employee payable, which finance teams routinely read as an error. Agree the policy up front, configure the exchange difference accounts, and confirm the rate provider is actually scheduled to update rather than sitting on a rate loaded during the build.

Receipts that cannot support an input tax claim

A photograph of a card slip is not a tax invoice. Where the document does not name the company and show the supplier tax number and the tax amount, the input tax is not recoverable and the whole claim is a cost. Odoo will post the tax anyway, because it reads the category and never the paperwork, so the control has to be human or structural. Build it in: a no tax category for non-compliant receipts, an approval step where the approver opens the attachment instead of reading the total, and a rule that a claim with no attachment is refused rather than chased.

Approved is not paid

Status and Payment Status are two different fields on the report. Status stops at Posted, while Payment Status is read off the journal entry, so a whole team can believe an employee has been paid because the report looks finished. Someone has to own the payment run. The mirror image is a report that was posted and then paid manually from the bank without being matched back, which leaves the employee payable open indefinitely. Reconcile the payment against the report's own entry, not against a free text memo line.

Leavers with an open balance

Archiving an employee does not clear what the company owes them, and it does remove the record from default filters. Final settlement, gratuity and end of service pay are calculated in payroll while unpaid claims sit in accounting, and the two are rarely joined up. Put an open expenses check on the offboarding list, and run an aged payable by employee partner before the last payroll of anyone leaving.

What it works with

  • + Odoo Accounting, for the journal entry, the tax lines and the outbound payment
  • + Odoo HR, which supplies the employee, the manager and the work contact partner
  • + Odoo Payroll in Enterprise, where reimbursement is settled on the payslip
  • + Odoo Sales and Project, for costs rebilled to a customer order or costed to a project
  • + Analytic accounting, so travel lands on a department, project or cost centre
  • + Odoo Approvals in Enterprise, when spend needs sanctioning before it is incurred
  • + Bank and corporate card feeds, matched against company paid expenses at reconciliation
  • + Odoo Documents in Enterprise, where receipts must be retained in a controlled workspace

Odoo Expenses by country

Saudi Arabia (KSA)

Input VAT on an employee receipt is recoverable only against a compliant simplified tax invoice, which since ZATCA Phase 1 must carry the supplier VAT number and a QR code, so a plain card slip is not enough. Reimbursement routed through payroll also has to stay consistent with the salary file you send under the wage protection system, so most companies pay expenses as a separate transfer.

United Arab Emirates

Two rules shape the category list. Input VAT is blocked on entertainment provided to people who are not employees, and under the corporate tax law half of entertainment expenditure is disallowed as a deduction. Entertainment therefore needs its own category, its own account and no recoverable tax, so the year end adjustment is a report rather than an exercise in unpicking a general travel line.

Pakistan

Section 21 of the Income Tax Ordinance disallows the deduction where a single expenditure above the prescribed limit is settled in cash rather than through a banking channel, with carve outs for small payments, utilities and travel fares. The practical effect on a rollout is that petty cash becomes the expensive way to reimburse, so pay by transfer and keep the bank reference on the report. Sales tax adds a second condition, because input tax needs an invoice carrying the company's own registration, and a receipt made out to the employee gives no claim however good the photograph is.

India

Section 17(5) of the CGST Act blocks input tax credit on food and beverages, club membership and rent a cab unless the employer is obliged by law to provide them, so those need separate categories with no credit. Credit on anything else depends on the supplier reporting the invoice against your GSTIN so it appears in GSTR-2B, which never happens on a receipt raised in an employee's own name.

Turkey

Meal and transport allowances are exempt from income tax only up to daily limits that GIB resets each year, so a fixed allowance category needs an annual rate review. KDV recovery generally needs a fatura in the company name, and a retail receipt does not carry the same right. Supplier documents now arrive as e-Fatura or e-Arsiv, so matching them to a claim is a reconciliation task.

United Kingdom

Mileage is reimbursed at the HMRC approved rates, 45p a mile for the first 10,000 business miles in a car and 25p after that, so the mileage category is a rate that moves when HMRC does. VAT is reclaimable on the fuel element only, with a fuel receipt behind it. Under Making Tax Digital the figures must reach the VAT return through digital links, not a rekeyed total.

Germany

GoBD requires receipts to be kept in an unalterable and auditable form, and scanning the paper away needs a written Verfahrensdokumentation. Invoices up to 250 euro count as Kleinbetragsrechnungen and can support input VAT without the buyer name, which covers most claims. Domestic subsistence is reimbursed tax free at the statutory rates of 28 euro for a full day and 14 euro for an arrival or departure day.

France

Mileage on a personal car is reimbursed against the bareme kilometrique, which varies by fiscal horsepower and annual distance and is republished every year, so a single flat rate per kilometre will be wrong for someone. VAT is not recoverable on employee hotel accommodation and only partly recoverable on fuel for passenger cars, so one combined travel category produces a wrong return. Supplier receipts increasingly arrive as Factur-X.

Odoo Expenses FAQs

Is Odoo Expenses in Community, or do we need Enterprise?

The app is Community, and more of it is Community than buyers expect: the claim, the approval chain, the journal entry, the payment, the split wizard, and rebilling an expense to a sale order. Five things sit on the Enterprise side. Reading the amounts off a receipt image, which is also an in-app purchase billed per document scanned, so it costs money on top of the subscription. Settling reimbursement on a payslip, because payroll is Enterprise. The Approvals app, if spend has to be sanctioned before it is incurred. Studio, for extra fields on the claim. Batch payment files, if finance pays claims in runs rather than one at a time. Price the edition against those five items and your user count, not against the app.

Does every employee need a paid user account to claim?

To file their own claims, yes. Expenses is a back office app and needs an internal user, so portal access does not reach it. Where a login is not worth paying for, site labour claiming twice a year for example, an approver or an expenses administrator files against the employee record on their behalf. The employee record has to exist either way, because it carries the manager, the payable partner and the analytic default. Only the login is optional.

Who can see whose claims?

A user with no expense group sees only their own reports. Team approver extends that to the employees who report to them, all approver to every report in the company, and administrator adds configuration and the posting rights. The part people miss is the journal entry, which is visible to anyone with accounting access, so nothing is confidential once a report posts. If a category is genuinely sensitive, keep it out of expenses rather than trying to hide it behind groups.

How does this behave across multiple companies?

The expense follows the employee's company, so the journal, the taxes and the payable all come from that company. Anyone claiming against two companies needs an employee record in each. Recharging between the two is an intercompany accounting exercise afterwards, not something the expense report does on your behalf. Tax configuration is per company as well, so a category tested in one is not tested in the other.

How do we correct a report that posted against the wrong category?

Before posting it is cheap. The approver refuses the report with a reason, the employee reads that reason in the chatter and resubmits. After posting the journal entry is in the way, so the accountant resets or reverses that entry first and the report then goes back to draft for correction. If the employee has already been paid, leave the reconciliation alone and post a correcting entry instead. Decide which of those two routes finance owns before go live.

Can we give staff a travel advance and settle claims against it?

Not with a dedicated feature, because standard Odoo has no employee advance model. The working pattern is accounting. Pay the advance as an outbound payment to the employee's work contact partner, which leaves a debit sitting on their account, then reconcile the posted expense report against it so only the balance moves either way. This only stays readable if each employee has their own partner and the payable can be aged by person. On one shared default payable it becomes guesswork inside a quarter.

Should we import historic expense claims during a migration?

Rarely. What matters at cutover is the open balance owed to each employee, loaded as an opening journal entry against the employee payable rather than as hundreds of approved reports. Importing closed history buys little, because the analytic and tax detail behind old claims is rarely clean enough to report on and it then sits beside live data looking authoritative. Keep it in the old system or in a reporting extract for the retention period, agree with finance which of the two answers an audit question, and start the live database with a clean list.

What reporting comes as standard?

Expenses has its own analysis view, a pivot and graph over the claims, so spend cuts by employee, category, date and analytic account without anything being built. The money side comes from accounting: the aged payable by employee partner shows what is owed, the tax report shows what was recovered. What does not exist is a per category limit that blocks a claim at entry. Budgets sit in Accounting against accounts and analytic accounts, and they report against actuals rather than stopping anyone spending.

Related HR modules