Why your ERP project's biggest risk isn't the software
A retrospective on three implementations that nearly went off the rails for the same reason — and one that succeeded because we caught it early.
Odoo 19 · Inventory · Security
Out of the box, Odoo lets every salesperson see stock at every warehouse. For multi-location operations with regional sales teams, that's usually wrong. Here's how to fix it with one well-placed ir.rule.
A distributor we’ve been working with runs three warehouses — one in Richmond, one in Surrey, and a small consignment depot on Vancouver Island. Their sales team is split regionally. Each rep should only quote stock from the warehouse they’re responsible for; the Surrey rep shouldn’t promise units sitting in Richmond, because by the time the order ships, those units are usually already spoken for.
The default Odoo setup doesn’t enforce this. A salesperson can run a stock report against any warehouse and quote whatever’s available. That’s a feature when you’re a small single-site shop. It’s a bug when you have regional managers fighting over the same pallet.
You can solve this with a record rule. It’s twenty lines of XML and you don’t need to fork any of Odoo’s core modules.
res.users — allowed_warehouse_ids — that lists which warehouses each rep can see.ir.rule on stock.quant that filters stock records to those warehouses for users in the Sales / User group.stock.warehouse itself, so the warehouse dropdowns in quotations only show the allowed warehouses.Start with a small module. Call it aphid_warehouse_acl. Manifest, models, security, data — standard layout.
# models/res_users.py
from odoo import fields, models
class ResUsers(models.Model):
_inherit = "res.users"
allowed_warehouse_ids = fields.Many2many(
comodel_name="stock.warehouse",
relation="res_users_allowed_warehouse_rel",
column1="user_id",
column2="warehouse_id",
string="Allowed Warehouses",
help="Warehouses this user is permitted to see stock for. "
"Leave empty for full access (controlled by group instead).",
)
Nothing exciting. A many-to-many to stock.warehouse. The help text matters — we want admins to know that “empty” doesn’t mean “no access,” it means “fall back to the group rules.” We’ll wire that up in the rule’s domain.
This is the heart of it. We’re scoping stock.quant — the on-hand quantities table — to records whose warehouse falls inside the user’s allowed list.
<!-- security/ir_rules.xml -->
<record id="stock_quant_warehouse_acl" model="ir.rule">
<field name="name">Stock quants: restrict to allowed warehouses</field>
<field name="model_id" ref="stock.model_stock_quant"/>
<field name="groups" eval="[(4, ref('sales_team.group_sale_salesman'))]"/>
<field name="domain_force">
[
'|',
('user.allowed_warehouse_ids', '=', False),
('location_id.warehouse_id', 'in', user.allowed_warehouse_ids.ids),
]
</field>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_unlink" eval="False"/>
</record>
A few things worth noting in that domain:
'|' is an OR. If the user has no allowed warehouses configured, they fall through to a “see everything in your group” default. That keeps the rule additive — turning it on doesn’t lock everyone out by accident.location_id.warehouse_id rather than going straight to a warehouse field on the quant, because not every stock location belongs to a warehouse (think: transit, virtual, scrap). Quants in those locations will have warehouse_id = False, and the rule will filter them out for restricted users. That’s the correct behaviour — a regional rep shouldn’t see transit stock either.The stock quant rule is the meat. But if you stop there, your sales team will still see the other warehouses in dropdowns — they’ll just get empty results when they pick one. Cleaner is to filter the warehouse list itself.
<record id="stock_warehouse_acl" model="ir.rule">
<field name="name">Warehouses: restrict visibility for sales</field>
<field name="model_id" ref="stock.model_stock_warehouse"/>
<field name="groups" eval="[(4, ref('sales_team.group_sale_salesman'))]"/>
<field name="domain_force">
[
'|',
('user.allowed_warehouse_ids', '=', False),
('id', 'in', user.allowed_warehouse_ids.ids),
]
</field>
</record>
Inventory managers need the full picture. Odoo handles this elegantly: rules attached to specific groups are only evaluated against users in those groups. A user who is both a Salesperson and an Inventory Manager will have both rule sets applied, ORed together. So if your inventory manager has no allowed_warehouse_ids set, they’ll see everything through the manager group’s implicit unrestricted access. If they do have a list, you’ll restrict them — which is probably not what you want.
The clean fix is: only assign allowed_warehouse_ids values for users who should be restricted. Don’t populate the field for inventory managers, admins, or directors. The “empty means full access” branch we built into the domain handles it.
Three checks to run before you call it done:
stock.quant ID for a forbidden warehouse and try to open it directly via /odoo/action-stock.quant_action/<id>. Odoo should redirect to the list view, empty — not throw a permission error, not show the record. If it shows the record, your rule isn’t loaded.Three things to watch:
sale.order.warehouse_id in a view extension.Record rules are one of the most powerful and most underused features in Odoo’s permission model. A short rule like the one above replaces what most teams try to solve with custom modules, view overrides, or — worst case — trusting people to do the right thing. The whole module sits at about thirty lines of code and survives Odoo upgrades cleanly because it doesn’t touch any core logic.
If your team is wrestling with multi-warehouse permissions in Odoo and the patterns above don’t quite fit your situation, drop us a line. We’ve done variants of this for inventory, accounting and HR contexts.
About the author. Shane runs Aphid Consulting, an Odoo Silver Partner in Victoria, BC. He has been implementing ERP systems since the OpenERP 7 days and has the scars to prove it.
Need help with this?
Talk to a real Odoo consultant about your inventory permission model.