SAP MM 模块物料主数据批量导入与增强开发:字段扩展、校验逻辑与批量导入工具开发
博客简介:针对物料主数据治理场景,讲解物料主数据自定义字段配置、BADI_MATERIAL_CHECK 校验逻辑开发、LSMW/CATT 批量导入工具配置、自定义批量导入程序开发的核心方法,附"物料编码自动生成""物料描述规范校验""视图扩展"的实现代码,提升物料主数据维护效率与数据准确性。
"采购订单增强你会了,MM 模块另一个高频场景------物料主数据------也少不了增强。本篇从物料主数据字段扩展(CI_MARA/CI_MARC)→ BADI_MATERIAL_CHECK 校验 → LSMW 批量导入 → 自定义导入程序,完整覆盖物料主数据治理需求,附物料编码自动生成、描述规范校验、视图扩展的可复用代码。"
本篇针对物料主数据治理场景,讲解物料主数据自定义字段配置、BADI_MATERIAL_CHECK 校验逻辑开发、LSMW/CATT 批量导入工具配置、自定义批量导入程序开发的核心方法,附"物料编码自动生成""物料描述规范校验""视图扩展"的实现代码。
📖 写在前面
本篇定位
MM 模块物料主数据是整个供应链的基础数据------采购、生产、销售、仓储都要用到物料号。物料主数据的质量直接影响业务执行效率。本篇聚焦物料主数据的两个核心增强场景:①字段扩展 + BAdI 校验 (提升物料数据质量);②批量导入工具开发(提升数据维护效率),所有代码基于 SAP NetWeaver 7.51。
本篇适合谁读
- 需要在 MM 模块做物料主数据增强的开发者
- 需要批量导入物料数据的业务分析师
- 负责物料主数据治理的产品经理
贯穿项目概览
贯穿项目:物料主数据增强 + 批量导入
业务需求(来自物料管理部):
┌─────────────────────────────────────────────────────────┐
│ 1. 字段扩展: │
│ - 基本数据:旧物料号(ZOLD_MATNR)、安全库存级别(ZSAFETY_LEVEL)│
│ - 工厂数据:批次管理开关(ZBATCH_FLAG)、效期管理开关(ZEXPIRY_FLAG)│
│ │
│ 2. 校验规则: │
│ - 物料编码必须符合命名规范(前缀+类别+流水号) │
│ - 物料描述不能为空,且长度≥5个字符 │
│ - 旧物料号不能重复 │
│ - 危险品类(物料组 Z001~Z009)必须开启批次管理 │
│ │
│ 3. 批量导入: │
│ - LSMW 批量导入工具(支持多视图同步创建) │
│ - 自定义导入程序(带完整日志和错误回写) │
└─────────────────────────────────────────────────────────┘
#mermaid-svg-DtHJyJB6mWKD1ew2{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-DtHJyJB6mWKD1ew2 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-DtHJyJB6mWKD1ew2 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-DtHJyJB6mWKD1ew2 .error-icon{fill:#552222;}#mermaid-svg-DtHJyJB6mWKD1ew2 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-DtHJyJB6mWKD1ew2 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-DtHJyJB6mWKD1ew2 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-DtHJyJB6mWKD1ew2 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-DtHJyJB6mWKD1ew2 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-DtHJyJB6mWKD1ew2 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-DtHJyJB6mWKD1ew2 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-DtHJyJB6mWKD1ew2 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-DtHJyJB6mWKD1ew2 .marker.cross{stroke:#333333;}#mermaid-svg-DtHJyJB6mWKD1ew2 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-DtHJyJB6mWKD1ew2 p{margin:0;}#mermaid-svg-DtHJyJB6mWKD1ew2 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-DtHJyJB6mWKD1ew2 .cluster-label text{fill:#333;}#mermaid-svg-DtHJyJB6mWKD1ew2 .cluster-label span{color:#333;}#mermaid-svg-DtHJyJB6mWKD1ew2 .cluster-label span p{background-color:transparent;}#mermaid-svg-DtHJyJB6mWKD1ew2 .label text,#mermaid-svg-DtHJyJB6mWKD1ew2 span{fill:#333;color:#333;}#mermaid-svg-DtHJyJB6mWKD1ew2 .node rect,#mermaid-svg-DtHJyJB6mWKD1ew2 .node circle,#mermaid-svg-DtHJyJB6mWKD1ew2 .node ellipse,#mermaid-svg-DtHJyJB6mWKD1ew2 .node polygon,#mermaid-svg-DtHJyJB6mWKD1ew2 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-DtHJyJB6mWKD1ew2 .rough-node .label text,#mermaid-svg-DtHJyJB6mWKD1ew2 .node .label text,#mermaid-svg-DtHJyJB6mWKD1ew2 .image-shape .label,#mermaid-svg-DtHJyJB6mWKD1ew2 .icon-shape .label{text-anchor:middle;}#mermaid-svg-DtHJyJB6mWKD1ew2 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-DtHJyJB6mWKD1ew2 .rough-node .label,#mermaid-svg-DtHJyJB6mWKD1ew2 .node .label,#mermaid-svg-DtHJyJB6mWKD1ew2 .image-shape .label,#mermaid-svg-DtHJyJB6mWKD1ew2 .icon-shape .label{text-align:center;}#mermaid-svg-DtHJyJB6mWKD1ew2 .node.clickable{cursor:pointer;}#mermaid-svg-DtHJyJB6mWKD1ew2 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-DtHJyJB6mWKD1ew2 .arrowheadPath{fill:#333333;}#mermaid-svg-DtHJyJB6mWKD1ew2 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-DtHJyJB6mWKD1ew2 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-DtHJyJB6mWKD1ew2 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-DtHJyJB6mWKD1ew2 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-DtHJyJB6mWKD1ew2 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-DtHJyJB6mWKD1ew2 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-DtHJyJB6mWKD1ew2 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-DtHJyJB6mWKD1ew2 .cluster text{fill:#333;}#mermaid-svg-DtHJyJB6mWKD1ew2 .cluster span{color:#333;}#mermaid-svg-DtHJyJB6mWKD1ew2 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-DtHJyJB6mWKD1ew2 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-DtHJyJB6mWKD1ew2 rect.text{fill:none;stroke-width:0;}#mermaid-svg-DtHJyJB6mWKD1ew2 .icon-shape,#mermaid-svg-DtHJyJB6mWKD1ew2 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-DtHJyJB6mWKD1ew2 .icon-shape p,#mermaid-svg-DtHJyJB6mWKD1ew2 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-DtHJyJB6mWKD1ew2 .icon-shape .label rect,#mermaid-svg-DtHJyJB6mWKD1ew2 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-DtHJyJB6mWKD1ew2 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-DtHJyJB6mWKD1ew2 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-DtHJyJB6mWKD1ew2 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 字段扩展
CI_MARA / CI_MARC
BADI_MATERIAL_CHECK
CHECK_DATA 校验
物料编码
Z_MATNR_GENERATE
批量导入
LSMW / 自定义程序
BAPI_MATERIAL_SAVEDATA
批量创建物料
版本说明:本篇内容基于 SAP NetWeaver 7.51,BADI_MATERIAL_CHECK 在 ECC 6.0+ 版本中均可用。
一、物料主数据增强架构
1.1 物料主数据核心增强点
| 增强类型 | 增强名称 | 触发时机 | 适用事务码 | 推荐场景 |
|---|---|---|---|---|
| BAdI | BADI_MATERIAL_CHECK | 物料保存前校验 | MM01/MM02 | 核心校验、字段合法性检查 |
| BAdI | BADI_MATERIAL_REF | 物料引用/删除时 | MM06 | 物料删除前检查、引用数据清理 |
| BAdI | BADI_MATERIAL_UI_CUST | MM01/MM02 界面 | MM01/MM02 | 自定义界面逻辑、增强标签页 |
| 用户出口 | EXIT_SAPLMGMU_001 | 物料保存前(老方式) | MM01/MM02 | 老项目兼容 |
| 显式增强 | ENHANCEMENT-POINT | SAP 预留标记 | MM01/MM02 | 灵活插入逻辑 |
推荐优先用 BADI_MATERIAL_CHECK:功能最完善、参数最规范、支持多实现。
1.2 物料主数据的关键表与追加结构
| SAP 表 | 说明 | 追加结构 | 扩展对象 |
|---|---|---|---|
| MARA | 物料基本数据 | CI_MARA | 基本视图自定义字段 |
| MARC | 工厂级别物料数据 | CI_MARC | 工厂视图自定义字段 |
| MBEW | 会计评估数据 | CI_MBEW | 会计视图自定义字段 |
| MARD | 存储位置数据 | CI_MARD | 存储位置视图字段 |
| MVKE | 销售数据 | CI_MVKE | 销售视图自定义字段 |
1.3 BADI_MATERIAL_CHECK 接口方法
⚠️ 技术要点 :BADI_MATERIAL_CHECK 的接口
IF_EX_BADI_MATERIAL_CHECK只有一个核心方法CHECK_DATA。所有校验逻辑都写在这一个方法中,通过参数IM_ACTIVITY区分操作类型。
| 方法名 | 功能 | 关键参数 |
|---|---|---|
| CHECK_DATA | 物料主数据综合校验 | IM_ACTIVITY(活动类型)、IM_MARA(基本数据)、IM_MARC(工厂数据) |
参数说明:
| 参数 | 类型 | 说明 |
|---|---|---|
| IM_ACTIVITY | TRTYP | 活动类型:H=创建、V=修改、A=显示 |
| IM_MARA | MARA | 物料基本数据(含 CI_MARA 追加字段) |
| IM_MARC | MARC | 物料工厂数据(含 CI_MARC 追加字段) |
二、自定义字段扩展
2.1 基本数据视图扩展(CI_MARA)
SE11 → 输入结构名 CI_MARA → 创建(Append 结构)
CI_MARA 是 SAP 预留的物料基本数据追加结构容器。
在 CI_MARA 中定义自定义字段:
┌──────────────┬──────────┬──────────┬──────────────────────────────┐
│ 字段名 │ 数据类型 │ 长度 │ 说明 │
├──────────────┼──────────┼──────────┼──────────────────────────────┤
│ ZOLD_MATNR │ CHAR │ 18 │ 旧物料号(系统切换前的编号) │
│ ZSAFETY_LEVEL│ CHAR │ 1 │ 安全库存级别(1/2/3) │
│ ZDESIGNER │ CHAR │ 12 │ 设计人员 │
│ ZDESIGN_DATE │ DATS │ 8 │ 设计日期 │
│ ZMAT_TYPE │ CHAR │ 10 │ 自定义物料分类 │
└──────────────┴──────────┴──────────┴──────────────────────────────┘
激活 CI_MARA → 自动追加到 MARA 表
2.2 工厂视图扩展(CI_MARC)
SE11 → 输入结构名 CI_MARC → 创建(Append 结构)
在 CI_MARC 中定义自定义字段:
┌──────────────────┬──────────┬──────────┬──────────────────────────────┐
│ 字段名 │ 数据类型 │ 长度 │ 说明 │
├──────────────────┼──────────┼──────────┼──────────────────────────────┤
│ ZBATCH_FLAG │ FLAG │ 1 │ 批次管理开关(关联 XCHPF) │
│ ZEXPIRY_FLAG │ FLAG │ 1 │ 效期管理开关(关联 XDATE) │
│ ZMAX_STORAGE_DAYS│ INT4 │ 10 │ 最大存储天数 │
│ ZMIN_BATCH_QTY │ QUAN │ 13 │ 最小批次数量 │
└──────────────────┴──────────┴──────────┴──────────────────────────────┘
激活 CI_MARC → 自动追加到 MARC 表
2.3 视图扩展(MM01/MM02 界面显示)
SAP 标准的 MM01/MM02 界面会自动显示 CI_MARA/CI_MARC 等追加结构的字段:
MM01 创建物料 → 基本数据视图
→ 自定义字段自动出现在底部的"客户字段"区域
MM01 创建物料 → 工厂视图
→ 自定义字段自动出现在底部的"客户字段"区域
把自定义字段放到主界面(而非底部区域)的三种方式:
| 方式 | 说明 | 难度 |
|---|---|---|
| Enhancement 向导 | SE80 → 屏幕增强 → 创建自定义子屏幕 | 中 |
| BADI_MATERIAL_UI_CUST | 修改界面逻辑 | 高 |
| Screen Painter 自定义子屏幕 | 直接画屏幕控件 | 中 |
三、BADI_MATERIAL_CHECK 校验逻辑开发
3.1 配置表设计
物料编码规则配置表:
表名:ZT_MATNR_RULE
MANDT TYPE MANDT " 客户端
MATKL TYPE CHAR9 " 物料组(*=通配)
PREFIX TYPE CHAR2 " 编码前缀(如 RM/FG/SG)
CATEGORY TYPE CHAR2 " 物料类别(01/02/03)
SERIAL_FROM TYPE NUMC6 " 流水号下限
SERIAL_TO TYPE NUMC6 " 流水号上限
LENGTH TYPE NUMC2 " 编码总长度
ACTIVE TYPE FLAG " 是否激活
物料黑名单表:
表名:ZT_MATERIAL_BLACKLIST
MANDT TYPE MANDT " 客户端
MATNR TYPE MATNR " 物料号
BLOCK_FLAG TYPE FLAG " 黑名单标记
REASON TYPE CHAR50 " 黑名单原因
CREATE_BY TYPE UNAME " 创建人
CREATE_DATE TYPE DATUM " 创建日期
3.2 完整 BAdI 实现代码
⚠️ 关键修正:
- 接口方法名是
CHECK_DATA,不是CHECKIM_ACTIVITY的值是H/V/A,不是01/02/03- 自定义字段通过
ASSIGN COMPONENT动态访问(因追加结构字段可能在编译期不可见)
abap
"=======================================================================
" BAdI 实现:ZIMPL_MATNR_CHECK
" BAdI:BADI_MATERIAL_CHECK
" 功能:物料编码规范校验 + 描述规范 + 旧物料号重复检查 + 批次管理校验
" 触发:MM01/MM02 保存前
" 创建日期:2026.09.15
"=======================================================================
CLASS cl_impl_matnr_check DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_ex_badi_material_check.
PRIVATE SECTION.
METHODS get_custom_field
IMPORTING iv_field_name TYPE string
is_mara TYPE mara
RETURNING VALUE(rv_value) TYPE string.
ENDCLASS.
CLASS cl_impl_matnr_check IMPLEMENTATION.
METHOD if_ex_badi_material_check~check_data.
"-------------------------------------------------------------------
" 步骤0:活动类型判断(只对创建/修改校验)
" H=创建, V=修改, A=显示
"-------------------------------------------------------------------
IF im_activity NOT IN ('H', 'V').
RETURN.
ENDIF.
DATA: lv_matnr TYPE matnr,
lv_matkl TYPE matkl,
lv_biskey TYPE biskey,
lv_old_matnr TYPE char18.
lv_matnr = im_mara-matnr.
lv_matkl = im_mara-matkl.
lv_biskey = im_mara-biskey.
" 通过动态 ASSIGN 访问自定义字段 ZOLD_MATNR
lv_old_matnr = me->get_custom_field(
iv_field_name = 'ZOLD_MATNR'
is_mara = im_mara ).
" ================================================================
" 校验1:物料编码命名规范(仅创建时校验)
" 规则:前缀2位 + 类别2位 + 流水号6位 = 10位
" 示例:RM01000001
" ================================================================
IF im_activity = 'H' AND lv_matnr IS NOT INITIAL.
" 长度检查(物料号内部格式为18位,前导补0)
DATA(lv_matnr_trim) = lv_matnr.
CALL FUNCTION 'CONVERSION_EXIT_MATN1_OUTPUT'
EXPORTING input = lv_matnr
IMPORTING output = lv_matnr_trim.
IF STRLEN( lv_matnr_trim ) < 10.
MESSAGE e001(zmm_mat_enh) WITH lv_matnr.
ENDIF.
" 从配置表读取该物料组的编码规则
DATA: ls_rule TYPE zt_matnr_rule.
SELECT SINGLE *
FROM zt_matnr_rule
INTO @ls_rule
WHERE matkl = @lv_matkl
AND active = 'X'.
IF sy-subrc = 0.
" 校验前缀
IF lv_matnr_trim(2) <> ls_rule-prefix.
MESSAGE e002(zmm_mat_enh)
WITH lv_matnr ls_rule-prefix.
ENDIF.
ENDIF.
ENDIF.
" ================================================================
" 校验2:物料描述规范
" 规则:不能为空,长度≥5,不能只包含数字
" ================================================================
IF lv_biskey IS INITIAL.
MESSAGE e004(zmm_mat_enh).
ELSEIF STRLEN( lv_biskey ) < 5.
MESSAGE e005(zmm_mat_enh) WITH lv_biskey.
ELSEIF lv_biskey CO '0123456789'.
MESSAGE e006(zmm_mat_enh) WITH lv_biskey.
ENDIF.
" ================================================================
" 校验3:旧物料号不能重复
" ================================================================
IF lv_old_matnr IS NOT INITIAL.
SELECT SINGLE matnr FROM mara
INTO @DATA(lv_existing_matnr)
WHERE matnr = @lv_old_matnr.
IF sy-subrc = 0 AND lv_existing_matnr <> lv_matnr.
MESSAGE e007(zmm_mat_enh) WITH lv_old_matnr.
ENDIF.
" 检查黑名单
SELECT SINGLE * FROM zt_material_blacklist
INTO @DATA(ls_black)
WHERE matnr = @lv_old_matnr
AND block_flag = 'X'.
IF sy-subrc = 0.
MESSAGE e008(zmm_mat_enh)
WITH lv_old_matnr ls_black-reason.
ENDIF.
ENDIF.
" ================================================================
" 校验4:危险品类(Z001~Z009)必须开启批次管理
" ================================================================
IF im_marc IS NOT INITIAL AND lv_matkl IS NOT INITIAL.
" 判断物料组是否在 Z001~Z009 范围(前4位匹配 Z00)
IF lv_matkl(4) = 'Z00'.
IF im_marc-xchpf <> 'X'.
MESSAGE e009(zmm_mat_enh) WITH lv_matnr lv_matkl.
ENDIF.
ENDIF.
ENDIF.
ENDMETHOD.
METHOD get_custom_field.
FIELD-SYMBOLS: <fs_value> TYPE any.
ASSIGN COMPONENT iv_field_name OF STRUCTURE is_mara
TO <fs_value>.
IF sy-subrc = 0 AND <fs_value> IS ASSIGNED.
rv_value = <fs_value>.
ELSE.
rv_value = ''.
ENDIF.
ENDMETHOD.
ENDCLASS.
3.3 消息类配置(SE91)
消息类:ZMM_MAT_ENH
001:物料号 &1 长度不符合规范(应≥10位)
002:物料号 &1 前缀不符合配置规则(应为 &2)
003:物料号 &1 建议使用前缀 &2(未配置规则时的默认前缀)
004:物料描述不能为空
005:物料描述 &1 长度不足5位
006:物料描述 &1 不能只包含数字
007:旧物料号 &1 已存在于系统中,不能重复
008:旧物料号 &1 在黑名单中(原因:&2)
009:危险品类 &1(物料组 &2)必须开启批次管理
四、物料编码自动生成
4.1 编码规则配置表
表名:ZT_MATNR_SEQ(物料编码流水号)
MANDT TYPE MANDT " 客户端
PREFIX TYPE CHAR2 " 编码前缀(RM/FG/SG)
CATEGORY TYPE CHAR2 " 物料类别(01/02/03)
SERIAL_NUM TYPE NUMC10 " 当前流水号
LAST_USED TYPE TIMESTAMPL " 最后使用时间
PRG_CREATED TYPE PROGNAME " 创建者程序
DATE_MODIFIED TYPE DATUM " 修改日期
4.2 编码生成函数
abap
"=======================================================================
" 函数:Z_MATNR_GENERATE
" 功能:根据规则自动生成物料编码(并发安全)
" 返回:生成的物料号 + 消息类型
"=======================================================================
FUNCTION z_matnr_generate.
IMPORTING
iv_matkl TYPE matkl " 物料组
iv_prefix TYPE char5 " 编码前缀(可选)
iv_category TYPE char2 " 物料类别(可选)
EXPORTING
ev_matnr TYPE matnr " 生成的物料号(含前导零18位)
ev_msgtype TYPE char1 " S=成功 / E=错误
ev_message TYPE char100 " 消息内容
EXCEPTIONS
no_rule_defined.
DATA: lv_prefix TYPE char5,
lv_category TYPE char2,
lv_serial TYPE numc10,
ls_seq TYPE zt_matnr_seq,
lv_matnr_raw TYPE char18.
"-------------------------------------------------------------------
" 步骤1:确定编码前缀
"-------------------------------------------------------------------
IF iv_prefix IS NOT INITIAL.
lv_prefix = iv_prefix.
ELSE.
SELECT SINGLE prefix
FROM zt_matnr_rule
INTO @lv_prefix
WHERE matkl = @iv_matkl
AND active = 'X'.
IF sy-subrc <> 0.
" 未配置规则,用默认前缀
CASE iv_matkl(2).
WHEN '01'. lv_prefix = 'RM'.
WHEN '02'. lv_prefix = 'FG'.
WHEN '03'. lv_prefix = 'SG'.
WHEN OTHERS. lv_prefix = 'OT'.
ENDCASE.
ENDIF.
ENDIF.
"-------------------------------------------------------------------
" 步骤2:确定物料类别
"-------------------------------------------------------------------
IF iv_category IS NOT INITIAL.
lv_category = iv_category.
ELSE.
lv_category = iv_matkl+2(2).
ENDIF.
"-------------------------------------------------------------------
" 步骤3:获取流水号(FOR UPDATE 锁行,保证并发安全)
"-------------------------------------------------------------------
SELECT SINGLE *
FROM zt_matnr_seq
INTO @ls_seq
WHERE prefix = @lv_prefix
AND category = @lv_category
FOR UPDATE.
IF sy-subrc = 0.
lv_serial = ls_seq-serial_num + 1.
UPDATE zt_matnr_seq
SET serial_num = @lv_serial,
last_used = @( zcl_helper=>get_timestamp( ) ),
date_modified = @sy-datum
WHERE prefix = @lv_prefix
AND category = @lv_category.
ELSE.
lv_serial = 1.
ls_seq-prefix = lv_prefix.
ls_seq-category = lv_category.
ls_seq-serial_num = lv_serial.
ls_seq-last_used = zcl_helper=>get_timestamp( ).
ls_seq-prg_created = sy-cprog.
ls_seq-date_modified = sy-datum.
INSERT zt_matnr_seq FROM @ls_seq.
ENDIF.
"-------------------------------------------------------------------
" 步骤4:拼接编码(前缀 + 类别 + 流水号)
" 示例:RM01000001
"-------------------------------------------------------------------
lv_matnr_raw = |{ lv_prefix }{ lv_category }{ lv_serial ALPHA = IN }|.
" 转换为内部物料号格式(前导补零到18位)
CALL FUNCTION 'CONVERSION_EXIT_MATN1_INPUT'
EXPORTING
input = lv_matnr_raw
IMPORTING
output = ev_matnr.
ev_msgtype = 'S'.
ev_message = |编码生成成功:{ ev_matnr }|.
ENDFUNCTION.
五、批量导入工具开发
5.1 方案选型:LSMW vs CATT vs 自定义程序
| 方案 | 适用场景 | 优点 | 缺点 | 推荐度 |
|---|---|---|---|---|
| LSMW | 标准事务批量操作 | 配置化、无代码、内置批处理 | 不够灵活、复杂校验难实现 | ★★★☆☆ |
| CATT/eCATT | 标准事务回放、回归测试 | 支持录制、回放 | 性能差、数据量有限 | ★★☆☆☆ |
| 自定义导入程序 | 复杂校验、批量创建、带日志 | 灵活、完整控制、性能好 | 需要开发 | ★★★★★ |
| BAPI 批量导入 | 大批量、高性能 | 性能好、支持 BAPI 扩展 | 需要理解 BAPI 参数 | ★★★★★ |
推荐:自定义导入程序 + BAPI_MATERIAL_SAVEDATA
5.2 LSMW 配置简要步骤
LSMW 创建物料批量导入:
Step1: 维护对象属性 → 对象 Z_MATNR_IMPORT / 子对象 MM01
Step2: 维护源结构 → 定义源字段(MATNR/WERKS/MATKL/MEINS/...)
Step3: 维护目标结构 → BAPI_MATERIAL_SAVEDATA 接口结构
Step4: 维护字段映射 → 源字段 → 目标字段
Step5: 规格说明 → 指定 BAPI:BAPI_MATERIAL_SAVEDATA
Step6: 批量导入程序 → 生成标准程序
Step7: 读取数据 → 转换 → 执行 → 长文本
5.3 自定义批量导入程序(完整代码,已修正 BAPI 参数)
⚠️ BAPI_MATERIAL_SAVEDATA 正确参数:
HEADDATA、CLIENTDATA、CLIENTDATAX、PLANTDATA、PLANTDATAX、VALUATIONDATA、VALUATIONDATAX都是 IMPORTING 参数(结构)MATERIALDESCRIPTION、RETURN是 TABLES 参数- 注意:每个 DATA 结构都需要对应的 X 结构来标记"哪些字段需要更新"
abap
"=======================================================================
" 自定义批量导入程序:ZMM_MATERIAL_IMPORT
" 功能:从CSV文件导入物料主数据,调用BAPI_MATERIAL_SAVEDATA
" 特点:带完整日志、错误回写、事务控制、支持测试模式
"=======================================================================
REPORT zmm_material_import.
"-----------------------------------------------------------------------
" 选择屏幕
"-----------------------------------------------------------------------
SELECTION-SCREEN BEGIN OF BLOCK b1 WITH FRAME TITLE text-001.
PARAMETERS: p_file TYPE rlgrap-filename OBLIGATORY, " 上传文件路径
p_test AS CHECKBOX, " 测试运行
p_limit TYPE i DEFAULT 0. " 限量(0=全部)
SELECTION-SCREEN END OF BLOCK b1.
SELECTION-SCREEN BEGIN OF BLOCK b2 WITH FRAME TITLE text-002.
PARAMETERS: p_werks TYPE werks_d. " 默认工厂
SELECTION-SCREEN END OF BLOCK b2.
"-----------------------------------------------------------------------
" 类型定义
"-----------------------------------------------------------------------
TYPES: BEGIN OF ty_source,
matnr TYPE matnr, " 物料号(空则自动生成)
matkl TYPE matkl, " 物料组
mtart TYPE mtart, " 物料类型
meins TYPE meins, " 基本单位
werks TYPE werks_d, " 工厂
brgew TYPE brgew, " 重量
biskey_de TYPE char40, " 描述(德)
biskey_en TYPE char40, " 描述(英)
old_matnr TYPE char18, " 旧物料号
END OF ty_source.
TYPES: BEGIN OF ty_log,
guid TYPE sysuuid_c,
lineno TYPE numc5,
matnr TYPE matnr,
status TYPE char1, " S=成功 / E=错误 / W=警告
message TYPE char255,
create_ts TYPE timestampl,
END OF ty_log.
DATA: gt_source TYPE TABLE OF ty_source,
gt_log TYPE TABLE OF ty_log,
gv_count TYPE i VALUE 0,
gv_error TYPE i VALUE 0,
gv_success TYPE i VALUE 0.
"-----------------------------------------------------------------------
" 主逻辑
"-----------------------------------------------------------------------
START-OF-SELECTION.
" 步骤1:读取CSV文件
PERFORM upload_file USING p_file CHANGING gt_source.
IF gt_source IS INITIAL.
MESSAGE '文件为空,请检查路径和格式' TYPE 'E'.
RETURN.
ENDIF.
" 步骤2:限量截取
IF p_limit > 0 AND p_limit < LINES( gt_source ).
DELETE gt_source FROM p_limit + 1.
ENDIF.
" 步骤3:循环处理
DATA: lv_total TYPE i,
lv_cur TYPE i.
lv_total = LINES( gt_source ).
LOOP AT gt_source INTO DATA(ls_source).
lv_cur = lv_cur + 1.
gv_count = gv_count + 1.
" 进度条
IF lv_cur MOD 5 = 0.
CALL FUNCTION 'SAPGUI_PROGRESS_INDICATOR'
EXPORTING
percentage = ( lv_cur * 100 / lv_total )
text = |处理中 { lv_cur } / { lv_total }|.
ENDIF.
" 调用物料创建子程序
PERFORM create_material
USING ls_source
CHANGING DATA(ls_log).
APPEND ls_log TO gt_log.
IF ls_log-status = 'S'.
gv_success = gv_success + 1.
ELSE.
gv_error = gv_error + 1.
ENDIF.
ENDLOOP.
" 步骤4:输出结果
PERFORM output_result.
PERFORM display_log.
"=======================================================================
" 子程序:读取CSV文件
"=======================================================================
FORM upload_file
USING iv_file TYPE rlgrap-filename
CHANGING ct_data TYPE STANDARD TABLE.
" 使用 GUI_UPLOAD 读取 CSV
DATA: lt_raw TYPE TABLE OF string.
CALL FUNCTION 'GUI_UPLOAD'
EXPORTING
filename = iv_file
filetype = 'ASC'
TABLES
data_tab = lt_raw
EXCEPTIONS
OTHERS = 1.
IF sy-subrc <> 0.
MESSAGE '文件读取失败' TYPE 'E'.
RETURN.
ENDIF.
" 跳过首行标题,从第二行开始
LOOP AT lt_raw INTO DATA(lv_line) FROM 2.
SPLIT lv_line AT ',' INTO
DATA(lv_matnr) DATA(lv_matkl) DATA(lv_mtart)
DATA(lv_meins) DATA(lv_werks) DATA(lv_brgew)
DATA(lv_biskey_de) DATA(lv_biskey_en) DATA(lv_old_matnr).
APPEND VALUE #(
matnr = lv_matnr
matkl = lv_matkl
mtart = lv_mtart
meins = lv_meins
werks = COND #( WHEN lv_werks IS INITIAL THEN p_werks ELSE lv_werks )
brgew = lv_brgew
biskey_de = lv_biskey_de
biskey_en = lv_biskey_en
old_matnr = lv_old_matnr
) TO ct_data.
ENDLOOP.
ENDFORM.
"=======================================================================
" 子程序:创建物料(调用 BAPI_MATERIAL_SAVEDATA)
"=======================================================================
FORM create_material
USING iv_source TYPE ty_source
CHANGING cv_log TYPE ty_log.
DATA: lv_matnr TYPE matnr,
lv_error TYPE flag VALUE space,
lt_return TYPE TABLE OF bapiret2.
cv_log-guid = cl_system_uuid=>create_uuid_c32_static( ).
cv_log-lineno = gv_count.
cv_log-create_ts = zcl_helper=>get_timestamp( ).
"-----------------------------------------------------------------------
" 步骤1:确定物料号(自动生成或使用传入值)
"-----------------------------------------------------------------------
IF iv_source-matnr IS INITIAL.
CALL FUNCTION 'Z_MATNR_GENERATE'
EXPORTING
iv_matkl = iv_source-matkl
IMPORTING
ev_matnr = lv_matnr
ev_msgtype = DATA(lv_msgtype)
ev_message = DATA(lv_msg).
IF lv_msgtype <> 'S'.
cv_log-status = 'E'.
cv_log-message = |编码生成失败:{ lv_msg }|.
RETURN.
ENDIF.
ELSE.
" 传入物料号需要转换为内部格式
CALL FUNCTION 'CONVERSION_EXIT_MATN1_INPUT'
EXPORTING input = iv_source-matnr
IMPORTING output = lv_matnr.
ENDIF.
cv_log-matnr = lv_matnr.
"-----------------------------------------------------------------------
" 步骤2:准备 BAPI 参数
"-----------------------------------------------------------------------
" HEADDATA(物料抬头)
DATA: ls_head TYPE bapimathead.
ls_head-material = lv_matnr.
ls_head-industrysector = 'M'. " 行业:机械工程
ls_head-materialtype = iv_source-mtart.
ls_head-basic_view = 'X'. " 创建基本视图
" CLIENTDATA(客户端级数据)
DATA: ls_clientdata TYPE bapimatdoa,
ls_clientdatax TYPE bapimatdob.
ls_clientdata-matl_desc = iv_source-biskey_en.
ls_clientdata-base_uom = iv_source-meins.
ls_clientdata-gross_wt = iv_source-brgew.
ls_clientdatax-matl_desc = 'X'.
ls_clientdatax-base_uom = 'X'.
ls_clientdatax-gross_wt = 'X'.
" PLANTDATA(工厂级数据)
DATA: ls_plantdata TYPE bapimatdoa,
ls_plantdatax TYPE bapimatdob.
ls_plantdata-plant = iv_source-werks.
ls_plantdata-pur_group = '001'. " 采购组
ls_plantdata-mrp_type = 'PD'. " MRP类型
ls_plantdatax-plant = 'X'.
ls_plantdatax-pur_group = 'X'.
ls_plantdatax-mrp_type = 'X'.
" MATERIALDESCRIPTION(物料描述,多语言)
DATA: lt_text TYPE TABLE OF bapi_makt.
IF iv_source-biskey_en IS NOT INITIAL.
APPEND VALUE #(
material = lv_matnr
langu = 'EN'
maktx = iv_source-biskey_en
) TO lt_text.
ENDIF.
IF iv_source-biskey_de IS NOT INITIAL.
APPEND VALUE #(
material = lv_matnr
langu = 'DE'
maktx = iv_source-biskey_de
) TO lt_text.
ENDIF.
"-----------------------------------------------------------------------
" 步骤3:调用 BAPI 创建物料
"-----------------------------------------------------------------------
CALL FUNCTION 'BAPI_MATERIAL_SAVEDATA'
EXPORTING
headdata = ls_head
clientdata = ls_clientdata
clientdatax = ls_clientdatax
plantdata = ls_plantdata
plantdatax = ls_plantdatax
TABLES
materialdescription = lt_text
return = lt_return.
"-----------------------------------------------------------------------
" 步骤4:检查返回消息
"-----------------------------------------------------------------------
LOOP AT lt_return INTO DATA(ls_return) WHERE type CA 'EA'.
lv_error = 'X'.
cv_log-status = 'E'.
cv_log-message = ls_return-message.
EXIT.
ENDLOOP.
IF lv_error = space.
cv_log-status = 'S'.
cv_log-message = |物料 { lv_matnr } 创建成功|.
ENDIF.
"-----------------------------------------------------------------------
" 步骤5:COMMIT / ROLLBACK
"-----------------------------------------------------------------------
IF lv_error = 'X' OR p_test = 'X'.
CALL FUNCTION 'BAPI_TRANSACTION_ROLLBACK'.
IF p_test = 'X' AND lv_error = space.
cv_log-message = |【测试模式】{ cv_log-message }(已回滚)|.
ENDIF.
ELSE.
CALL FUNCTION 'BAPI_TRANSACTION_COMMIT'
EXPORTING wait = 'X'.
ENDIF.
ENDFORM.
"=======================================================================
" 子程序:输出结果概览
"=======================================================================
FORM output_result.
WRITE: / '=================================================='.
WRITE: / |物料批量导入完成|.
WRITE: / '=================================================='.
WRITE: / |总处理: { gv_count } 条|.
WRITE: / |成功: { gv_success } 条|.
WRITE: / |错误: { gv_error } 条|.
WRITE: / |测试模式:{ COND #( WHEN p_test = 'X' THEN '是' ELSE '否' ) }|.
WRITE: / '=================================================='.
IF p_test = 'X'.
WRITE: / |注意:测试模式下数据已全部回滚,未保存到数据库|.
ENDIF.
ENDFORM.
"=======================================================================
" 子程序:ALV 显示日志
"=======================================================================
FORM display_log.
" 只显示有错误的记录
IF gv_error = 0.
RETURN.
ENDIF.
TRY.
cl_salv_table=>factory(
IMPORTING r_salv_table = DATA(lo_alv)
CHANGING t_table = gt_log ).
lo_alv->get_columns( )->set_optimize( 'X' ).
lo_alv->get_columns( )->get_column( 'STATUS' )->set_medium_text( '状态' ).
lo_alv->get_columns( )->get_column( 'MESSAGE' )->set_medium_text( '消息' ).
lo_alv->display( ).
CATCH cx_salv_msg INTO DATA(lx_msg).
MESSAGE lx_msg TYPE 'E'.
ENDTRY.
ENDFORM.
5.4 导入 CSV 文件格式说明
CSV 文件格式(首行为标题,从第二行开始是数据):
MATNR,MATKL,MTART,MEINS,WERKS,BRGEW,BISKEY_DE,BISKEY_EN,OLD_MATNR
,01-01,FERT,PC,1000,5.0,Schraube M10,Metal screw M10,OLD001
,01-02,FERT,PC,1000,3.2,Mutter M6,Nut M6,OLD002
RM03000003,03-01,HALB,PC,2000,12.0,Welle,Shaft,OLD003
,02-01,VERP,PC,1000,1.5,Verpackung,Packaging,
说明:
- 第一列 MATNR 可为空 → 系统自动调用 Z_MATNR_GENERATE 生成编码
- 工厂 WERKS 必填
- 物料类型 MTART 必填(FERT=成品 / HALB=半成品 / ROH=原材料 / VERP=包装)
常见问题与排查
-
Q1:BADI_MATERIAL_CHECK 在 MM01 创建物料时不触发?
A:排查方向:①SE19 检查 BAdI 实现是否激活;②实现的过滤器是否排除了当前物料类型/工厂;③MM01 操作后是否点击了保存 (CHECK_DATA 在保存时才触发);④实现类的方法名是否为
IF_EX_BADI_MATERIAL_CHECK~CHECK_DATA(接口只有一个方法);⑤在实现类开头加日志确认是否执行。 -
Q2:自定义批量导入程序中,BAPI_MATERIAL_SAVEDATA 调用成功了但物料没创建?
A:检查是否正确调用了
BAPI_TRANSACTION_COMMIT。BAPI 调用成功只是返回,不会自动 COMMIT 。批量导入时每条处理完都要 COMMIT(带WAIT = 'X'),否则事务回滚数据会丢失。另外检查测试模式标志是否误开。 -
Q3:CSV 批量导入时,中文描述显示乱码?
A:CSV 文件编码问题。
GUI_UPLOAD默认读取 ASC 编码。建议:①CSV 文件保存为 UTF-8 或 ANSI 编码;②如果 UTF-8 带 BOM,GUI_UPLOAD可能会有乱码,建议另存为 ANSI;③使用GUI_UPLOAD的codepage参数指定编码(如'8400'表示 UTF-8)。 -
Q4:BADI_MATERIAL_CHECK 的 is_mara / is_marc 结构里看不到自定义字段?
A:能看到------CI_MARA/CI_MARC 追加结构中的字段会自动包含在
IM_MARA/IM_MARC结构中。但编译期访问时,如果字段是新追加的,可能提示"字段不存在",需要通过ASSIGN COMPONENT '字段名' OF STRUCTURE im_mara TO <fs>动态访问。 -
Q5:批量导入失败了部分记录,怎么重试?
A:日志表
gt_log中记录了每条的状态和错误信息。重试流程:①筛选状态'E'的记录;②从原 CSV 中提取对应行;③单独重跑。建议扩展程序支持"失败重跑"模式,增加参数P_RETRY_FILE和相关处理逻辑。 -
Q6:物料编码自动生成函数和批量导入配合使用时,并发会重复吗?
A:
Z_MATNR_GENERATE函数中用了SELECT ... FOR UPDATE锁行 + 事务提交,能保证并发安全------两个请求同时来,一个拿到锁,另一个等,第一个提交后第二个读+1。但必须在同一个 SAP 系统中使用------多应用服务器同时并发时,FOR UPDATE 的数据库级锁同样有效。 -
Q7:BADI_MATERIAL_CHECK 和用户出口 EXIT_SAPLMGMU_001 选哪个?
A:选
BADI_MATERIAL_CHECK。用户出口是老式的过程式出口(只有一个 INCLUDE 可写代码,不支持多实现),BAdI 是面向对象的(支持多实现、过滤器、不同方法分职责)。新开发用 BAdI,老项目维护时可能需要同时关注两者。
总结
物料主数据增强开发全流程
| 步骤 | 操作 | 事务码 | 核心产出 |
|---|---|---|---|
| 1. 字段扩展 | CI_MARA + CI_MARC 追加结构 | SE11 | 基本数据/工厂数据自定义字段 |
| 2. BAdI 定位 | 搜索 BADI_MATERIAL_CHECK | SE18/SE19 | 找到 BAdI 定义 |
| 3. 配置表 | 编码规则 ZT_MATNR_RULE + 黑名单 | SE11 | 业务规则配置 |
| 4. BAdI 实现 | 四个校验维度(编码/描述/旧料号/批次) | SE24 | 完整校验逻辑 |
| 5. 编码函数 | Z_MATNR_GENERATE | SE37 | 自动编码生成 |
| 6. 批量导入 | 自定义程序 ZMM_MATERIAL_IMPORT | SE38 | CSV → BAPI 批量创建 |
| 7. 测试 | MM01 手动 + 批量导入测试 | MM01/SE38 | 确认增强生效 |
关键代码避坑速查
| 坑 | 错误做法 | 正确做法 |
|---|---|---|
| BAdI 方法名 | IF_EX_BADI_MATERIAL_CHECK~CHECK |
IF_EX_BADI_MATERIAL_CHECK~CHECK_DATA |
| 活动类型值 | IF im_activity = '01' |
IF im_activity = 'H'(H=创建/V=修改/A=显示) |
| BAPI 参数 | HEADDATA 放在 TABLES |
HEADDATA 是 IMPORTING 结构 |
| X 结构 | 只传 DATA 不传 X | 每个 DATA 必须对应 X 结构标记更新字段 |
| 自定义字段访问 | im_mara-zold_matnr |
ASSIGN COMPONENT 'ZOLD_MATNR' OF STRUCTURE im_mara |
| BAPI 提交 | 不调 COMMIT | 每条处理后 BAPI_TRANSACTION_COMMIT + WAIT='X' |
本篇与下一篇的衔接
| 本篇(MM 物料主数据) | 下一篇(MM 采购申请) |
|---|---|
| 物料主数据字段扩展+校验 | 采购申请自定义校验+自动转单 |
| BADI_MATERIAL_CHECK BAdI | ME_PROCESS_REQ_CUST BAdI |
| CSV 批量导入程序 | 采购申请与 SRM 接口集成 |
下一篇预告:《MM 模块采购申请与审批流开发:自定义校验、自动转单与需求集成实现》------物料主数据增强你会了,MM 模块的下一个高频场景是采购申请。下一篇讲解采购申请增强校验、自动转采购订单逻辑、采购申请与 SRM 系统接口对接、审批流增强配置的完整方案,结合"部门采购需求自动分配采购组、闲置库存不足自动触发采购申请"等典型场景给出实操代码。
作者 :爱喝水的鱼丶
版本记录:2026 年 9 月
💬 你做过物料主数据增强吗?批量导入时踩过哪些坑?欢迎分享你的物料主数据治理经验。