Stop salespeople changing the unit price in Odoo, and hide cost and margin (Odoo 20)
Read-only, hidden or required fields per user group, enforced on screens, imports, the API and AI agents

To stop salespeople from changing the unit price in Odoo 20, hide the cost and margin from them, or make any field read-only, required or hidden for one user group, the rule has to be checked on the server: Odoo Community has no per-group field setting, every internal user can read a product's cost, and a field made read-only in the form view can still be changed by import, through the API, or by an AI agent or n8n flow using the person's API key. Below: why prices and cost are open by default, the free workarounds and their limits, and a no-code server-side rule that holds for screens, imports, the API and AI agents, checked against the Odoo 20 source.
- Prices are free to edit. Unit Price is an ordinary editable field on the order line. The Sales setting Discounts shows or hides the Disc.% column for everyone; it has no per-group option.
- Cost and margin are visible to all employees. The product Cost and Sales Margin's Unit Cost, Margin and Margin (%) are limited to
base.group_user(shown as Role / User in Odoo 20), which every internal user has. - Read-only in a view is not a lock. Imports and API calls write to the model, not to the form, so they never see the view's settings.
- A server-side rule per field and user group is what actually holds, on every screen, import and API call.
Why salespeople can change the price and see the cost
In Odoo 20, Unit Price (price_unit) on a sales order line is computed from the pricelist but stays editable, and access rights are set per model: a user who may edit a quotation may edit all of its editable fields. There is no option in Settings to make one field read-only for one group.
The product Cost (standard_price) is declared with groups="base.group_user", and the Sales Margin module, which installs automatically with Sales, declares Unit Cost (purchase_price), Margin and Margin (%) the same way. That group is the ordinary internal user group, so every salesperson can read these values: on the form, in list columns they can add back, in exports, in pivot views and through the API.
Free ways to lock a field, and where they stop
- Make the field read-only or invisible in the form view. An inherited view, or Odoo Studio in Enterprise, can grey out Unit Price or hide the Unit Cost column; Studio can even show a field only to some groups. This is still a display setting. Importing a file (Odoo's import calls the model directly), XML-RPC, JSON-RPC, JSON-2, and connectors such as n8n or an MCP server for an AI agent all bypass the form, so the value still changes or can still be read.
- Turn off the Discounts setting. The Disc.% column disappears for everyone, managers included, and the unit price itself stays editable.
- Record rules. They decide which records a user may read or edit, not which fields. A rule cannot say "this user may edit the order but not its price".
- Change the code. A developer can override the model's
writeandcreateto refuse a field for a group, or change a field'sgroupsattribute so that only managers can read it. This does work on the server, but it is custom code to write, test and carry through every upgrade, and each new field needs another change.
The first three are free and quick, but none of them is checked when the value arrives at the server. If the rule matters (prices, cost, margin), it has to be checked there.
A read-only field that can still be changed by import
This is the most common surprise. A field made read-only in the form view looks locked, but Import (in the gear menu next to the list title) still offers it as a column and writes the file's values straight to the model, and so does every API call. Setting readonly=True on the Python field keeps it off the Import screen, but write, create and load sent through the API still accept the value. Neither is a check on the server, and that is where the check has to be.
Lock fields per user group on the server
We made Field Lock for this (GRAMSHIFT, Odoo 20, Community and Enterprise on your own server or Odoo.sh; Odoo Online does not accept third-party Python modules). You add rules instead of editing views: a model, the fields, a mode (Read-only, Required or Hidden), the user groups it applies to and the groups exempted. No code.
- Lock prices: Sales Order Line, Unit Price and Discount, Read-only, for Sales / User: Own Documents Only, except Sales / Administrator. Salespeople still create, quote and confirm; the price Odoo computes from the pricelist, also after a quantity change, is accepted. The Discount button below the order lines follows the rule.
- Hide cost and margin: Product, Cost, and Sales Order Line and Sales Order, Unit Cost, Margin and Margin (%), Hidden (add Sales Analysis, Margin and Margin (%), if salespeople open reports). For salespeople the columns and the Margin line of the totals disappear, the API returns the values empty, and exporting, filtering or grouping by them is refused. Managers see everything as before.
- Required for one team: Sales Order, Customer Reference, Required, for the sales team only, so every quotation carries the customer's PO number while other departments save as before.
The rule is checked on the values themselves, whatever sends them: the screen, an import, XML-RPC, JSON-RPC, JSON-2, or an AI agent using the salesperson's API key. A refused change rolls back the whole save and returns an access error that names the field (HTTP 403 on JSON-2, or 422 for a missing required field), so a person, an import file or an agent knows why. If you also want to control what an AI agent's API key may do beyond fields, AI API Key Permissions makes keys read-only or unable to delete, and AI API Key Approval holds their changes until a person approves them.