Expense policies live in a PDF on the shared drive
Almost every business has an expense policy. Very few have one that’s enforced where the claim is made. The usual setup is a document on a shared drive, a manager who approves on trust, and a finance person who spots the $112 dinner three weeks later and has to decide whether it’s worth an argument.
The timing is backwards. The employee knows the facts at the moment they enter the claim. The system knows the policy. Checking one against the other then is cheaper for everyone than an email thread at month end. It’s also fairer, because the rule applies the same way to the new starter and the sales director. Finance stops being the department that says no after the money is spent, which does wonders for how often people answer their emails.
Policy checks inside the expense management system
On Odoo, expense categories are products flagged as expensable, and each claim line is an hr.expense. The addon adds caps and rules to those categories and enforces them with constraints, so they apply whether the claim comes from the web, the mobile app or an import. Mileage rates sit in company settings, so finance can change them without a developer.
On ERPNext, Expense Claim and Expense Claim Type come from the Frappe HR app. The generated app adds a cap on Expense Claim Type, a validate hook on Expense Claim and, if you want it, a Workflow that routes claims to the department approver and then to finance above a limit.
The difference between _inherit on an existing model and creating a new one matters here. We extend the standard expense objects rather than building a parallel “policy expense”, and our post on the two kinds of Odoo model inheritance shows why that keeps accounting and reporting intact.
Example: meals and mileage for a field sales team
A building products company has a field sales team visiting merchants and sites. Policy: meals are capped at $45 per person per day, mileage is paid at the company’s internal rate of $0.45 per km, and anything over $75 needs a receipt.
On a Tuesday, a rep logs lunch with a merchant at $28. That evening, stuck on the road, they add dinner at $31. The constraint adds the two, sees $59 against a $45 cap and stops the second line with a message saying $28 was already claimed that day. The rep can claim $17 of dinner, or ask their manager to approve an exception with a note. Either way, the decision happens that night, not three weeks later, and the manager approving the exception can see exactly what they’re agreeing to.
The same rep drives 212 km for site visits. They enter the distance and purpose. The claim comes out at $95.40, above the receipt threshold, but a mileage claim has no receipt, so the rule accepts a route description instead. Small detail. Get it wrong and every rep in the company will tell you about it by Friday.
Policies we’d rather you didn’t encode
Some rules are better as guidance than as code.
- Hard blocks on everything. Caps on meals are fine. Blocking a hotel claim during a snowstorm because it’s $20 over is not. For some categories, flag and approve instead of stopping.
- Five approval levels. Two is usually enough. Every extra layer mostly adds waiting.
- Tax recoverability logic. Whether a given expense is claimable for tax purposes depends on local rules. Keep it in the tax configuration and ask your accountant.
- Receipt checks that read the image. Matching amounts from photos is unreliable enough to create more work than it saves. Require the attachment and let a person look when it matters.
From approved claim to paid and posted
Approved claims turn into journal entries and payables, which is where the checks in our custom accounting module take over. If you reimburse through salary, the claim needs to reach payroll cleanly. And if you bill travel back to clients, link each claim to a project through project management so it shows up in project costs rather than general overhead.
If you’re on Odoo and the gap is mostly fields and views, our Odoo customization page covers the lighter-touch options.