Odoo 17销售联系人权限控制:记录规则实战指南
简介本资源是一款专为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)]会被翻译为SQLWHERE 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_unlink0是硬性要求禁止销售删除联系人避免数据丢失删除操作应由管理员执行。注意CSV中model_id:id必须用XML IDmodel_res_partner不能写模型名res.partner否则安装时报KeyError: model_res_partner。3.3res_partner_rules.xml核心代码与加载逻辑该XML文件定义记录规则必须包含record标签并指定modelir.rule?xml version1.0 encodingutf-8? odoo data noupdate1 record idrule_salesperson_own_contacts modelir.rule field namenameSalesperson: Own Contacts Only/field field namemodel_id refmodel_res_partner/ field namedomain_force[(create_uid, , user.id)]/field field namegroups eval[(4, ref(sales_team.group_sale_salesman))]/ field nameglobal evalFalse/ /record /data /odoo参数说明noupdate1标记为“不更新”避免模块升级时被覆盖refmodel_res_partner引用内置模型XML ID非字符串domain_force强制应用的域user.id由框架注入groupseval中(4, ref(...))表示“添加该组到规则”4是Odoo ORM的“link”操作码globalFalse规则仅对指定组生效非全局globalTrue会绕过组限制极度危险。安装时Odoo会将此规则写入数据库ir_rule表并在每次res.partner查询时自动注入WHERE条件。3.4 安装步骤三步验证是否生效放置插件将salesperson_own_contacts/目录复制到Odoo addons路径如/opt/odoo/custom/addons/更新模块列表进入Odoo后台 → 设置 → 激活开发者模式 → 应用程序 → 点击右上角“更新应用程序列表”安装模块搜索“Salesperson Own Contacts”点击“安装”。安装后立即验证用管理员账号创建两个联系人分别用销售员A、B登录A登录后进入“联系人”菜单应仅看到自己创建的联系人在浏览器地址栏输入/web#action...直接跳转联系人列表同样受规则约束——证明规则在URL路由层已生效。提示若安装后无效果90%原因是未重启Odoo服务。Odoo 17对安全文件的加载是启动时一次性扫描修改security/目录后必须重启服务才能重载规则。4. 避坑指南五个真实翻车现场与血泪修复方案4.1 现象销售员登录后联系人列表为空连自己创建的也看不到原因ir.model.access.csv中perm_read0或group_id:id写错导致权限未绑定到销售员组。解决检查CSV中sales_team.group_sale_salesman是否存在在设置→用户公司→组中搜索确认并确保perm_read1。用管理员账号执行SQL验证SELECT * FROM ir_model_access WHERE group_id IN (SELECT id FROM res_groups WHERE nameSales / User) AND model_id (SELECT id FROM ir_model WHERE modelres.partner);若无返回结果说明权限未生效。4.2 现象规则生效但销售员无法创建新联系人原因perm_create0或res.partner模型的create方法被其他模块重写并抛出异常。解决先确认CSV中perm_create1再检查是否有其他插件继承了res.partner并重写了create()在重写方法中加入self.env.user.has_group(sales_team.group_sale_salesman)判断。本插件不干预创建逻辑仅控制读取。4.3 现象导入联系人后销售员看不到这批数据原因Excel导入时未映射create_uid字段系统默认用uid1管理员创建。解决在导入模板中显式添加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_uiduser.id而public user的user.id为False导致域解析失败。解决在domain_force中增加user.id存在性判断field namedomain_force[|, (create_uid, , user.id), (create_uid, , False)]/field允许create_uid为空的记录如网站表单创建的联系人对所有人可见再通过其他规则如website_publishedTrue控制。5. 进阶用法支持销售主管查看全部 指派制双轨过滤5.1 为销售主管添加“穿透查看”权限业务中常需销售主管查看团队全员联系人。不能简单给主管admin权限破坏最小权限原则而应通过规则优先级实现Odoo按规则ID升序执行后加载的规则覆盖前面的。我们在security/res_partner_rules.xml中追加主管规则record idrule_salesmanager_all_contacts modelir.rule field namenameSales Manager: All Contacts/field field namemodel_id refmodel_res_partner/ field namedomain_force[(1, , 1)]/field field namegroups eval[(4, ref(sales_team.group_sale_manager))]/ field nameglobal evalFalse/ /record[(1, , 1)]是恒真条件等价于无过滤。因group_sale_manager组ID大于group_sale_salesman该规则后加载主管登录时将忽略销售员规则看到全部数据。5.2 实现user_id指派制双规则共存策略当客户要求“销售员只能看自己创建的以及被指派给自己的”时需合并两个条件。Odoo不支持单条规则内OR逻辑|操作符在规则域中无效必须拆分为两条规则并设置globalTrue!-- 规则1创建者 -- record idrule_created_by modelir.rule field namenameCreated By/field field namemodel_id refmodel_res_partner/ field namedomain_force[(create_uid, , user.id)]/field field namegroups eval[(4, ref(sales_team.group_sale_salesman))]/ field nameglobal evalTrue/ !-- 关键设为global -- /record !-- 规则2指派给 -- record idrule_assigned_to modelir.rule field namenameAssigned To/field field namemodel_id refmodel_res_partner/ field namedomain_force[(user_id, , user.id)]/field field namegroups eval[(4, ref(sales_team.group_sale_salesman))]/ field nameglobal evalTrue/ /recordglobalTrue表示该规则对所有用户生效无论是否在groups中但Odoo会对同一模型的多条global规则执行OR合并。最终效果等价于WHERE create_uid ? OR user_id ?。5.3 跨模型关联过滤销售订单中联系人可见性控制销售订单sale.order的partner_id字段关联res.partner但默认不继承res.partner的记录规则。需为sale.order单独添加规则record idrule_sale_order_partner modelir.rule field namenameSale Order: Partner Visibility/field field namemodel_id refmodel_sale_order/ field namedomain_force[ |, (partner_id.create_uid, , user.id), (partner_id.user_id, , user.id) ]/field field namegroups 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(userself.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输出验证 ④ 网站表单兼容性测试。少走一步上线当天就得通宵改。希望帮到你。本文还有配套的精品资源点击获取