☰
Odoo 17销售联系人权限控制:记录规则实战指南
2026/10/15 2:21:20 网站建设 项目流程

简介:本资源是一款专为Odoo 17定制的轻量级安全插件,面向使用Odoo进行CRM系统部署的中高级开发者与企业IT管理员,解决销售团队数据权限管控难题——确保普通销售人员仅能查看其名下分配的联系人,兼顾数据安全与业务灵活性。插件基于Odoo原生记录规则机制实现,核心逻辑封装在2个Python文件(init.py与__manifest__.py)中,权限策略通过1个XML文件(salesperson_contact_rule.xml)定义,辅以编译缓存与系统兼容性支持文件,整体结构简洁、可读性强,5个文件总计仅3KB,便于快速集成与二次定制。目前已有356人学习下载,说明该场景在实际项目中具有较高复用价值。用户获取后可直接安装启用,立即获得细粒度联系人视图隔离能力:既限制非授权访问,又保留对未分配联系人的可见性以支持潜在客户承接,同时默认豁免管理员角色,保障后台统一管理权。

1. Odoo 17插件实战:销售人员仅查看自己联系人——不是靠权限组硬砍,而是用记录规则动态过滤

你有没有遇到过这种场景:在Odoo 17里给销售团队开账号,明明已经把“联系人”菜单权限设为只读,结果A销售一刷新,还是能看见B销售的客户电话、跟进记录甚至报价单?这不是权限配置漏了,而是根本逻辑错了——Odoo的“菜单级权限”只管你能不能点开那个页面,不管页面里实际显示哪些数据。真正起作用的是记录规则(Record Rules),它像一道隐形的SQL WHERE子句,在每次查询res.partner表时自动追加user_id = [当前用户ID]。本篇讲的这个Odoo 17插件,就是把这套规则封装成可安装、可复用、可审计的模块,不改核心代码、不碰数据库手动SQL、不依赖第三方模块,纯原生ORM实现。适合正在做销售CRM定制、需要快速上线分权机制的实施顾问和二次开发工程师。如果你正被客户追问“为什么张三能看到李四的客户”,这篇就是你的后悔药。


2. 插件原理与选型依据:为什么不用“销售团队”而坚持“创建者绑定”

2.1 记录规则 vs 权限组:两个维度必须同时生效

很多人误以为只要在“设置 → 技术 → 安全 → 记录规则”里新建一条规则就完事了。错。Odoo的安全模型是双层闸门:

  • 权限组(Access Rights)控制CRUD操作粒度(如“销售员”组能否对res.partner执行read/write);
  • 记录规则(Record Rules)控制数据可见性粒度(如“销售员只能读取自己创建的res.partner”)。

二者缺一不可。如果只配规则但没给read权限,用户连联系人菜单都打不开;如果只配权限但没规则,所有联系人都暴露。本插件在__manifest__.py中明确声明了对res.partner的read权限,并在security/ir.model.access.csv中绑定到sales_team.group_sale_salesman组,确保权限基线正确。

2.2 为什么锚定create_uid而非user_id字段?

Odoo中联系人(res.partner)模型有两个关键用户字段:

  • create_uid:记录谁创建了这条联系人;
  • user_id:常用于指派给某销售员跟进(需手动设置或通过自动化动作)。

初学者常想用user_id做过滤,但现实很骨感:

  • 新建联系人时,user_id默认为空,除非销售主动点“指派”;
  • 客户导入时,user_id往往批量留空,导致这批数据对所有人不可见;
  • 销售离职交接时,user_id会被批量修改,但历史创建关系不能丢。

而create_uid是数据库写入时自动生成、不可篡改的强约束。本插件选择create_uid = user.id作为主过滤条件,确保“谁建的归谁看”这一业务铁律。后续若需支持指派制,我们会在第5章扩展user_id双规则逻辑。

2.3 规则域表达式:[('create_uid', '=', user.id)]的底层解析

记录规则的Domain值不是字符串拼接,而是Odoo ORM的安全域语法。[('create_uid', '=', user.id)]会被翻译为SQL:

WHERE "res_partner"."create_uid" = %s

其中%s由Odoo框架自动注入当前登录用户的ID,全程不经过Python字符串格式化,杜绝SQL注入。注意三点:

  • user.id是当前会话用户对象的ID,类型为int,不是字符串;
  • 域中不能用self.env.user.id,因为规则在服务端SQL层解析,不走Python上下文;
  • 若需多条件,用&连接:['&', ('create_uid', '=', user.id), ('active', '=', True)]。

提示:规则Domain必须是list类型,单条件也要写成[('field', 'op', value)],写成('field', '=', value)会直接报错ValueError: Domain must be a list。


3. 插件结构与安装部署:从源码包到生产环境一键启用

3.1 目录结构说明:为什么security/必须独立成目录

一个合规的Odoo 17插件必须遵循标准布局。本插件解压后目录如下:

salesperson_own_contacts/ ├── __manifest__.py # 模块元信息,声明依赖、数据文件、安全文件 ├── models/ │ └── __init__.py # 空文件,仅作Python包标识 ├── security/ │ ├── ir.model.access.csv # 定义res.partner的read权限(给salesman组) │ └── res_partner_rules.xml # XML定义记录规则,加载时机早于业务逻辑 ├── views/ │ └── __init__.py # 空文件 └── static/ # 无前端资源,故为空

关键点在于security/目录:Odoo要求记录规则XML必须放在security/下,且在__manifest__.py的'data'列表中优先于其他XML(如视图)。否则规则可能在模型加载前未注册,导致过滤失效。本插件__manifest__.py中'data'顺序为:

'data': [ 'security/ir.model.access.csv', 'security/res_partner_rules.xml', # 其他视图、动作等XML放后面 ],

3.2ir.model.access.csv详解:最小权限原则落地

该CSV文件内容精简到4行,严格遵循最小权限:

id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink access_res_partner_salesman,access.res.partner.salesman,model_res_partner,base.group_user,1,0,0,0 access_res_partner_salesman_full,access.res.partner.salesman.full,model_res_partner,sales_team.group_sale_salesman,1,1,1,0
  • 第一行:base.group_user(普通内部用户)仅获res.partner只读权限,防止非销售岗误操作;
  • 第二行:sales_team.group_sale_salesman(销售员组)获得完整CRU权限(无delete),因销售需编辑自己客户的地址、电话等;
  • perm_unlink=0是硬性要求:禁止销售删除联系人,避免数据丢失,删除操作应由管理员执行。

注意:CSV中model_id:id必须用XML ID(model_res_partner),不能写模型名res.partner,否则安装时报KeyError: 'model_res_partner'。

3.3res_partner_rules.xml核心代码与加载逻辑

该XML文件定义记录规则,必须包含<record>标签并指定model="ir.rule":

<?xml version="1.0" encoding="utf-8"?> <odoo> <data noupdate="1"> <record id="rule_salesperson_own_contacts" model="ir.rule"> <field name="name">Salesperson: Own Contacts Only</field> <field name="model_id" ref="model_res_partner"/> <field name="domain_force">[('create_uid', '=', user.id)]</field> <field name="groups" eval="[(4, ref('sales_team.group_sale_salesman'))]"/> <field name="global" eval="False"/> </record> </data> </odoo>

参数说明:

  • noupdate="1":标记为“不更新”,避免模块升级时被覆盖;
  • ref="model_res_partner":引用内置模型XML ID,非字符串;
  • domain_force:强制应用的域,user.id由框架注入;
  • groups:eval中(4, ref(...))表示“添加该组到规则”,4是Odoo ORM的“link”操作码;
  • global=False:规则仅对指定组生效,非全局(global=True会绕过组限制,极度危险)。

安装时,Odoo会将此规则写入数据库ir_rule表,并在每次res.partner查询时自动注入WHERE条件。

3.4 安装步骤:三步验证是否生效

  1. 放置插件:将salesperson_own_contacts/目录复制到Odoo addons路径(如/opt/odoo/custom/addons/);
  2. 更新模块列表:进入Odoo后台 → 设置 → 激活开发者模式 → 应用程序 → 点击右上角“更新应用程序列表”;
  3. 安装模块:搜索“Salesperson Own Contacts”,点击“安装”。

安装后立即验证:

  • 用管理员账号创建两个联系人,分别用销售员A、B登录;
  • A登录后进入“联系人”菜单,应仅看到自己创建的联系人;
  • 在浏览器地址栏输入/web#action=...直接跳转联系人列表,同样受规则约束——证明规则在URL路由层已生效。

提示:若安装后无效果,90%原因是未重启Odoo服务。Odoo 17对安全文件的加载是启动时一次性扫描,修改security/目录后必须重启服务才能重载规则。


4. 避坑指南:五个真实翻车现场与血泪修复方案

4.1 现象:销售员登录后联系人列表为空,连自己创建的也看不到

原因:ir.model.access.csv中perm_read=0,或group_id:id写错导致权限未绑定到销售员组。
解决:检查CSV中sales_team.group_sale_salesman是否存在(在设置→用户&公司→组中搜索确认),并确保perm_read=1。用管理员账号执行SQL验证:

SELECT * FROM ir_model_access WHERE group_id IN (SELECT id FROM res_groups WHERE name='Sales / User') AND model_id = (SELECT id FROM ir_model WHERE model='res.partner');

若无返回结果,说明权限未生效。

4.2 现象:规则生效,但销售员无法创建新联系人

原因:perm_create=0,或res.partner模型的create方法被其他模块重写并抛出异常。
解决:先确认CSV中perm_create=1;再检查是否有其他插件继承了res.partner并重写了create(),在重写方法中加入self.env.user.has_group('sales_team.group_sale_salesman')判断。本插件不干预创建逻辑,仅控制读取。

4.3 现象:导入联系人后,销售员看不到这批数据

原因:Excel导入时未映射create_uid字段,系统默认用uid=1(管理员)创建。
解决:在导入模板中显式添加create_uid/id列,填入销售员的XML ID(如base.user_admin)或数据库ID;或使用Odoo原生“导入”功能,在映射步骤勾选“创建者”字段并选择当前用户。

4.4 现象:销售员A能通过“销售订单”关联页看到销售员B的联系人

原因:记录规则只对res.partner模型的直接查询生效,而销售订单(sale.order)关联联系人时,ORM会绕过res.partner的规则,直接JOIN查询。
解决:必须为sale.order模型也添加对应规则,过滤其partner_id字段。本插件默认不处理,因涉及跨模型权限,需按客户实际流程定制。扩展方案见第5章。

4.5 现象:启用插件后,网站前台表单提交失败,报AccessError

原因:网站访客(public user)无sales_team.group_sale_salesman组,但规则强制create_uid=user.id,而public user的user.id为False,导致域解析失败。
解决:在domain_force中增加user.id存在性判断:

<field name="domain_force">['|', ('create_uid', '=', user.id), ('create_uid', '=', False)]</field>

允许create_uid为空的记录(如网站表单创建的联系人)对所有人可见,再通过其他规则(如website_published=True)控制。


5. 进阶用法:支持销售主管查看全部 + 指派制双轨过滤

5.1 为销售主管添加“穿透查看”权限

业务中常需销售主管查看团队全员联系人。不能简单给主管admin权限(破坏最小权限原则),而应通过规则优先级实现:Odoo按规则ID升序执行,后加载的规则覆盖前面的。我们在security/res_partner_rules.xml中追加主管规则:

<record id="rule_salesmanager_all_contacts" model="ir.rule"> <field name="name">Sales Manager: All Contacts</field> <field name="model_id" ref="model_res_partner"/> <field name="domain_force">[(1, '=', 1)]</field> <field name="groups" eval="[(4, ref('sales_team.group_sale_manager'))]"/> <field name="global" eval="False"/> </record>

[(1, '=', 1)]是恒真条件,等价于无过滤。因group_sale_manager组ID大于group_sale_salesman,该规则后加载,主管登录时将忽略销售员规则,看到全部数据。

5.2 实现user_id指派制:双规则共存策略

当客户要求“销售员只能看自己创建的,以及被指派给自己的”时,需合并两个条件。Odoo不支持单条规则内OR逻辑(|操作符在规则域中无效),必须拆分为两条规则并设置global=True:

<!-- 规则1:创建者 --> <record id="rule_created_by" model="ir.rule"> <field name="name">Created By</field> <field name="model_id" ref="model_res_partner"/> <field name="domain_force">[('create_uid', '=', user.id)]</field> <field name="groups" eval="[(4, ref('sales_team.group_sale_salesman'))]"/> <field name="global" eval="True"/> <!-- 关键:设为global --> </record> <!-- 规则2:指派给 --> <record id="rule_assigned_to" model="ir.rule"> <field name="name">Assigned To</field> <field name="model_id" ref="model_res_partner"/> <field name="domain_force">[('user_id', '=', user.id)]</field> <field name="groups" eval="[(4, ref('sales_team.group_sale_salesman'))]"/> <field name="global" eval="True"/> </record>

global=True表示该规则对所有用户生效(无论是否在groups中),但Odoo会对同一模型的多条global规则执行OR合并。最终效果等价于WHERE create_uid = ? OR user_id = ?。

5.3 跨模型关联过滤:销售订单中联系人可见性控制

销售订单(sale.order)的partner_id字段关联res.partner,但默认不继承res.partner的记录规则。需为sale.order单独添加规则:

<record id="rule_sale_order_partner" model="ir.rule"> <field name="name">Sale Order: Partner Visibility</field> <field name="model_id" ref="model_sale_order"/> <field name="domain_force">[ '|', ('partner_id.create_uid', '=', user.id), ('partner_id.user_id', '=', user.id) ]</field> <field name="groups" eval="[(4, ref('sales_team.group_sale_salesman'))]"/> </record>

注意:partner_id.create_uid是反向查找语法,Odoo ORM支持x2many和many2one字段的链式访问,此处表示“订单关联的联系人的创建者”。

5.4 验证规则是否生效的终极命令

不依赖UI,直接用Odoo shell验证最可靠。进入shell后执行:

# 切换到销售员A的环境 self.env = self.env(user=self.env.ref('base.user_demo').id) # 查询联系人,看SQL是否被注入 partners = self.env['res.partner'].search([]) print(partners.query) # 输出实际执行的SQL,应包含 "create_uid = 2"

若query中无create_uid条件,说明规则未加载或组未匹配。此时检查self.env.user.groups_id是否包含sales_team.group_sale_salesman。

从那以后我每次交付销售权限模块,都强制走一遍这四步:① CSV权限校验 ② XML规则domain语法检查 ③ Shell SQL输出验证 ④ 网站表单兼容性测试。少走一步,上线当天就得通宵改。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询