有一次在客户现场,领导让我把一张采购订单从收货到付款的全流程数据捞出来,用于对账。我一开始以为跑几个表就能搞定,结果发现 MARD 查出来只有数量,MBEW 里的金额跟财务那边对不上,MSEG 里的金额加起来也不等于总账凭证的金额。后来我仔细把 MM 和 FI 之间的凭证流、科目确定逻辑翻了一遍,才算把"采购验收的库存、数量、金额、会计凭证"这件事彻底串起来。
这篇文章就把这套数据链路完整讲清楚:采购验收后库存放在哪个表,物料数量以及金额在哪个表,会计凭证是怎么产生出来的,以及这些表之间到底怎么关联。适合正在做 SAP 二次开发、报表取数,或者被 MM 和 FI 对账折磨的朋友参考。先说明一下,文中涉及的字段和表名都基于标准 SAP ECC / S/4HANA,如果有定制化开发,字段可能改过,但核心思路是一样的。
1. 采购验收的数据落盘:收货那一笔到底写了哪些表
先说结论:采购收货(移动类型 101)这一下,数据不是写进一张表,而是同时更新了物料凭证表、库存数量表、采购订单历史表,并且在后台异步生成了财务凭证。要理清"库存放在哪个表",必须先分清"流水"和"存量"这两个概念。
1.1 物料凭证:MKPF + MSEG 才是收货真正的主表
MIGO 做收货后,系统会生成一张物料凭证,抬头存在 MKPF,行项目存在 MSEG。这是整个采购收货最核心的两张表。
MKPF(物料凭证抬头)主要存凭证号、凭证类型、记账日期、过账日期、创建人这些表头信息。关键字段:
- MBLNR:物料凭证编号
- MJAHR:物料凭证年度
- BLART:凭证类型,标准收货一般是 WE,冲销可能是后续的 102 等
- BUDAT:过账日期
MSEG(物料凭证行项目)才是真正存业务细节的表,也是做报表时用得最多的一张表。一张收货单如果收了 10 行物料,MSEG 就有 10 行。关键字段:
- MBLNR、MJAHR、ZEILE:凭证号、年度、行项目号,三个字段定位唯一行
- BWART:移动类型,101 表示采购订单收货,102 表示冲销收货
- MATNR:物料号
- WERKS:工厂
- LGORT:库存地点
- MENGE、MEINS:数量、基本计量单位
- DMBTR、WRBTR:金额,前者是本位币金额,后者是凭证货币金额
- SHKZG:借贷标志,S 是借方,H 是贷方
- EBELN、EBELP:采购订单号和行项目号
- KOSTL、AUFNR、KDAUF、KDPOS:成本中心、生产订单、销售订单号等科目分配信息
- LFBNR、LFBJA:前置凭证号,如果是冲销业务,这里会指向被冲销的原始凭证
有人会把 MSEG 当成库存表用,这是不对的。MSEG 是流水账,它记录的是"每一次过账动作",不是"当前库存是多少"。当前库存要看下面这张表。
1.2 库存数量更新:MARD 以及 MCHB / MSKU
普通物料在工厂和库存地点层面的当前库存,存在 MARD 表。
MARD 的主键是 MATNR + WERKS + LGORT。需要注意,MARD 里不是只有"总库存"一个字段,而是把库存按类型拆成了几个字段:
- LABST:非限制使用库存,也就是最常用的"可用库存"
- UMLME:在途库存,常见于跨工厂转储或收货到"在途"状态
- INSME:质检库存
- SPEME:冻结库存
- RETME:退货库存
如果物料启用了批次管理,那库存要按批次继续细分,存在 MCHB 表,主键是 MATNR + WERKS + LGORT + CHARG,字段拆分逻辑和 MARD 类似。如果是特殊库存,比如供应商寄售库存、销售订单库存、项目库存,那 MARD 里根本没有,要看 MSKU(特殊库存)相关表。这个后面单独说。
所以回答标题第一个问题"库存放在哪个表":普通库存的数量在 MARD,批次库存数量在 MCHB,特殊库存数量在 MSKU,而每次变化的明细在 MSEG。如果只想看某一时刻的库存余额,不用去翻 MSEG 流水,直接查 MARD / MCHB / MSKU 就行。
1.3 采购订单历史:EKBE 记录每一次交付
除了物料凭证,采购订单历史表 EKBE 也会同步更新。EKBE 是"采购订单历史"表,每一行代表采购订单行项目的一次业务动作(收货、发票校验、后续交货等)。关键字段:
- EBELN、EBELP:采购订单号及行项目
- BELNR、BUZEI:关联的物料凭证号和行号
- BEWTP:业务类型,比如 WE 表示收货,RE 表示发票
- MENGE:本次动作的数量
- DMBTR:本次动作的金额
- BUDAT:过账日期
EKBE 的价值在于,它能让你从"采购订单"的视角看这单到底收了多少货、来了多少发票、还剩多少没交付。很多采购订单状态报表都是基于 EKBE 做的。但要注意,EKBE 存的是"发生过的动作",不是"当前状态",已冲销的动作不会自动从 EKBE 里消失,查状态时要额外处理。
2. 库存数量与金额的"分家":为什么库存表没有金额,金额又去了哪
在做物料账的人十有八九会有个困惑:MARD 里没有金额字段,那我的库存金额到底在哪?这个问题要分两个层面回答。
2.1 MARD 只有数量,真的没有金额
MARD 从设计之初就是一张数量账,它只回答"这个物料在这个工厂这个库位,当前有多少可用、多少在检、多少冻结",不回答"这些库存值多少钱"。因为库存金额在 SAP 里不是一个简单的"数量乘单价",它受到移动平均价、标准价、物料分类账、汇率差异等一堆因素影响。如果 MARD 里塞一个金额字段,每次过账都要按不同估价方式更新,维护成本会失控。
所以在 MM 层面,"库存数量"和"库存价值"是分开管理的。数量归 MARD,价值归物料账和财务账。
2.2 金额要看 MBEW / MBEWH 或者 FI 总账
先看物料本身的价值。启用物料账(Material Ledger)之前,物料当前的评估价格和价值在 MBEW 表,主键是 MATNR + BWKEY(评估范围)+ BWTAR(评估类型)。
MBEW 里和库存金额直接相关的是这几个字段:
- LBKUM:评估库存总数量
- SALK3:评估库存总价值(本位币)
- VKSAL:上期结转价值
- VBKUM:上期结转数量
- VPRSV:价格控制标识,S 表示标准价,V 表示移动平均价
- VERPR:当前期间价格
- STPRS:标准价格
如果启用了物料分类账,那按月、按凭证的库存价值明细就更复杂了,核心表是 MLHD(物料分类账抬头)和 MLIT(物料分类账行项目),可以按期间、按凭证追溯库存数量和金额的变化。SAP 的 MR21、MR22 改价,或者 CKMLCP 物料账结算,都会写进这套表。
再从财务视角看,库存科目的当期余额其实是总账层面的金额。换句话说,财务上真正的"存货资产"在 FI 的科目余额里(比如总账科目 140001 原材料,通过 BSEG/BSIS/FAGLFLEXT 等表反映)。MBEW 里的 SALK3 只是物料维度的价值参考,它和总账科目余额之间靠评估范围、科目表、评估类这些配置关联,两边可能因为未清项、外币评估、未结算差异存在短期不一致,月底通过物料分类账结算或重分类调整拉平。
所以回答"物料数量以及金额在哪个表":数量看 MARD/MCHB/MSKU,价值看 MBEW(未启物料账时)或 MLIT/MLHD + FI 总账。如果想直接看某一期间的期初期末库存金额,MB5L、MB5S 这些标准报表底层就是在处理这些表的组合。
2.3 特殊库存和批次库存别用错了表
这里提醒一句,MARD 只包含"普通库存",也就是属于自己的、放在自有库存地点的库存。下面这几种情况都不要去 MARD 里找:
- 供应商寄售库存(特殊库存标识 K):数量在 MSKU,物料属于供应商,结算时才转自有库存
- 销售订单库存(特殊库存标识 E):数量在 MSKU,挂在具体销售订单下
- 项目库存(特殊库存标识 Q):数量在 MSKU,挂在 WBS 元素下
- 批次库存:数量在 MCHB,字段逻辑和 MARD 类似
判断到底该查哪张表,可以看物料凭证流水里的 SOBKZ(特殊库存标识)字段。SOBKZ 为空就是普通库存,查 MARD;有值就按值去找 MSKU 或 MCHB。另外一个实用经验是,MB51 查物料凭证时,勾上"物料编号、工厂、库存地点"这些字段就能直接看出每一笔冲的是普通库存还是特殊库存,不用自己猜。
3. 从物料凭证到 FI 凭证:101 过账和 OBYC 科目确定的完整逻辑
现在回答标题里最核心的问题:采购验收是如何产生会计凭证的。
流程本身不复杂:MIGO 收货保存后,系统先写物料凭证(MKPF/MSEG),同时根据物料主数据和后台科目确定配置,自动生成一张 FI 会计凭证(BKPF/BSEG)。但"自动生成"背后有一套完整的规则,理解它才能看懂金额为什么这样拆。
3.1 收货过账的关键配置:OBYC 中的 BSX / WRX / PRD
SAP 的 MM 自动科目确定配置集中在一个事务码里:OBYC。这里面按"事务键"(Transaction Key)区分不同业务场景,采购收货时最常碰到的三个事务键是:
- BSX:库存记账。收货时借记存货科目的规则
- WRX:GR/IR 结算。收货时贷记"应付账款-暂估"(Goods Receipt / Invoice Receipt)的规则
- PRD:采购价格差异。标准价物料收货产生价差时,差异进这个科目
- KDM:供应商折扣/后续贷记,通常发票校验后处理
OBYC 的科目确定不是"移动类型选科目",而是靠三个维度组合:科目表(Chart of Accounts)、评估修改(Valuation Modifier)、评估类(Valuation Class)。其中评估类存在物料主数据的会计视图(表 MARC-BKLAS),它把物料按属性分组,比如原材料、半成品、成品各用一个评估类,然后在 OBYC 里给每个评估类指定对应的存货科目和 GR/IR 科目。
所以流程可以概括为:收货时,系统根据物料主数据找到评估类,再根据工厂所属的评估范围和后台配置的评估修改,在 OBYC 中匹配出应记账的存货科目和 GR/IR 科目,然后生成分录。
3.2 价格控制方式决定差异怎么走
这一步是最容易懵的地方。同样一笔收货,物料主数据里如果价格控制是 V(移动平均价)和 S(标准价),会计凭证的分录是不同的。
移动平均价(V):
- 存货科目按采购订单的净价入账
- 分录就是借:存货科目(BSX),贷:GR/IR(WRX)
- 金额相等,没有差异行
标准价(S):
- 存货科目按物料主数据里的标准价格入账
- GR/IR 按采购订单价格入账
- 两者之间的差额进采购价格差异科目(PRD)
举个例子,某物料标准价是 100 元,这次采购订单净价是 120 元,移动类型 101 收货后,FI 凭证是三行:
- 借:原材料存货科目(BSX) 100 元
- 借:采购价格差异(PRD) 20 元
- 贷:GR/IR(WRX) 120 元
这就是为什么你拿着 MSEG 里的 DMBTR 去对 FI 凭证,经常对不上的原因。MSEG 的 DMBTR 反映的是物料凭证层面的总金额(通常是采购价或评估价),而 BSEG 是会计凭证层面的分录行,两者是"多条分录对一条流水"的关系。
3.3 一张 101 凭证科目确定实例
标准配置下,一次普通采购收货产生的会计凭证大致是这个套路:
| 行 | 科目方向 | 科目来源 | 金额来源 |
|---|---|---|---|
| 1 | 借 | 存货科目(OBYC-BSX,按评估类确定) | 标准价或移动平均价 |
| 2 | 借/贷 | 价格差异科目(OBYC-PRD,仅标准价物料存在差额时) | 标准价与采购价差额 |
| 3 | 贷 | GR/IR 科目(OBYC-WRX) | 采购订单净价 |
如果这张采购订单涉及运费、关税、杂费,或者收货时启用了"收货自动冲销"逻辑,科目行还会更多。例如 103(收货到质检库存)转 105(质检转可用库存)是两笔凭证;跨工厂转储 101(从工厂 A 转出用 303 等)则涉及不同的移动类型和科目规则,但科目确定思路都一样:找 BSX、找 WRX、找 PRD。
另外,S/4HANA 里 GR/IR 记账还多了供应商维度,后台可以在 OBYC 的 GR/IR 科目上开启供应商字段,这样每一笔 GR/IR 都能按供应商挂账,发票校验时自动清账,降低了余额对账的难度。这也是为什么新项目上,财务都会要求把 GR/IR 科目的未清项管理打开。
4. 表关联实操:从一张收货单反查科目、库存数量与库存金额
这一节直接给你关联路径,重点是理解 SAP 里物料凭证和会计凭证之间的"隐藏桥"。
4.1 从物料凭证到会计凭证的黄金桥:BKPF-AWKEY
MSEG 里面其实没有一个叫"FI 凭证号"的字段,很多人在这里卡住。那怎么从物料凭证找到会计凭证?答案是看会计凭证抬头表 BKPF 的 AWKEY 字段。
BKPF-AWKEY 存的是一个"参考关键字",它把 FI 凭证和源业务单据联系起来。对于收货产生的 FI 凭证,AWKEY 的规则是"物料凭证号 + 物料凭证年度",直接拼成一个字符串。同时 BKPF-AWTYP(参考事务类型)会标记出来源类型,比如 MKPF 代表物料凭证。
所以从收货物料凭证找 FI 凭证的逻辑是:
- 从 MSEG 拿到 MBLNR、MJAHR
- 用 AWTYP = 'MKPF' 且 AWKEY = MBLNR + MJAHR 在 BKPF 里查 FI 凭证抬头
- 从 BKPF 拿到 BUKRS、BELNR、GJAHR
- 再去 BSEG 按 BUKRS + BELNR + GJAHR 取分录行
反过来说,如果财务给了一张 FI 凭证,你想看它是不是来自收货,也看 BKPF-AWTYP 和 AWKEY。AWTYP 是 MKPF 的就是物料凭证,再把 AWKEY 拆开,前 10 位是物料凭证号,后面是年度,就能去 MKPF/MSEG 查明细。
4.2 反向追查:会计凭证如何知道它来自收货
还有一种常见场景:BSEG 里的某一行的来源不明,比如看到一笔 20 元的"采购价格差异"借方,不知道是谁产生的。这时可以回到 BKPF 找 AWTYP 和 AWKEY,如果是 MKPF,再去 MSEG 查对应的物料凭证。MSEG 里有 EBELN、EBELP,能一路追到采购订单;有 MATNR、WERKS,能追到物料和工厂;有 LFBNR,能追到它是不是冲销了某个原始收货。
要注意,冲销凭证(比如 102 冲 101)的 AWKEY 指向的是一张新的物料凭证,而不是原始凭证。原始 101 和冲销 102 之间靠 MSEG-LFBNR/LFBJA 关联。所以做凭证链追溯时,要同时用 AWKEY 和 LFBNR 两条路,才能把"原凭证-冲销凭证"完整串起来。
另外,MSEG 里还有两个字段经常被忽略:KDAUF/KDPOS(销售订单号/行)和 AUFNR(生产订单号),采购收货如果是为了某个订单采购,这些字段会带上订单号,做"采购收货成本归集"报表时非常有用。
4.3 完整 SQL 排查链示例
给你一套可以拿去改的查询思路。假设现在要查供应商 1000 在 2024 年 5 月 1 日收货后,某物料的库存数量、库存金额和 FI 分录。
第一步,按采购订单或物料查 MSEG 流水:
SELECT MBLNR, MJAHR, ZEILE, BWART, SHKZG, MENGE, DMBTR, EBELN, EBELP FROM MSEG WHERE MATNR = '物料号' AND WERKS = '1000' AND BUDAT >= '20240501' AND BUDAT <= '20240531' AND BWART = '101';第二步,拿第一行返回的 MBLNR、MJAHR 去 BKPF 找 FI 凭证:
SELECT BUKRS, BELNR, GJAHR, AWTYP, AWKEY, BLART, BUDAT FROM BKPF WHERE AWTYP = 'MKPF' AND AWKEY = '该MBLNR' || '该MJAHR';第三步,用 BKPF 返回的 BUKRS、BELNR、GJAHR 去 BSEG 看行项目:
SELECT BUZEI, SHKZG, HKONT, DMBTR, WRBTR, KOSTL, AUFNR FROM BSEG WHERE BUKRS = '公司代码' AND BELNR = '凭证号' AND GJAHR = '年度';第四步,看当前库存数量和库存金额:
-- 普通库存数量 SELECT LABST, UMLME, INSME, SPEME FROM MARD WHERE MATNR = '物料号' AND WERKS = '1000' AND LGORT = '库存地点'; -- 物料当前价值 SELECT LBKUM, SALK3, VPRSV, VERPR FROM MBEW WHERE MATNR = '物料号' AND BWKEY = '1000';如果启用了物料分类账,想按期间看数量和金额变化,可以查 MLIT,按 MBLNR、MJAHR 过滤,它会把每一笔物料凭证对应的物料账金额和 FI 过账标记都带出来。我在项目上做"进销存差异分析"时,MLIT 的效率比 MSEG + BSEG 手工核对高很多,因为 MLIT 已经把物料号、期间、数量、金额、FI 凭证号串好。
5. 开发采购收货报表时,几个绕不开的坑和通用做法
最后聊点实战经验。这几条是我在多个项目里真金白银踩出来的,尤其是做 MM-FI 对账报表的同事,建议认真看。
5.1 坑一:MSEG 的金额直接累加不等于入账金额
前面说过,标准价物料的收货会产生差异行,差异在 BSEG,不在 MSEG。如果你直接 SELECT SUM(DMBTR) FROM MSEG 来算库存金额变化,得到的结果和总账一定对不上。
通用做法:以 BSEG 的库存科目行(BSX 对应的科目)为准累加金额,或者用 MLIT 汇总物料账金额,再或者干脆以总账科目余额为主表,MSEG 只用来核对明细张数,不做金额汇总。开发报表时我一般这样分:数量变化看 MSEG(MENGE),金额变化看 BSEG 或 MLIT,两条链路分开设计,不要混在一个 SQL 里。
5.2 坑二:MARD 没有历史,查期间库存不能用它
MARD 只存当前数量,每笔过账直接更新对应字段。你查 5 月 31 日的 MARD,看到的是当前值,不是 5 月底的历史值,等于把 6 月发生的业务也算进去了。
要查某期间期初期末库存,有几个标准路径:
- 物料分类账:MLIT/MLHD 按期间过滤,最准确
- 物料凭证流水:起点库存 + 期间内入 - 期间内出 = 期末库存,工作量比较大
- 标准报表 MB5B(物料库存的收发货日期清单)和 MB5L(库存余额),它们底层就是把物料凭证流水按期间结转计算
所以做库存期间报表,不要设计成"SE16N 导出 MARD 就能出数"的思路。MARD 只能用于"此时此刻还有多少"的场景。
5.3 坑三:冲销、退货、质检库存和寄售库存的关联差异
冲销收货(移动类型 102)在 MSEG 里是一行反向数据,SHKZG 会反过来,但 MARD 的更新要看原始 101 是不是已经入账。如果原始 101 是把货收进质检库存(INSME),冲销 102 也是从 INSME 扣减,不会动 LABST。所以做"库存数量变化"报表时,必须按库存类型分类统计,不能只盯着 LABST。
退货给供应商(移动类型 122)也是容易错的地方。122 虽然挂着采购订单,但它不是简单的收货冲销,它可能产生额外的退货费用或触发发票校验的后续处理。查表时,MSEG 里的 BWART 要单独按 122 过滤,不能和 101 混在一起算"采购入库量"。
寄售库存更特殊:供应商寄售库存(SOBKZ = K)在 MSKU 里,物料还没有所有权,不产生存货科目入账。当从寄售库存转自有库存(移动类型 201 或 411/412 结合)消费/领用时,才按转储的移动类型产生 FI 凭证。如果做采购库存金额报表时把寄售库存的 MSKU 数量当成普通库存金额去算,会虚增存货。这一点我在项目上栽过,最后加了 SOBKZ 过滤条件才把报表调整正确。
最后说一个我现在做事的习惯:新接手的 SAP 报表需求,先画两条线——一条走 MM 数量(MKPF/MSEG → MARD/MCHB/MSKU),一条走 FI 金额(BKPF/BSEG → 总账或 MLIT),两条线在物料凭证号(MBLNR)和会计凭证(BELNR)这个交叉点汇合。这样既不会漏掉数量,也不会算错金额,后续对账、排查问题都省心很多。