ERPseries / 进销存与业财一体化

业财通识33:主数据管理——一份数据、一个真相

上一篇文章我们聊了事件驱动架构。它的核心思路很简单:模块之间不再直接调用,而是通过事件来通信。订单创建了,发个事件;库存扣了,发个事件;财务记账了,也发个事件。各模块各干各的,互不阻塞。 这个思路在解耦上很有效。但有一类问题,事件驱动本身解…

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

导言:事件驱动之后,数据对不上怎么办

上一篇文章我们聊了事件驱动架构。它的核心思路很简单:模块之间不再直接调用,而是通过事件来通信。订单创建了,发个事件;库存扣了,发个事件;财务记账了,也发个事件。各模块各干各的,互不阻塞。

这个思路在解耦上很有效。但有一类问题,事件驱动本身解决不了。

假设销售系统里有一个客户,叫"华东智联科技有限公司"。财务系统收到事件后,在自己的库里找这个客户,却发现记成了"华东智联公司"。客服系统里又有一条"华东智联上海分公司"。

三个名字看起来都像同一家公司,但系统里的编码各不相同。

这时候问题来了:信用额度该查哪个客户?发票该开给哪个抬头?应收账款该挂在哪个编号下面?

事件驱动解决了"消息怎么传",但它不能保证"传过去的数据是对的"。

这就是我们今天要聊的主题:主数据管理(MDM, Master Data Management)。我的结论是:如果各模块对"客户是谁""商品是什么"这些基础认知都不一致,再先进的架构也只是把错误传得更快而已。

主数据 vs 交易数据:两种完全不同的数据

在谈 MDM 之前,我们必须先区分两类数据。这个区分是理解一切的基础。

业务定义

  • 主数据(Master Data):描述业务实体的静态数据,变化频率低,被多个模块共享。例如客户、供应商、商品、会计科目、组织架构。
  • 交易数据(Transactional Data):描述业务事件的动态数据,每天都在产生和变化。例如采购订单、销售订单、入库单、发票、付款记录。

你可以把主数据理解成企业的"员工花名册",把交易数据理解成"每天的考勤打卡记录"。

花名册上的人不常变,但每天的打卡记录都在新增。如果花名册上"张三"的工号不一致,那么不管打卡系统多先进,张三的考勤统计都会出错。

下面这张表,帮你快速建立两者的对比认知:

维度 主数据 交易数据
变化频率 慢,按月或按季度更新 快,每天产生大量新记录
数据量 少,通常几千到几万条 多,可达百万甚至上亿条
共享范围 跨模块共享,全局唯一 通常归属单一模块或流程
错误代价 一条错误影响所有关联流程 一条错误通常只影响单笔业务

这个对比说明一件事:主数据虽然量小,但牵一发而动全身。 一个客户编码错误,可能导致订单、信用、发票、收款全链路的混乱。

ERP 中的五大主数据

在 ERP 系统里,有五类主数据是最核心、最基础的。它们几乎出现在每一个业务流程中。

1. 客户主数据

业务定义:客户主数据是对"企业在跟谁做生意"的统一描述,包含客户的身份信息、属性信息和关系信息。

我们在第 12 篇文章里已经详细讲过 CRM 作为客户主数据底座的三层模型——身份层、属性层、关系层。那篇文章的核心结论是:CRM 不是销售工具,而是 O2C 的数据地基。 如果对这个三层模型感兴趣,可以回看第 12 篇。

客户主数据的核心字段包括:客户编码、客户名称、统一社会信用代码、客户类型、信用额度、付款条件、开票信息、联系人等。

2. 供应商主数据

业务定义:供应商主数据是对"企业向谁采购"的统一描述,是 P2P 流程的起点。

在采购流程中,采购订单必须明确指向一个供应商。如果同一个供应商在系统里有多个编号,采购就无法准确统计该供应商的历史交易、履约质量、应付账款余额。

核心字段包括:供应商编码、供应商名称、统一社会信用代码、供应商分类、付款银行信息、结算币种、账期、资质有效期等。

3. 商品主数据

业务定义:商品主数据是对"企业在买卖什么"的统一描述,是库存管理和成本核算的基础。

第 5 篇文章里,我们详细拆解了 SPU 和 SKU 的概念。SPU 是"产品族",SKU 是"具体可管理库存的商品"。所有的采购、入库、出库、盘点、销售,最终都要落实到 SKU 级别。

商品主数据的核心字段包括:SKU 编码、SPU 编码、商品名称、规格属性、计量单位、默认成本价、默认售价、税码、存储条件等。

4. 会计科目主数据

业务定义:会计科目主数据是对"企业用什么口径记账"的统一描述,是财务核算的语言体系。

每一笔业务发生之后,最终都要翻译成会计分录。而会计分录里的"借什么、贷什么",完全取决于科目主数据的定义。如果不同模块对"库存商品"这个科目的编码理解不一致,总账和业务系统的对账就会永远对不平。

核心字段包括:科目编码、科目名称、科目类别(资产 / 负债 / 权益 / 收入 / 成本)、余额方向、层级关系、是否启用辅助核算等。

5. 组织架构主数据

业务定义:组织架构主数据是对"企业由谁来做"的统一描述,是权限隔离和数据归属的依据。

一张采购订单属于哪个采购部,一笔销售回款归属于哪个事业部,一份报表汇总到哪个法人主体,这些都依赖组织架构主数据。

核心字段包括:组织编码、组织名称、组织类型(公司 / 部门 / 团队)、上级组织、法人标识、生效日期、状态等。

这五类主数据,像五根柱子一样撑起了 ERP 的全部业务流程。任何一根柱子歪了,上面的流程都会跟着倾斜。

为了帮你快速建立全貌认知,下面这张表汇总了五大主数据的关键信息:

主数据类型 归属模块 核心字段 消费模块
客户 CRM 编码、名称、信用额度、付款条件 销售、财务、客服
供应商 SRM / 采购 编码、名称、银行信息、账期 采购、财务、库存
商品 商品中心 SKU 编码、规格、成本价、售价 采购、库存、销售、财务
会计科目 财务总账 科目编码、名称、类别、方向 财务、所有业务模块
组织架构 人力 / 组织管理 组织编码、类型、上级组织 全模块(权限与归属)

数据治理:谁来创建、谁来维护、谁来审批

主数据的问题,从来不只是"技术问题",更是"治理问题"。

技术可以保证数据同步的时效性,但无法回答"谁有权修改客户信用额度""供应商银行账户变了要经过谁审批"这类问题。

一个常见的主数据生命周期,通常包含以下阶段:

graph LR
    A[申请创建] --> B[数据初审]
    B --> C[标准审核]
    C --> D[正式发布]
    D --> E[日常维护]
    E --> F[变更审批]
    F --> G[归档停用]

每个阶段的职责划分,决定了主数据的质量底线。

申请创建:通常由业务部门发起。销售要新增一个大客户,采购要引入一个新供应商,商品部要上架一款新品。

数据初审:由主数据管理员或数据质量团队执行,检查必填字段是否完整、编码是否符合规范、是否与已有数据重复。

标准审核:对于敏感字段或高价值主数据,需要跨部门审核。例如新客户信用额度的设定,通常需要财务部门参与。

正式发布:审核通过后,主数据进入"生效"状态,各下游系统才能读取和使用。

日常维护:不同字段由不同部门维护。销售维护联系人信息,财务维护开票和付款信息,这是最常见的分工模式。

变更审批:关键字段的修改需要重新审批。例如客户信用额度的上调、供应商银行账户的变更,通常不能由业务人员直接修改。

归档停用:当主数据不再使用时,不应直接删除,而是标记为"停用"。因为历史交易数据可能还在引用它,删除会导致数据断层。

这个流程看起来有些繁琐,但它回答了一个核心问题:主数据不是谁都能改的花名册,而是一份有审批、有记录、有责任的正式档案。

主数据同步的三种模式

当主数据在一个系统里创建之后,其他系统怎么知道?这就是主数据同步要解决的问题。

在实践中,有三种最常见的同步模式。

模式一:主从复制

一个系统被明确指定为"主数据源",其他系统作为"从节点",定期或实时从主节点同步数据。

例如:CRM 是客户主数据的唯一源头,销售系统、财务系统、客服系统都定期从 CRM 同步客户信息。各从节点只读,不写。

这种模式最简单,一致性也最好。但它要求企业有明确的"谁做主"的决策。

模式二:发布订阅

主数据发生变更时,主动发布一个事件,订阅了该事件的系统自行消费。

例如:客户信用额度被修改后,CRM 发布一个"客户信用已变更"的事件。财务系统、风控系统、销售系统都订阅了这个事件,各自更新自己的本地缓存。

这种模式解耦性好,适合微服务架构。但它引入了"最终一致性"的问题——订阅方可能不是立即收到变更,中间有一个短暂的时间窗口数据不一致。

模式三:API 网关

不强制各系统同步数据到本地,而是提供一个统一的主数据查询服务。各业务系统在需要时,实时调用 API 获取最新数据。

例如:订单系统创建订单时,不依赖本地缓存的客户信息,而是实时调用主数据服务,获取客户的最新信用额度和付款条件。

这种模式的数据一致性最好,但增加了实时调用的依赖和延迟。如果主数据服务不可用,业务流程可能会卡住。

三种模式各有优劣,适用的场景也不同。下面这张决策表可以帮助你快速判断:

场景 推荐模式 优点 缺点
数据变化少,一致性要求高 主从复制 简单稳定,易排查 耦合度较高
微服务架构,模块自治 发布订阅 解耦好,扩展性强 有最终一致性窗口
数据变化频繁,实时性要求高 API 网关 数据最新,无同步延迟 强依赖中心服务

在实际工程中,很多企业会混合使用。例如客户主数据用主从复制保证稳定,库存数量用发布订阅实时推送,信用额度检查用 API 网关实时查询。

数据质量:重复、不一致与过时

即使有了同步机制,主数据仍然会出现三类典型的质量问题。

问题一:重复数据

同一个客户,通过官网注册了一次,通过销售手工录入了一次,通过经销商批量导入又来了一次。系统里出现了三条"看起来很像"的记录。

通俗理解:就像公司花名册里,同一个员工因为入职时用了本名、后来改了英文名,结果被录入了两次。

问题二:不一致数据

销售系统里的客户地址已经更新了,但财务系统里的开票地址还是旧的。客服系统里的联系人电话是最新的,但订单系统里还在用旧电话。

通俗理解:花名册上的手机号更新了,但考勤系统、报销系统、门禁系统里的手机号还是旧的。

问题三:过时数据

供应商的银行账户三年前变更过,但系统里的付款信息没有同步更新。结果财务每次付款都打到旧账户,退回后再人工处理。

通俗理解:花名册上某个员工已经离职半年了,但因为没有人去标记"离职",他每个月还能收到系统自动发的内部邮件。

针对这三类问题,系统层面通常需要两个基础能力:重复检测合并逻辑

下面是一段简化版的重复检测与合并逻辑:

function detectDuplicateCustomers(candidates) {
  const groups = [];
  for (const c of candidates) {
    const exact = groups.find(g => g.taxId && g.taxId === c.taxId);
    const fuzzy = groups.find(g => 
      nameSimilarity(g.name, c.name) > 0.85 &&
      (g.phone === c.phone || g.email === c.email)
    );
    const match = exact || fuzzy;
    if (match) {
      match.records.push(c);
    } else {
      groups.push({ records: [c] });
    }
  }
  return groups.filter(g => g.records.length > 1);
}

function mergeCustomerGroup(group) {
  const base = group.records.find(r => r.status === 'approved')
            || group.records.sort((a, b) => b.updatedAt - a.updatedAt)[0];
  const merged = { ...base, sourceIds: group.records.map(r => r.id) };
  for (const r of group.records) {
    if (r.id !== merged.id) markAsMerged(r.id, merged.id);
  }
  return merged;
}

这段代码的核心思想是:

  1. 先检测:通过精确匹配(如统一社会信用代码)和模糊匹配(名称相似度 + 辅助字段)找出可能的重复。
  2. 再合并:选定一条作为主记录,其他记录标记为"已合并"并指向主记录,保证历史交易不被破坏。

为什么必须是"一份数据、一个真相"

有些工程师会问:为什么不能各模块各自维护一份客户数据?销售系统管销售的客户视图,财务系统管财务的客户视图,各自独立,互不干扰。

这个想法听起来很符合"微服务自治"的理念。我们在第 30 篇文章里讨论 DDD 时,也提到过一个概念:限界上下文(Bounded Context)。不同上下文中,同一个名词的含义可以不同。例如销售上下文中的"商品"关注的是价格和库存,财务上下文中的"商品"关注的是成本科目和计价方式。

但这里有一个关键边界:限界上下文允许视角不同,但不允许身份不同。

销售上下文可以只关心客户等级和联系人,财务上下文可以只关心开票信息和信用额度。但它们必须承认"这是同一个客户"。如果销售上下文里的"华东智联"和财务上下文里的"华东智联"被系统识别为两个不同的实体,那么所有跨模块的协作都会断裂。

"一份数据、一个真相"(Single Source of Truth)不是要求所有系统用同一张表,而是要求所有系统对"谁是华东智联"这个问题给出同一个答案。

具体的实现方式可以是:

  • 各系统保留自己的本地数据,但客户的"唯一身份标识"来自同一个源头。
  • 各系统可以扩展自己关心的字段,但核心的身份字段(如客户编码、统一社会信用代码)不允许本地修改。
  • 当身份字段需要变更时,必须通过主数据服务统一处理,并同步给所有消费方。

这才是 MDM 的真正目标:不是消除差异视角,而是保证身份一致。

总结

今天我们聊了主数据管理这个看起来"不性感"但极其重要的话题。

主数据是 ERP 的地基。客户、供应商、商品、科目、组织架构,这五类数据量不大,但影响了从采购到付款、从订单到收款、从库存到财务的每一个流程。

如果主数据治理不好,事件驱动架构只会把错误传播得更快,微服务拆分只会让不一致变得更隐蔽,DDD 的限界上下文也会因为身份无法对齐而失去协作基础。

文章的最后,我想把今天的内容压缩成三个可以带走的判断:

  1. 主数据的问题,首先是治理问题,然后才是技术问题。 谁来创建、谁来维护、谁来审批,这些规则比同步机制更重要。
  2. 同步模式没有银弹。 主从复制、发布订阅、API 网关,三种模式可以共存,关键是根据数据变化频率和一致性要求做选择。
  3. "一份数据、一个真相"的核心是身份一致,不是视角统一。 各模块可以有自己的业务视图,但必须承认同一个身份。

至此,从业务流、产品模块到架构设计,这个系列已经走完了完整的一圈。

下一篇文章是系列的收尾篇。我会把前面的内容串起来,聊聊从 Coder 到 Architect,一个程序员理解业务之后,会做出什么不一样的技术判断。


往期回顾


关于十三Tech

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

Share

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

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

查看活动入口

相关文章

ERP2026年4月23日
series / 进销存与业财一体化

业财通识34:从 Coder 到 Architect——业财知识的职业价值

写完这 34 篇文章,我最大的感受是:如果回到 2025 年 9 月、刚开始接触业财系统的时候,我会对那个只会写 CRUD 的自己说一句话——先搞清楚业务在发生什么,再动手写代码。 年 9 月,我在一次需求评审会上听到产品经理说"采购申请要…

ERP2026年4月14日
series / 进销存与业财一体化

业财通识32:事件驱动架构——用事件解耦业务模块

上一篇文章我们聊了 DDD 战术设计中的聚合根、实体与值对象。其中一个核心结论还记得吗:聚合内部的数据修改,通过领域事件来驱动状态流转。订单状态从"待审核"变成"已确认",本质上就是一次领域事件的发布与消费。 但这里有一个自然而然的追问:聚…

ERP2026年4月9日
series / 进销存与业财一体化

业财通识31:DDD 战术设计——聚合根、实体与值对象

在第 30 篇中,我们用 DDD 的战略设计把业务领域拆成了一个个限界上下文。采购上下文、销售上下文、库存上下文……边界清晰,职责分明。 但划好了边界,只是解决了"哪里放什么"的问题。 如果你真正坐下来写代码,很快会遇到一个更具体的问题:一…