ERP 系统是如何工作的:模块、数据流与底层总账
跟踪一笔交易在 ERP 各个模块中的流转,剖析真实的会计分录,以及为什么总账是其他一切报表赖以读取的根本记录。
ERP 系统的核心工作原理是:将每一项业务事件仅记录一次,并存入单一数据库中,允许各个模块共同读取和写入该共享记录。无论是仓库、销售团队还是会计人员查看一条已发货的订单,看到的都是完全相同的记录。维护整个系统合规性的核心模块是总账(general ledger):所有具有货币价值的事件最终都会在总账中生成一笔借贷平衡的会计分录(journal entry),而所有的报表均源自这些分录。
本文将追踪一笔销售交易在各个模块中的流转过程,展示它所产生的会计分录,并解释为什么集成 ERP 的本质在于处理事件与总账,而不是屏幕界面。
共享数据库
在 ERP 出现之前,各个部门运行着各自独立的程序。库存保存在仓储系统中,发票在计费系统中,账目则在财务软件中。月末的大量时间都耗费在核对这三者之上。什么是 ERP 软件一文对这一品类进行了阐述,而让它得以运转的底层机制则是单一数据模型,其中:
- **业务伙伴(partner)**记录被销售、采购和总账共同使用,
- **物料(item)**记录承载了库存数量、先进先出(FIFO)成本层、售价以及收入科目,
- 单据(document)(订单、发票、红字发票、采购单、生产单)关联业务伙伴和物料,并生成且仅生成一组平衡的会计分录,
- 会计分录关联生成它的单据,因此报表中的每一个数字都可以追溯到源头。
当记录实现共享时,模块之间便无需再进行对账。唯一需要核对的是外部世界:银行对账单和税务申报。
模块全景图
| 模块 | 写入内容 | 读取内容 |
|---|---|---|
| 销售 | 订单、发票、红字发票 | 业务伙伴、物料、价格、增值税规则 |
| 采购 | 采购订单、供应商发票 | 业务伙伴、物料、费用科目 |
| 库存 | 库存变动、FIFO 成本层、盘点 | 物料、仓库 |
| Production | 生产订单、领料消耗、完工入库 | 物料清单、库存 |
| 银行 | 导入的交易、付款匹配 | 未清发票、银行账户 |
| 薪酬 | 薪酬计算、社保公积金、申报 | 员工、合同、税率表 |
| 固定资产 | 资产台账、折旧运行 | 资产组、使用寿命 |
| 税务 | 增值税申报、欧盟销售汇总表、Intrastat、电子发票 | 已开具和已登记的单据 |
| 总账 | 会计分录、期间锁定 | 上述所有内容 |
| 报表 | 无 | 总账与明细账 |
除了最后两行之外,其余每一行操作的终点都是向总账写入数据。正是这一设计原则,将 ERP 与一组零散连接的工具区分开来。
完整的数据流演练
以下示例使用了 Nordlet 的端到端接口以及其自带的立陶宛会计科目表默认科目。任何 ERP 执行的步骤都是相同的;只是科目代码和端到端接口名称有所差异。
1. 订单
电商前台通过 POST /v1/ecommerce/orders/create 提交一个订单:包含一个业务伙伴、带有物料和数量的行项目、以及收货国家。此时尚无任何内容过账到总账。订单只是一种承诺,而非交易。
2. 库存预留
orders/reserve 预留库存数量,以防第二笔订单售出相同的库存单位。此时仍然没有会计分录:预留的库存仍然属于公司。
3. 履约发货
orders/fulfill 记录货物离开仓库。在一次数据库事务中会同时发生两件事:
- 按 FIFO 成本消耗库存:优先使用每种物料最早的成本层,这正是 FIFO 库存计价(FIFO inventory valuation)词条中所描述的方法。
- 这些库存单位的成本作为一笔会计分录过账,借记主营业务成本,贷记库存。
| 科目 | 借方 | 贷方 |
|---|---|---|
| 6000 主营业务成本 | 600.00 | |
| 2040 库存 | 600.00 |
履约发货同时会为该订单创建一个草稿发票(draft invoice),并根据收货国家利用 EU VAT 引擎(EU VAT engine)确定适用的增值税处理方式。
4. 开具发票
开具发票是针对按时点确认收入的销售确认收入的时刻。单据获得连续的编号,并过账一笔平衡的分录:价税合计计入应收款,增值税计入销项税额,不含税金额计入收入。
对于一笔 1,000 欧元(不含税)且适用 21% 立陶宛增值税的销售:
| 科目 | 借方 | 贷方 |
|---|---|---|
| 2410 应收账款 | 1,210.00 | |
| 5000 商品销售收入 | 1,000.00 | |
| 4492 应交增值税 - 销项税额 | 210.00 |
如果行项目采用的确认方法不是按时点确认,不含税金额则转入递延收入(deferred income),并由收入确认引擎(revenue recognition engine)按计划进行结转。应收款和增值税的过账保持不变。
此后,该发票可以导出为 PDF、Peppol BIS 3.0 电子发票,或公司所在国家/地区要求的国家级电子发票格式。
5. 银行对账
两周后,1,210 欧元到账。资金变动通过 PSD2 银行数据流(bank feed)或 camt.053 对账单导入系统。银行模块通过金额、付款备注、交易对手名称和 IBAN 对候选发票进行匹配评分,然后由 bank/transactions/match 过账结算:
| 科目 | 借方 | 贷方 |
|---|---|---|
| 2710 银行存款 | 1,210.00 | |
| 2410 应收账款 | 1,210.00 |
发票状态变更为已支付,并触发 sale_invoice.paid Webhook 事件。付款对账(payment reconciliation)词条详细解释了匹配逻辑。若为外币付款,系统会在同一步骤中将实现的汇兑差额计入汇兑损益科目。
6. 增值税申报
月末无需重新录入任何数据。增值税申报表根据已开具和已登记的单据自动计算:国内申报表(立陶宛的 FR0600)通过 declarations/eu/vat-return/compute 计算,欧盟境内一站式服务(OSS)下的跨境消费销售通过 declarations/eu/oss/compute 计算。上述销售由于其增值税方案为 domestic(国内),因此会落入国内申报表的标准税率栏中。
7. 期间锁定
申报完成后,ledger/periods/lock 将关闭该月份。此后,任何日期落入该期间的过账(包括迟到的银行对账或重试的 API 调用)都将被 HTTP 409 拒绝。期间锁定还会触发该期间截止日前所有应确认的递延收入结转,从而防止出现已实现的收入仍滞留在负债科目中却关闭月份的情况。
为什么总账是记录系统
上述每个模块都维护一个明细账(subledger):按客户划分的应收账款、按供应商划分的应付账款、按物料划分的库存、按资产划分的固定资产。总账(general ledger)则保存了这些明细账必须与之核对一致的总额。明细账与总账(Subledger vs general ledger)探讨了二者的关系;其实际意义在于,从总账生成的报表和从明细账生成的报表必须得出相同的数字,而 ERP 正是通过从相同的会计分录(journal entry)生成这两者来保证这一点的。
以下三个特性使得总账足以作为值得信赖的记录系统:
- 强制借贷平衡。 在 Nordlet 中,数据库会拒绝任何借方不等于贷方的分录。模块中的软件缺陷绝不可能产生失衡的过账。
- 历史记录只能追加。 已过账的分录永不修改。如果出现错误,必须通过冲销(reversal)和一笔新分录来纠正,且两者均关联至原始单据。不可篡改总账(immutable ledger)词条解释了为什么审计人员坚持这一要求。
- 期间锁定。 已锁定的月份无法更改,因此你所申报的数字就是保留在系统中的数字。
集成:事件驱动与幂等性,而非夜间批处理
较旧的 ERP 集成依赖于夜间交换的文件:导出订单、导入订单、在早晨检查错误日志。现代 ERP 则提供 API,集成演变为遵循以下两个规范。
事件输出。 当发生变更时,ERP 会主动通知你。Nordlet 将每个 Webhook 事件写入与变更在同一个数据库事务中的发件箱(outbox),因此被回滚的单据绝不会发出事件,也不会丢失任何事件。投递过程使用 HMAC-SHA256 签名,并在遇到故障时采用指数退避算法进行重试。你的系统只需响应 sale_invoice.paid,而不必每五分钟询问一次是否有款项到账。
安全重试。 网络可能会发生故障,重试的请求绝不能创建第二张发票。Nordlet 在每个修改数据的调用中都接受一个 Idempotency-Key(幂等键)请求头:带有相同有效负载(payload)的相同键会返回已存储的响应,带有不同有效负载的相同键会被拒绝,并且幂等键在 24 小时后过期。幂等性(idempotency)词条和API 规范(API conventions)描述了其精确行为。
由于每个操作都是一个 POST /v1/{module}/{resource}/{action} 调用,且应用程序使用的是相同的端到端接口,因此集成可以完整运行上述整个流程,而无需任何人打开界面。如何将云会计 API 集成到 SaaS 平台中(How to integrate a cloud accounting API into a SaaS platform)一文详细演练了这种构建过程。
报表读取的是什么
报表本身并不存储数字。它们是对特定时间点的总账和明细账进行的查询:
- 试算平衡表(trial balance)、资产负债表、利润表、现金流量表按科目和期间读取会计分录。
- **账龄分析(debt aging)**按到期日读取未清的应收账款和应付账款。
- 库存余额与变动读取库存明细账及其 FIFO 成本层。
- 增值税汇总表读取已开具和已登记单据的增值税明细行。
- 成本中心报表读取每行分录上的成本中心维度。
在 Nordlet 中,报表会在后台生成为 XLSX、PDF 或 JSON 格式,当文件准备就绪时会触发 Webhook 通知。集团合并数据来源于合并报表(consolidation),它结合了多家公司的总账,并考虑了持股权重和货币换算。
实践中容易出现问题的地方
大多数 ERP 的问题都是共享记录原则的失败,而不是软件本身的缺陷。常见的情况包括:
- 为 ERP“无法做到”的事情设立平行电子表格(parallel spreadsheet),最终导致其数据与账目不符。
- 使用手工会计分录来修正模块输出,而不是去修正单据,这破坏了从报表追溯到源头的链条。
- 写入余额而不是单据的集成,导致总账中包含无人能解释的总数。
- 开放的会计期间连续数月未锁定,导致上一季度的申报数据发生漂移。
为什么 ERP 实施会失败(Why ERP implementations fail)探讨了这些习惯在实施过程中是如何形成的,而现实的 ERP 实施时间表(realistic ERP implementation timelines)则展示了它们通常出现在项目的哪个阶段。
FAQ
ERP 系统的核心是什么?
总账以及围绕总账构建的共享数据模型。每个模块都会写入生成平衡会计分录的单据,并且每个报表都源自这些分录。
数据是如何在 ERP 中流动的?
业务事件作为单据(订单、发票、库存变动、薪酬计算)被记录一次。该单据在总账中生成会计分录,并在相关的明细账中生成变动。报表和税表正是根据这些记录计算得出的。模块之间不会复制任何数据。
模块与集成有什么区别?
模块与系统的其余部分写入相同的数据库和相同的总账。集成则是调用 ERP 的 API 的外部程序。对于 API 优先的 ERP 来说,这种区别很小,因为集成使用的是与模块相同的端到端接口。
ERP 会自动过账会计分录吗?
是的。开具发票、履约发货、银行对账、运行薪酬或计提资产折旧,每项操作都会各自生成会计分录,而无需人工去构造借方和贷方。所使用的科目来自可按公司进行更改的过账规则。
为什么 ERP 要锁定会计期间?
为了确保针对某一期间报告或申报的数字在此之后无法更改。锁定的期间会拒绝新的过账,从而保护审计线索,并迫使更正进入当前期间,以便于被发现和审视。