Repository navigation
分拆法人(公司)维度:新建 sys_company,从 sys_business_unit 中剥离 kind='company'(并移除 cost_center) #2483
Description
背景 / 问题
当前 sys_business_unit(下称 BU)用一棵递归树 + 一个 kind 枚举(company / division / department / office / cost_center,见 packages/platform-objects/src/identity/sys-business-unit.object.ts)同时承载了语义完全不同的概念:
- 公司 / 法人实体(
kind='company')—— 财务/法定边界(报税、并表、按法人记账、跨公司交易); - 成本中心(
kind='cost_center')—— 管理会计/费用分摊单元; - 职能组织单元(
division/department/office)—— 汇报/分区结构。
把它们压进同一棵树、只靠 kind 区分,在集团场景下会"混乱":一个集团公司既有子公司(法人),又有集团本部的部门(职能单元),它们被迫成为同一层的兄弟节点。kind 是 display hint("does not change graph semantics"),不足以可靠表达"这是法律主体 vs 这是部门",也无处存放公司专有属性(税号、币种、财年等)。
判据:"语义不同才拆,只是粒度不同就留作 kind"。
division/department/office是同一种"组织单元"的不同粒度 → 留作 kind;company与cost_center语义不同 → 不该留在 BU 树里。
命名
公司对象技术名 sys_company,label: 'Company' / '公司'。"Company" 是主流 ERP 的法人术语(Workday Financials 即用 Company;Dynamics/Oracle 用 Legal entity 表达同一概念)。
⚠️ sys_company与 BU 现有的kind='company'字面撞名,因此必须与"移除 BU 的kind='company'"在同一波迁移落地,不可长期并存,否则数据模型里会出现两个"company"概念。
为什么不能复用 organization_id
sys_organization 是多租户隔离边界(managedBy: 'better-auth'、protection: { lock: 'full' },见 sys-organization.object.ts):
- 语义相反:org 的唯一职责是硬隔离(RLS 按 active org 挡行)。把子公司建成 org,会让集团内跨子公司可见/并表/跨公司选人全部失效——而这正是集团场景需要的。
- schema 锁死:无法在其上添加公司属性(税号/币种/财年/母公司/合并方式)。
- better-auth 语义渗透:active-org 切换、org 成员/邀请、
require_mfa等会被一个"公司"莫名继承。 - 单向门:单租户下 org 是干净的预留隔离轴;烧成"公司"后,将来真要多租户就没有隔离轴了。
为什么不把 BU 改名为"部门表"
department 只是 BU 众多 kind 之一;用一个 kind 命名整棵树会重犯"一名多义"。剥离公司/成本中心后 BU 仍承载 division/department/office(多粒度组织单元),且 membership / 共享(recipient_type='business_unit')/ 审批(bu: 前缀)/ 选人器都挂在它身上。BU 不改名,它仍是"组织结构 + 数据分区 + 安全"的那棵树。
方案
已定决策
- 新建
sys_company(技术名),label: 'Company'/'公司'——租户内的公司/法人维度,additive、能力门控。 - BU 移除
kind='company',改挂company_id外键(与移除 company 类型在同一波迁移落地,避免撞名并存)。 - BU 移除
kind='cost_center'—— 成本中心是会计/应用域概念,由业务软件开发者按需自建(自定义对象/字段),平台 BU 不预置。 - BU
kind最终保留division / department / office—— 全是同语义、不同粒度的组织单元。 - 不重载
organization_id,保持休眠预留。 - 公司范围的记录 opt-in
company_id:自动盖戳、去规范化冗余在记录上(同sys_user.primary_business_unit_id的惯例,见sys-user.object.ts:500),性质是软归属标签,非硬隔离 —— 集团内仍可跨公司可见。主数据(客户/产品/供应商)与系统对象不带。 sys_business_unit不改名。- 能力门控:不需要多公司的客户不启用
sys_company,零负担(符合 ADR-0057 的 evidence-gated 哲学)。
sys_company 字段集(草案)
| 字段 | 说明 |
|---|---|
name / label |
公司名称 |
code |
短码(唯一) |
tax_id / reg_no |
税号 / 工商注册号 |
currency |
本位币 |
fiscal_calendar |
财年/会计期间 |
parent_company_id |
母公司(公司股权/并表层级,与 BU 组织树正交) |
ownership_pct |
持股比例(并表用,可选) |
consolidation_method |
合并方式(全资/权益法/比例合并等) |
country / registered_address |
注册地 |
active / effective_from / effective_to |
生命周期 |
公司层级:
parent_company_id构成股权/并表树(集团→子公司→孙公司),默认单父(控股方);合资/多股东(DAG)为矩阵边界,留作 P2 junction(对称于 BU 的"主属树 + 矩阵 junction")。
BU 侧改动
kind枚举:移除company与cost_center,保留division / department / office(与下方迁移同批完成)。- 新增
company_id: Field.lookup('sys_company'),每个 BU 节点归属一个公司。 org_chart树视图渲染时,"公司"层来自sys_company的虚拟节点(按company_id分组 + join),不再是 BU 行。
共享层改动
- 新增
recipient_type='company'(或等价表达company_id IN (...)),替代原先"共享给kind='company'的 BU 子树"。 BusinessUnitGraphService维持不变(仍按 BU 子树展开);公司层的展开走company_id。
迁移
- 每个现存
kind='company'的 BU → 建一条sys_company行;给其后代 BU 盖company_id;删除该 company 节点。 - 现存
kind='cost_center'的 BU(如有)→ 评估去向:平台不再预置成本中心,需由对应业务方迁移为自建对象/字段(或在数据为空时直接移除枚举值)。 kind枚举移除company与cost_center(与步骤 1 同批)。- 重定向原先指向 company-BU 的共享/审批寻址到对应
sys_company。
待定(非阻塞)
- 哪些业务对象 opt-in
company_id(交易/财务类要,主数据/系统对象不要)。 - 自动盖戳来源:用户主公司(user→BU→company)/ 父记录继承 / "当前公司"上下文切换器,机制对称于 OrgScopingPlugin。
范围 / 非目标
- additive,不改
sys_business_unit结构(仅加一个 FK + 清理 kind 枚举),旧数据company_id可为 null; - 成本中心不进平台——交由业务软件自建;
- 不引入全多维 ERP 组织(position hierarchy / matrix cross-BU 等仍按 ADR-0057 的 P4 evidence-gated 路线);
- 与对应的 ObjectUI 选人器需求(选人组件升级:能力探测的分层 PeoplePicker(搜索优先默认 + 组织树/人群渐进增强) objectui#2112)解耦:选人器保持 dimension/root-agnostic,不依赖公司维度是否启用。
关联
- ADR:
docs/adr/0057-erp-authorization-core-business-units-and-scope-depth.md(ERP 授权核心 / BU 分区) - 相关对象:
sys-business-unit.object.ts、sys-user.object.ts、sys-business-unit-member.object.ts、sys-organization.object.ts - 下游需求:选人组件升级:能力探测的分层 PeoplePicker(搜索优先默认 + 组织树/人群渐进增强) objectui#2112(分层选人器)
Activity
[-]分拆法人维度:新建 sys_legal_entity,从 sys_business_unit 中剥离 kind='company'[/-][+]分拆法人(公司)维度:新建 sys_company,从 sys_business_unit 中剥离 kind='company'[/+][-]分拆法人(公司)维度:新建 sys_company,从 sys_business_unit 中剥离 kind='company'[/-][+]分拆法人(公司)维度:新建 sys_company,从 sys_business_unit 中剥离 kind='company'(并移除 cost_center)[/+]os-zhuang commentedon Jun 30, 2026
决定:暂缓(defer)—— 改走"租户-per-子公司"
经过对集团架构的权衡,当前不构建租户内的公司维度,改为:
理由:单租户 + 公司维度需要在每个 feature里重复实现公司隔离/scope(
company_id横切,开发成本高);而租户-per-子公司把隔离白拿,目前开发成本大幅降低。已知取舍(有意识接受):租户-per-子公司"放开难"——将来若需要跨公司运营/身份/真合并,retrofit 成本高。当前判断是子公司基本各自独立,这个赌注成立。
本 issue 设计仍然有效,留作将来真正出现复杂集团需求(跨公司交易、共享主数据、一人跨多子公司、真合并)时的起点 / 蓝图,届时重开即可。
关联:下游选人器 objectstack-ai/objectui#2112 据此简化。
Generated by Claude Code