ERPseries / 进销存与业财一体化

业财通识28:权限设计——谁能看、谁能改、谁能批

上一篇我们聊了状态机。状态机解决的是业务怎么流转的问题:一张采购申请从"草稿"到"待审批"再到"已审批",每一步都需要明确的事件触发,状态之间不能乱跳。 但状态机只管"规则",不管"人"。 想象这样一个场景:一个刚入职的实习生,在系统里看到…

16 分钟阅读
ERP
浏览量加载中...
文章信息
分类ERP
阅读16 min
Article body
正文内容

导言:状态机管流转,权限管谁能参与

上一篇我们聊了状态机。状态机解决的是业务怎么流转的问题:一张采购申请从"草稿"到"待审批"再到"已审批",每一步都需要明确的事件触发,状态之间不能乱跳。

但状态机只管"规则",不管"人"。

想象这样一个场景:一个刚入职的实习生,在系统里看到"审批"按钮,顺手把自己提交的采购申请点了通过。这张申请绕过了主管审批、绕过了财务审核,直接生效。状态机并没有报错,因为"审批通过"确实是一个合法的状态迁移事件。

问题出在"人"身上——这个人不该有这个权限。

这就是我们今天要讨论的主题:ERP 系统的权限设计。如果说状态机定义了业务流转的"交通规则",那么权限系统就是"驾照考试"和"道路限行"。没有后者,再好的规则也会被人为绕过。

权限设计为什么比普适系统更复杂

做过普通业务系统的工程师都知道,权限通常就是"谁能登录、谁能管理"。但在 ERP 里,这个问题要复杂得多。

第一,模块跨度大。 一个 ERP 系统里同时存在采购、销售、库存、财务等多个业务域。采购员需要创建采购订单,但不需要看到客户的信用额度;销售人员需要查看客户信息,但不该知道供应商的采购底价。

第二,角色密度高。 同一家公司里,采购员、采购主管、库管员、财务会计、财务经理、销售代表、销售总监……每个角色对同一个功能的操作范围都可能不同。

第三,数据敏感度分层。 客户的联系方式是低敏感数据,几乎所有销售都能看;客户的欠款余额和信用额度是高敏感数据,通常只有风控和财务能看;供应商的结算价格和账期是商业机密,可能只有采购主管以上级别才能接触。

第四,合规与审计要求。 ERP 系统的操作往往直接对应企业的资金进出和资产变动。如果采购员既能下单又能审批自己的订单,出了问题很难追溯责任。审计人员会要求系统证明"谁、在什么时间、对哪张单据做了什么操作"。

所以,ERP 的权限设计不是"加几个白名单"那么简单。它需要同时回答三个问题:

  1. 功能权限:这个人能不能点这个按钮?
  2. 数据权限:点了之后,他能看哪些数据?
  3. 操作权限:他能不能改、能不能删、能不能审批?

RBAC 三层模型:给用户发"门禁卡"

业界最常用的权限模型是 RBAC(Role-Based Access Control,基于角色的访问控制)

它的核心思想很简单:不直接给用户分配权限,而是给"角色"分配权限,再把用户绑定到角色上。这就像是公司大楼的门禁系统——不是给每个人单独配钥匙,而是按部门发门禁卡,每张卡能开哪些门是预先设定好的。

业务定义:RBAC 是一种将权限与角色关联、再将角色与用户关联的访问控制模型。它的目标是降低权限管理的复杂度:当员工入职、调岗或离职时,只需调整其角色绑定,而不需要逐个修改数百项权限配置。

RBAC 的经典三层结构如下:

graph TD;
    A[用户 User] -->|属于| B[角色 Role];
    C[用户 User] -->|属于| D[角色 Role];
    B -->|拥有| E[权限 Permission];
    D -->|拥有| F[权限 Permission];
    E -->|控制| G[功能操作];
    F -->|控制| H[数据范围];

用户(User):系统的具体使用者,对应一个真实的人或系统账号。

角色(Role):一组权限的集合,对应业务中的一个岗位或职能。例如"采购员"、"销售经理"、"财务会计"。

权限(Permission):对系统资源的操作许可。例如"创建采购订单"、"查看客户信用额度"、"审批付款申请"。

这种分层的好处是:当公司新增一个采购员时,管理员只需要把他加入"采购员"这个角色,他就自动拥有了采购员应有的全部权限。如果公司决定让所有采购员都能查看库存可用量,只需要修改"采购员"角色的权限配置,所有绑定该角色的用户会同时生效。

反过来看,如果没有角色这层抽象,每来一个新人,管理员都要手动勾选几十项权限,既低效又容易出错。

功能权限 vs 数据权限:两个维度,不能混为一谈

在 RBAC 的基础上,ERP 系统通常会把权限再拆成两个维度:功能权限数据权限

很多人容易把这两者混为一谈,认为"有权限"就是"什么都能做"。实际上,功能权限决定"能不能操作",数据权限决定"能操作谁的数据"。

维度 定义 示例
功能权限 能否执行某个功能操作 销售代表能否点击"创建订单"按钮
数据权限 能操作的数据范围 销售代表创建订单时,客户下拉框里出现哪些客户

用一个更生活化的类比:功能权限像是"你能不能进厨房",数据权限像是"进了厨房你能用哪些食材"。两个维度叠加,才能完整描述一个人的系统行为边界。

功能权限的典型场景

  • 采购员可以"创建采购订单",但不能"审批采购订单"。
  • 财务可以"查看付款申请",但不能"创建采购订单"。
  • 库管员可以"执行出库操作",但不能"修改销售价格"。

数据权限的典型场景

  • 销售代表 A 只能看到自己负责的客户,看不到同事的客户。
  • 华东区经理可以看到华东区所有销售的数据,但看不到华北区的。
  • 普通会计只能看到本公司的财务凭证,集团 CFO 可以看到全集团的汇总报表。

在实际系统实现中,这两个维度通常需要联合校验。例如,当用户点击"查看订单详情"时,系统先检查他是否有"查看订单"的功能权限,再检查这张订单是否在他的数据权限范围内。只有两个条件同时满足,请求才能通过。

这里有一个容易踩坑的细节:很多系统在前端做了按钮隐藏,认为"看不到按钮就等于没权限"。但这只是用户体验层面的处理,真正的权限校验必须放在后端。一个稍有技术基础的用户,完全可以通过直接调用 API 来绕过前端限制。所以功能权限和数据权限的校验,必须是后端接口的第一道闸门。

职责分离原则:一个人不能既是裁判又是运动员

权限设计中最重要的一条原则,叫做 SoD(Segregation of Duties,职责分离)

业务定义:职责分离是一种内控机制,要求将一项完整业务流程中的关键操作拆分给不同角色执行,防止单一个体同时拥有"发起"和"审批"的权力,从而降低舞弊和误操作的风险。

在第 1 篇中,我们讲过采购申请(PR)的审批流程:申请人提交后,需要经过主管审批、财务审批,才能进入采购执行阶段。这个流程之所以要设计多个审批节点,核心目的之一就是职责分离。

如果采购员既能提交 PR 又能审批 PR,他就完全可以给自己买一台不需要的电脑,然后自己点通过。系统里没有任何人能发现这个问题,直到审计时才会暴露。

SoD 在 ERP 系统中最常见的应用规则包括:

  1. 制单人不能审批自己的单据:采购员创建的采购订单,不能由自己审批。
  2. 录入人不能复核自己录入的数据:会计录入的凭证,需要由另一位会计或主管复核。
  3. 审批人不能执行被审批的操作:审批付款申请的人,不能是实际执行付款操作的人。

这些规则听起来是"常识",但在系统实现上需要明确的校验逻辑。以下是一段简化版的伪代码,展示如何在订单审批时检查 SoD:

function canApproveOrder(orderId, userId) {
  const order = getOrderById(orderId);

  // 检查功能权限:当前用户是否有审批权限
  if (!hasPermission(userId, "ORDER_APPROVE")) {
    return { allowed: false, reason: "无审批权限" };
  }

  // 检查职责分离:制单人不能审批自己的订单
  if (order.createdBy === userId) {
    return { allowed: false, reason: "不能审批自己创建的单据" };
  }

  // 检查数据权限:只能审批自己数据范围内的订单
  if (!inDataScope(userId, order)) {
    return { allowed: false, reason: "该订单不在您的数据范围内" };
  }

  return { allowed: true };
}

这段代码只有 16 行,但它同时覆盖了功能权限、职责分离和数据权限三个层面。在真实的 ERP 系统中,SoD 规则通常会以配置表的形式存在,方便业务人员根据公司制度灵活调整,而不是硬编码在程序里。

权限矩阵:把角色和权限的关系可视化

当角色越来越多、权限越来越细时,用文字描述"谁能做什么"就变得非常低效。这时候,权限矩阵是一个很好的工具。

业务定义:权限矩阵是一张以角色为行、以功能/数据权限为列的二维表格,用于可视化展示每个角色在系统中的完整权限配置。它是需求评审、权限梳理和审计检查的标准交付物。

以下是一个简化版的 ERP 权限矩阵模板,按业务模块拆分为两张表:

采购与销售模块:

角色 采购模块 销售模块
采购员 创建/查看 PO
采购主管 审批 PO、查看报表
销售代表 创建 SO、查看客户
销售经理 审批 SO、查看报表
库管员 查看 PO 查看 SO
财务会计 查看 PO 查看 SO
财务经理 查看采购报表 查看销售报表

库存与财务模块:

角色 库存模块 财务模块
采购员 查看库存
采购主管 查看库存 查看应付
销售代表 查看可用库存
销售经理 查看库存 查看应收
库管员 入库/出库/盘点
财务会计 查看库存报表 录入凭证/对账
财务经理 查看库存报表 审批付款/查看总账

这两张表合起来,就是一张完整的角色权限蓝图。它的价值不在于有多详细,而在于把"谁能做什么"变成了一张可以讨论、可以评审、可以审计的图纸。当业务部门说"这个岗位好像权限不够"或者"这个人不该看到这个数据"时,可以直接在矩阵上定位到对应的格子里讨论。

在系统实现层面,RBAC 的核心数据表通常包含三张主表和两张关联表:

表名 作用 核心字段
用户表 存储系统账号 user_id, username, dept_id, status
角色表 存储角色定义 role_id, role_name, role_type
权限表 存储权限定义 perm_id, perm_code, perm_name, resource_type
用户角色关联表 用户与角色的多对多关系 user_id, role_id
角色权限关联表 角色与权限的多对多关系 role_id, perm_id

如果还需要支持数据权限,通常会在"角色权限关联表"中增加一个 data_scope 字段,取值可以是"本人"、"本部门"、"本部门及下属"、"全部"等。

常见权限场景的拆解

理论讲完,我们来看三个在真实 ERP 系统中最常见的权限场景。

场景一:跨部门数据隔离

销售部门 A 和销售部门 B 各自负责不同的客户群体。系统需要保证:A 部门的销售代表在查看客户列表时,只能看到 A 部门的客户,看不到 B 部门的。

实现思路:在客户主数据上增加"归属部门"字段。当销售代表查询客户列表时,系统自动在查询条件中追加 WHERE dept_id = 当前用户所属部门。这不是前端隐藏,而是后端查询层面的数据过滤。

如果销售经理需要查看多个部门的数据,可以给他配置"数据权限范围 = 本部门及下属",系统在查询时自动展开其管辖的所有部门 ID。

这个机制的关键在于:数据权限不是前端过滤,而是后端查询条件的自动注入。即使销售代表知道了其他部门客户的编码,直接通过接口查询,系统也会因为他的数据权限范围限制而返回空结果。

场景二:审批权限分级

在第 9 篇中,我们讨论了信用检查失败后的审批流程。不同金额的订单,需要不同级别的人审批。例如:1 万元以下由销售主管审批,1 万到 10 万元由销售经理审批,超过 10 万元需要财务总监审批。

实现思路:这不是传统的"角色绑定权限",而是"条件驱动权限"。系统在判断当前用户是否有审批资格时,除了检查角色,还要检查订单金额是否落在该角色的审批区间内。这种规则通常通过"审批矩阵"或"审批策略表"来实现,而不是写死在代码里。

function getApprovers(orderAmount) {
  if (orderAmount < 10000) return ["销售主管"];
  if (orderAmount < 100000) return ["销售经理"];
  return ["财务总监"];
}

场景三:临时授权与代理

销售经理要休假一周,需要把手头的审批权限临时转给另一位同事。如果系统不支持临时授权,就只能把账号密码告诉对方,或者把对方的角色临时改成"销售经理"——两者都有明显的安全和审计风险。

实现思路:引入"代理授权"机制。销售经理可以在系统中指定一位代理人,设定代理的起止时间。在代理期间,代理人获得被代理角色的部分权限(通常可以限定范围,如"只代理审批权限,不代理查看报表权限")。代理关系到期后自动失效,不需要人工回收。

这在数据模型上通常表现为一张"代理关系表",记录 delegator_id、delegatee_id、start_time、end_time、scope 等字段。系统在校验权限时,先查用户自身的角色,再查其是否处于有效的代理关系中,两者取并集。

代理机制还有一个重要的审计要求:所有通过代理权限执行的操作,在系统日志中必须同时记录"操作人"和"代理人"两个字段。这样事后审计时,可以清楚地区分哪些操作是员工本人执行的,哪些是通过代理权限执行的。

总结:权限是内控的数字化表达

回到文章开头的那个场景:实习生点击了"审批通过",系统应该拒绝他。

这个拒绝的背后,不是一句简单的"你没权限",而是一整套权限设计在起作用:RBAC 模型告诉他没有这个角色的功能权限,SoD 规则告诉他即便有审批角色也不能审批自己的单据,数据权限告诉他这张单据不在他的可见范围内。

权限设计的本质,是把企业的管理制度和风险控制要求,翻译成系统里的校验规则。它不像状态机那样有明显的流转路径,也不像单据那样有具体的业务形态,但它在每一个按钮、每一次查询、每一次状态变更的背后默默工作。

我的判断是:一套好的权限系统,应该像空气一样存在——用户感受不到它的复杂,但缺了它,整个系统就会立刻陷入混乱。

下一篇,我们继续深入产品设计层面,聊聊如何把审批制度产品化:审批流设计。状态机定义了流转规则,权限定义了谁能参与,审批流则把"谁审批、按什么条件审批、审批不通过怎么办"这些业务制度变成可配置的系统能力。


往期回顾


关于十三Tech

资深服务端研发工程师、架构师、AI 编程实践者。
希望能和大家一起写出更优雅的代码。

Share

如果正好有需要,可以支持一下

这里放了一个阿里云活动入口。你本来就打算了解这类服务的话,可以从这里进去;如果有推广收益,我会优先用来覆盖服务器、域名和维护成本。

查看活动入口

相关文章

ERP2026年3月17日
series / 进销存与业财一体化

业财通识26:功能模块划分——如何把业务流程变成系统菜单

前面的 25 篇文章,我们一起走完了企业最核心的三条业务主线。 花钱的线:从采购申请到采购订单,从收货入库到发票校验,最后付款给供应商。赚钱的线:从销售机会到报价,从销售订单到发货开票,最后把应收账款收回来。管货的线:从入库出库到盘点调拨,…

ERP2026年3月12日
series / 进销存与业财一体化

业财通识25:业财对账——打通业务与财务的最后一公里

前面三篇文章,我们分别聊了应收对账、应付对账和银行对账。 这三类对账有一个共同点:对账的双方,至少有一方是外部机构——客户、供应商或银行。外部对账虽然有难度,但边界清晰、规则明确,对不上就是哪边出了问题,总能找到责任方。 但企业里还有一类对…

ERP2026年3月7日
series / 进销存与业财一体化

业财通识24:银行对账——银行流水与账本的逐笔核对

在前两篇文章中,我们分别聊了应收对账和应付对账:与客户核对该收的钱是否到账,与供应商核对该付的钱是否准确。 但这里有一个容易被忽略的环节。与客户和供应商对完账后,企业的财务工作并没有结束。还有一笔最重要的账要对——与银行对账。 原因很简单。…