MM 模块供应商主数据开发:字段扩展、分级管控与同步接口开发
博客简介:聚焦供应商全生命周期管理开发,讲解供应商主数据字段扩展、供应商分级校验逻辑开发、供应商主数据与 MDG 系统同步接口开发、供应商黑名单管控逻辑开发的完整流程,附"供应商资质效期自动校验""供应商信用等级自动赋值"的实操代码,实现供应商数据的标准化与跨系统一致性。
"P2P 流程的另一端------供应商主数据,是采购活动的源头。供应商资质过期、信用评级下降、黑名单管控......这些全靠增强实现。本篇聚焦供应商主数据全生命周期,用字段扩展 → 资质效期校验 → 信用等级自动赋值 → MDG 同步四个场景完整演示。"
本篇聚焦供应商全生命周期管理开发,讲解供应商主数据字段扩展、供应商分级校验逻辑开发、供应商主数据与 MDG 系统同步接口开发、供应商黑名单管控逻辑开发的完整流程,附"供应商资质效期自动校验""供应商信用等级自动赋值"的实操代码。
📖 写在前面
本篇定位
供应商主数据(Vendor Master Data)是 MM 模块的基础------PO 创建、GR 入库、MIRO 发票校验都要引用供应商。供应商数据质量直接影响采购活动的效率和合规性。本篇聚焦三个高频增强场景:①字段扩展与资质效期自动校验 ;②供应商信用等级自动赋值 ;③MDG 主数据治理系统同步接口,所有代码基于 SAP NetWeaver 7.51。
本篇适合谁读
- 需要在 MM 模块做供应商主数据增强的开发者
- 负责供应商资质和信用管理的业务分析师
- 搭建 SAP-MDG 主数据同步架构的架构师
贯穿场景概览
#mermaid-svg-4KvdbBgibRNov3CM{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-4KvdbBgibRNov3CM .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-4KvdbBgibRNov3CM .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-4KvdbBgibRNov3CM .error-icon{fill:#552222;}#mermaid-svg-4KvdbBgibRNov3CM .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-4KvdbBgibRNov3CM .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-4KvdbBgibRNov3CM .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-4KvdbBgibRNov3CM .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-4KvdbBgibRNov3CM .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-4KvdbBgibRNov3CM .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-4KvdbBgibRNov3CM .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-4KvdbBgibRNov3CM .marker{fill:#333333;stroke:#333333;}#mermaid-svg-4KvdbBgibRNov3CM .marker.cross{stroke:#333333;}#mermaid-svg-4KvdbBgibRNov3CM svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-4KvdbBgibRNov3CM p{margin:0;}#mermaid-svg-4KvdbBgibRNov3CM .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-4KvdbBgibRNov3CM .cluster-label text{fill:#333;}#mermaid-svg-4KvdbBgibRNov3CM .cluster-label span{color:#333;}#mermaid-svg-4KvdbBgibRNov3CM .cluster-label span p{background-color:transparent;}#mermaid-svg-4KvdbBgibRNov3CM .label text,#mermaid-svg-4KvdbBgibRNov3CM span{fill:#333;color:#333;}#mermaid-svg-4KvdbBgibRNov3CM .node rect,#mermaid-svg-4KvdbBgibRNov3CM .node circle,#mermaid-svg-4KvdbBgibRNov3CM .node ellipse,#mermaid-svg-4KvdbBgibRNov3CM .node polygon,#mermaid-svg-4KvdbBgibRNov3CM .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-4KvdbBgibRNov3CM .rough-node .label text,#mermaid-svg-4KvdbBgibRNov3CM .node .label text,#mermaid-svg-4KvdbBgibRNov3CM .image-shape .label,#mermaid-svg-4KvdbBgibRNov3CM .icon-shape .label{text-anchor:middle;}#mermaid-svg-4KvdbBgibRNov3CM .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-4KvdbBgibRNov3CM .rough-node .label,#mermaid-svg-4KvdbBgibRNov3CM .node .label,#mermaid-svg-4KvdbBgibRNov3CM .image-shape .label,#mermaid-svg-4KvdbBgibRNov3CM .icon-shape .label{text-align:center;}#mermaid-svg-4KvdbBgibRNov3CM .node.clickable{cursor:pointer;}#mermaid-svg-4KvdbBgibRNov3CM .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-4KvdbBgibRNov3CM .arrowheadPath{fill:#333333;}#mermaid-svg-4KvdbBgibRNov3CM .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-4KvdbBgibRNov3CM .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-4KvdbBgibRNov3CM .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-4KvdbBgibRNov3CM .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-4KvdbBgibRNov3CM .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-4KvdbBgibRNov3CM .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-4KvdbBgibRNov3CM .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-4KvdbBgibRNov3CM .cluster text{fill:#333;}#mermaid-svg-4KvdbBgibRNov3CM .cluster span{color:#333;}#mermaid-svg-4KvdbBgibRNov3CM 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-4KvdbBgibRNov3CM .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-4KvdbBgibRNov3CM rect.text{fill:none;stroke-width:0;}#mermaid-svg-4KvdbBgibRNov3CM .icon-shape,#mermaid-svg-4KvdbBgibRNov3CM .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-4KvdbBgibRNov3CM .icon-shape p,#mermaid-svg-4KvdbBgibRNov3CM .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-4KvdbBgibRNov3CM .icon-shape .label rect,#mermaid-svg-4KvdbBgibRNov3CM .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-4KvdbBgibRNov3CM .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-4KvdbBgibRNov3CM .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-4KvdbBgibRNov3CM :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 供应商主数据增强三大场景
场景1:资质效期自动校验 + 分级管控
场景2:信用等级自动赋值
场景3:MDG 系统同步接口
BADI_BUSINESSPLANT_UPDATE
CI_LFA1 扩展 + 定时 Job
定时 Job + 四维加权算法
信用等级回写 LFA1
ALE/IDoc + RFC 查询接口
SAP ⇄ MDG 双向同步
版本说明:本篇内容基于 SAP NetWeaver 7.51,供应商主数据核心逻辑在 ECC 6.0+ 版本中可用。MDG 同步接口为通用 SAP 主数据集成方案。
一、供应商主数据增强架构
1.1 核心增强点
⚠️ 技术提示 :SAP 供应商主数据在 ECC 中是 LFA1 体系 (供应商主数据),在 S/4HANA 中逐渐迁移到 BUT000 业务伙伴体系 。不同体系下的 BAdI 名称不同,开发前请用
SE18确认当前系统可用对象。
| 增强类型 | 增强名称 | 触发时机 | 适用事务码 | 推荐场景 |
|---|---|---|---|---|
| BAdI | BADI_BUSINESSPLANT | 供应商保存前 | XK01/XK02 | 字段校验、资质效期检查 |
| BAdI | BADI_PARTNER_UPDATE | 供应商数据修改时 | XK01/XK02 | 修改后自动同步 MDG |
| 用户出口 | EXIT_SAPMF02D_001 | 供应商保存前(老方式) | XK01/XK02 | 老项目兼容 |
| 隐式增强 | 函数/方法首尾 | BAPI_BUSINESSPARTNER_CREATE | SE37 | BAdI 没覆盖时的补充 |
1.2 供应商核心表与追加结构
| SAP 表 | 说明 | 追加结构 | 扩展对象 |
|---|---|---|---|
| LFA1 | 供应商一般数据 | CI_LFA1 | 资质、信用、黑名单标记 |
| LFB1 | 公司代码视图 | CI_LFB1 | 财务相关自定义字段 |
| LFBK | 银行数据 | CI_LFBK | 银行自定义字段 |
| LFM1 | 采购组织视图 | CI_LFM1 | 采购相关字段 |
| LFM2 | 采购组织下的供应商主数据 | CI_LFM2 | 采购组织级扩展 |
⚠️ 常见坑 :供应商的 BUKRS(公司代码)字段在 LFB1 (公司代码视图)中,不在 LFA1 中。开发时涉及公司代码的查询,必须通过
LFB1关联。
1.3 关键 BAdI 接口方法
BADI_BUSINESSPLANT(供应商更新 BAdI):
| 方法名 | 功能 | 适用场景 |
|---|---|---|
| BEFORE_UPDATE | 更新前 | 字段校验、资质效期检查 |
| AFTER_UPDATE | 更新后 | 写日志、触发下游通知、同步 MDG |
| CHECK | 保存前校验 | 综合校验、黑名单检查 |
二、供应商主数据字段扩展
2.1 CI_LFA1 资质与信用字段扩展
SE11 → 创建 CI_LFA1(SAP 预留的供应商追加结构容器)
在 CI_LFA1 中定义自定义字段:
┌─────────────────────┬──────────┬──────────┬──────────────────────────────┐
│ 字段名 │ 数据类型 │ 长度 │ 说明 │
├─────────────────────┼──────────┼──────────┼──────────────────────────────┤
│ ZISO9001 │ FLAG │ 1 │ ISO9001 认证标记 │
│ ZISO9001_NO │ CHAR │ 30 │ ISO9001 证书号 │
│ ZISO9001_EXP │ DATS │ 8 │ ISO9001 有效期 │
│ ZISO14001 │ FLAG │ 1 │ ISO14001 认证标记 │
│ ZISO14001_EXP │ DATS │ 8 │ ISO14001 有效期 │
│ ZCCC_FLAG │ FLAG │ 1 │ CCC 认证标记 │
│ ZCCC_NO │ CHAR │ 30 │ CCC 证书号 │
│ ZCCC_EXP │ DATS │ 8 │ CCC 有效期 │
│ ZCREDIT_LEVEL │ CHAR │ 1 │ 信用等级(A/B/C/D) │
│ ZCREDIT_SCORE │ DEC │ 5 │ 信用评分(0-100) │
│ ZCREDIT_UPDATE │ DATS │ 8 │ 上次信用评级日期 │
│ ZBLACKLIST_FLAG │ FLAG │ 1 │ 黑名单标记 │
│ ZBLACKLIST_REASON │ CHAR │ 100 │ 黑名单原因 │
│ ZBLACKLIST_DATE │ DATS │ 8 │ 拉黑日期 │
│ ZBLACKLIST_BY │ CHAR │ 12 │ 拉黑人 │
│ ZUNBLOCK_DATE │ DATS │ 8 │ 解封日期(用于临时黑名单) │
│ ZVENDOR_CATEGORY │ CHAR │ 5 │ 供应商分类(RAW/GEN/SRV) │
│ ZVENDOR_GRADE │ CHAR │ 1 │ 供应商等级(1/2/3/4 级) │
└─────────────────────┴──────────┴──────────┴──────────────────────────────┘
激活 CI_LFA1 → 自动追加到 LFA1 表
2.2 供应商资质配置表
表名:ZT_VENDOR_CERT_REQUIREMENT
MANDT TYPE MANDT " 客户端
MATKL TYPE MATKL " 物料组(*=所有物料)
CERT_TYPE TYPE CHAR15 " 证书类型:ISO9001/ISO14001/CCC
REQUIRED TYPE FLAG " 是否必填
WARN_DAYS TYPE NUMC3 " 提前多少天预警
BLOCK_ON_EXP TYPE FLAG " 过期是否自动屏蔽
ACTIVE TYPE FLAG " 是否激活
示例数据:
┌───────┬───────────┬──────────┬───────────┬────────────┬────────┐
│ MATKL │ CERT_TYPE │ REQUIRED │ WARN_DAYS │ BLOCK_ON_ │ ACTIVE │
│ │ │ │ │ EXP │ │
├───────┼───────────┼──────────┼───────────┼────────────┼────────┤
│ * │ ISO9001 │ X │ 30 │ X │ X │
│ Z001 │ CCC │ X │ 30 │ X │ X │
│ * │ ISO14001 │ │ 60 │ │ X │
└───────┴───────────┴──────────┴───────────┴────────────┴────────┘
三、资质效期自动校验
3.1 BADI_BUSINESSPLANT 实现代码
⚠️ 关键修正:
CI_LFA1追加字段需通过ASSIGN COMPONENT动态访问(编译期可能识别不到新字段)- 供应商的
BUKRS(公司代码)在 LFB1 表中,不在 LFA1- 更新函数内部不要写
COMMIT WORK,由框架统一提交
abap
"=======================================================================
" BAdI 实现:ZIMPL_VENDOR_CERT_CHECK
" BAdI:BADI_BUSINESSPLANT
" 功能:供应商资质效期校验
" 触发:XK01/XK02 供应商保存前
" 创建日期:2026.09.20
"=======================================================================
CLASS cl_impl_vendor_cert_check DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_ex_businesspartner.
PRIVATE SECTION.
METHODS get_custom_field
IMPORTING iv_field_name TYPE string
is_lfa1 TYPE lfa1
RETURNING VALUE(rv_value) TYPE string.
ENDCLASS.
CLASS cl_impl_vendor_cert_check IMPLEMENTATION.
METHOD if_ex_businesspartner~before_update.
DATA: lv_lifnr TYPE lifnr,
lv_iso9001 TYPE flag,
lv_iso9001_exp TYPE datum,
lv_ccc_flag TYPE flag,
lv_ccc_exp TYPE datum,
lv_today TYPE datum.
" 获取供应商号(BAdI 参数名因版本而异,通常为 IS_BUSINESSPARTNER 或 IV_PARTNER)
lv_lifnr = is_lfa1-lifnr.
IF lv_lifnr IS INITIAL.
RETURN.
ENDIF.
lv_today = sy-datum.
" 通过动态 ASSIGN 访问 CI_LFA1 追加字段
lv_iso9001 = me->get_custom_field( iv_field_name = 'ZISO9001' is_lfa1 = is_lfa1 ).
lv_iso9001_exp = me->get_custom_field( iv_field_name = 'ZISO9001_EXP' is_lfa1 = is_lfa1 ).
lv_ccc_flag = me->get_custom_field( iv_field_name = 'ZCCC_FLAG' is_lfa1 = is_lfa1 ).
lv_ccc_exp = me->get_custom_field( iv_field_name = 'ZCCC_EXP' is_lfa1 = is_lfa1 ).
" ================================================================
" 校验1:资质效期已过期 → 警告
" ================================================================
IF lv_iso9001 = 'X' AND lv_iso9001_exp IS NOT INITIAL.
SELECT SINGLE warn_days, block_on_exp
INTO (@DATA(lv_warn_days), @DATA(lv_block_exp))
FROM zt_vendor_cert_requirement
WHERE cert_type = 'ISO9001'
AND active = 'X'.
IF sy-subrc <> 0.
lv_warn_days = 30.
ENDIF.
DATA(lv_warn_date) = lv_today + lv_warn_days.
IF lv_iso9001_exp < lv_today.
MESSAGE w001(zmm_vendor_enh)
WITH lv_lifnr 'ISO9001' lv_iso9001_exp.
ELSEIF lv_iso9001_exp <= lv_warn_date.
MESSAGE w002(zmm_vendor_enh)
WITH lv_lifnr 'ISO9001' lv_iso9001_exp.
ENDIF.
ENDIF.
" CCC 效期检查(同理)
IF lv_ccc_flag = 'X' AND lv_ccc_exp IS NOT INITIAL.
SELECT SINGLE warn_days
INTO @DATA(lv_warn_days2)
FROM zt_vendor_cert_requirement
WHERE cert_type = 'CCC'
AND active = 'X'.
IF sy-subrc <> 0.
lv_warn_days2 = 30.
ENDIF.
DATA(lv_warn_date2) = lv_today + lv_warn_days2.
IF lv_ccc_exp < lv_today.
MESSAGE w001(zmm_vendor_enh) WITH lv_lifnr 'CCC' lv_ccc_exp.
ELSEIF lv_ccc_exp <= lv_warn_date2.
MESSAGE w002(zmm_vendor_enh) WITH lv_lifnr 'CCC' lv_ccc_exp.
ENDIF.
ENDIF.
" ================================================================
" 校验2:黑名单供应商自动解封逻辑
" ================================================================
DATA(lv_blacklist) = me->get_custom_field(
iv_field_name = 'ZBLACKLIST_FLAG'
is_lfa1 = is_lfa1 ).
DATA(lv_unblock_date) = me->get_custom_field(
iv_field_name = 'ZUNBLOCK_DATE'
is_lfa1 = is_lfa1 ).
IF lv_blacklist = 'X'.
" 临时黑名单(设置了解封日期且今天已过解封日)→ 自动解封
IF lv_unblock_date IS NOT INITIAL AND lv_unblock_date <= lv_today.
UPDATE lfa1
SET zblacklist_flag = ''
WHERE lifnr = @lv_lifnr.
MESSAGE i003(zmm_vendor_enh) WITH lv_lifnr.
ELSEIF lv_unblock_date IS INITIAL.
" 永久黑名单 → 阻止保存
MESSAGE e004(zmm_vendor_enh)
WITH lv_lifnr
me->get_custom_field( iv_field_name = 'ZBLACKLIST_REASON'
is_lfa1 = is_lfa1 ).
ENDIF.
ENDIF.
ENDMETHOD.
METHOD if_ex_businesspartner~after_update.
" 保存后写日志 + 触发 MDG 同步通知
PERFORM write_vendor_update_log
USING is_lfa1-lifnr.
ENDMETHOD.
METHOD get_custom_field.
FIELD-SYMBOLS: <fs_value> TYPE any.
ASSIGN COMPONENT iv_field_name OF STRUCTURE is_lfa1 TO <fs_value>.
IF sy-subrc = 0 AND <fs_value> IS ASSIGNED.
rv_value = <fs_value>.
ELSE.
rv_value = ''.
ENDIF.
ENDMETHOD.
ENDCLASS.
消息类配置(SE91):
消息类:ZMM_VENDOR_ENH
001:供应商 &1 证书 &2 已过期(有效期 &3),请尽快续办
002:供应商 &1 证书 &2 30 天内到期(有效期 &3),请及时续办
003:供应商 &1 已自动解封(黑名单到期)
004:供应商 &1 在黑名单中(原因:&2),不允许修改
005:供应商 &1 MDG 同步通知已发送
四、供应商信用等级自动赋值
4.1 信用等级评估算法设计
供应商信用评分算法(0-100 分制):
维度1:履约率(权重 30%)
→ 100% 履约 = 100 分
→ 每低于 1% -3 分,最低 0 分
维度2:付款及时性(权重 25%)
→ 平均付款天数 ≤ 标准天数 = 100 分
→ 每超出 1 天 -2 分,最低 0 分
维度3:质量合格率(权重 25%)
→ 质量问题率 0% = 100 分
→ 每 0.1% 问题 -2.5 分,最低 0 分
维度4:业务规模稳定性(权重 20%)
→ 过去 12 个月有 PO = 100 分
→ 无 PO = 0 分
→ PO 金额趋势稳定 = 80 分
→ PO 金额上升 = 90 分
综合评分 = 维度1×30% + 维度2×25% + 维度3×25% + 维度4×20%
信用等级映射:
A 级:85-100 分 (优质供应商,优先合作)
B 级:70-84 分 (合格供应商,正常合作)
C 级:50-69 分 (待改进供应商,加强监控)
D 级:0-49 分 (风险供应商,限制合作/考虑淘汰)
4.2 信用评估后台 Job 程序
⚠️ 关键修正:
EKKO没有EBELP字段(行项目号在 EKPO),也没有ZTERM作为凭证日期。凭证日期字段是BEDAT。- 供应商的
BUKRS在 LFB1 中,不在 LFA1。SELECT SINGLE ... INTO TABLE语法错误,单行不能用 INTO TABLE。MSEG-BWART退货用102(冲销)/122(退货),拒收用123等,101/105是收货。
abap
"=======================================================================
" 后台 Job 程序:ZMM_VENDOR_CREDIT_SCORE
" 功能:自动计算供应商信用等级 + 回写到 LFA1
" 定时执行:每月 1 号凌晨 3 点
"=======================================================================
REPORT zmm_vendor_credit_score.
SELECTION-SCREEN BEGIN OF BLOCK b1 WITH FRAME TITLE text-001.
PARAMETERS: p_test AS CHECKBOX, " 测试模式
p_bukrs TYPE bukrs OBLIGATORY DEFAULT '1000',
p_lifnr TYPE lifnr, " 单个供应商(空=全部)
p_months TYPE numc2 DEFAULT '12'. " 统计月数
SELECTION-SCREEN END OF BLOCK b1.
TYPES: BEGIN OF ty_vendor_result,
lifnr TYPE lifnr,
bukrs TYPE bukrs,
name1 TYPE name1_gp,
score_pct TYPE dec5_2, " 履约率得分
score_pay TYPE dec5_2, " 付款及时性得分
score_quality TYPE dec5_2, " 质量合格率得分
score_volume TYPE dec5_2, " 业务规模得分
score_total TYPE dec5_2, " 综合得分
credit_level TYPE char1, " 信用等级 A/B/C/D
credit_date TYPE datum, " 评级日期
END OF ty_vendor_result.
DATA: gt_results TYPE TABLE OF ty_vendor_result,
gv_count TYPE i VALUE 0.
START-OF-SELECTION.
PERFORM get_vendor_list USING p_bukrs p_lifnr CHANGING gt_results.
IF gt_results IS INITIAL.
WRITE: / '没有符合条件的供应商'.
STOP.
ENDIF.
" 计算统计起止日期(回溯 p_months 个月)
DATA: lv_from_date TYPE datum,
lv_to_date TYPE datum.
lv_to_date = sy-datum.
lv_from_date = lv_to_date - ( p_months * 30 ).
" 逐供应商计算信用评分
LOOP AT gt_results INTO DATA(ls_result).
gv_count = gv_count + 1.
IF gv_count MOD 10 = 0.
CALL FUNCTION 'SAPGUI_PROGRESS_INDICATOR'
EXPORTING
percentage = ( gv_count * 100 / lines( gt_results ) )
text = |计算中 { gv_count } / { lines( gt_results ) }|.
ENDIF.
" 计算四个维度得分
PERFORM calc_delivery_rate USING ls_result-lifnr lv_from_date lv_to_date
CHANGING ls_result-score_pct.
PERFORM calc_payment_timeliness USING ls_result-lifnr lv_from_date lv_to_date
CHANGING ls_result-score_pay.
PERFORM calc_quality_rate USING ls_result-lifnr lv_from_date lv_to_date
CHANGING ls_result-score_quality.
PERFORM calc_volume_stability USING ls_result-lifnr lv_from_date lv_to_date
CHANGING ls_result-score_volume.
" 加权计算综合分
ls_result-score_total = ls_result-score_pct * '0.30'
+ ls_result-score_pay * '0.25'
+ ls_result-score_quality * '0.25'
+ ls_result-score_volume * '0.20'.
" 映射信用等级
CASE.
WHEN ls_result-score_total >= 85. ls_result-credit_level = 'A'.
WHEN ls_result-score_total >= 70. ls_result-credit_level = 'B'.
WHEN ls_result-score_total >= 50. ls_result-credit_level = 'C'.
WHEN OTHERS. ls_result-credit_level = 'D'.
ENDCASE.
ls_result-credit_date = sy-datum.
MODIFY gt_results FROM ls_result.
ENDLOOP.
" 回写到 LFA1(测试模式不回写)
IF p_test <> 'X'.
LOOP AT gt_results INTO DATA(ls_update).
UPDATE lfa1
SET zcredit_level = @ls_update-credit_level,
zcredit_score = @ls_update-score_total,
zcredit_update = @ls_update-credit_date
WHERE lifnr = @ls_update-lifnr.
ENDLOOP.
COMMIT WORK.
ENDIF.
PERFORM output_summary USING gt_results p_test.
"=======================================================================
" 子程序1:获取供应商列表(修正:BUKRS 在 LFB1 中,不在 LFA1)
"=======================================================================
FORM get_vendor_list
USING iv_bukrs TYPE bukrs
iv_lifnr TYPE lifnr
CHANGING ct_data TYPE TABLE OF ty_vendor_result.
IF iv_lifnr IS NOT INITIAL.
" 单供应商查询:LFA1 JOIN LFB1 获取公司代码
SELECT lfa1~lifnr, lfb1~bukrs, lfa1~name1
INTO TABLE @ct_data
FROM lfa1
INNER JOIN lfb1 ON lfb1~lifnr = lfa1~lifnr
WHERE lfa1~lifnr = @iv_lifnr
AND lfb1~bukrs = @iv_bukrs.
ELSE.
SELECT lfa1~lifnr, lfb1~bukrs, lfa1~name1
INTO TABLE @ct_data
FROM lfa1
INNER JOIN lfb1 ON lfb1~lifnr = lfa1~lifnr
WHERE lfb1~bukrs = @iv_bukrs.
ENDIF.
ENDFORM.
"=======================================================================
" 子程序2:计算履约率(30% 权重)
" 修正:EKKO-BEDAT 才是凭证日期;EBELP 是数字,不能等于 'NEU'
"=======================================================================
FORM calc_delivery_rate
USING iv_lifnr TYPE lifnr
iv_from TYPE datum
iv_to TYPE datum
CHANGING cv_score TYPE dec5_2.
DATA: lv_po_total TYPE i,
lv_po_deliv TYPE i,
lv_pct_rate TYPE dec5_2.
" PO 总数(标准 PO 有 EKPO 行项目)
SELECT COUNT( DISTINCT ekko~ebeln ) INTO @lv_po_total
FROM ekko
INNER JOIN ekpo ON ekpo~ebeln = ekko~ebeln
WHERE ekko~lifnr = @iv_lifnr
AND ekko~bedat BETWEEN @iv_from AND @iv_to.
IF lv_po_total = 0.
cv_score = 0.
RETURN.
ENDIF.
" 有入库记录的 PO 数
SELECT COUNT( DISTINCT ekko~ebeln ) INTO @lv_po_deliv
FROM ekko
INNER JOIN ekpo ON ekpo~ebeln = ekko~ebeln
INNER JOIN mseg ON mseg~ebeln = ekpo~ebeln
AND mseg~ebelp = ekpo~ebelp
WHERE ekko~lifnr = @iv_lifnr
AND ekko~bedat BETWEEN @iv_from AND @iv_to
AND mseg~bwart IN ('101', '105').
lv_pct_rate = lv_po_deliv * 100 / lv_po_total.
" 每低于 1% -3 分
cv_score = lv_pct_rate * 3.
IF cv_score > 100.
cv_score = 100.
ELSEIF cv_score < 0.
cv_score = 0.
ENDIF.
ENDFORM.
"=======================================================================
" 子程序3:付款及时性(25% 权重)
"=======================================================================
FORM calc_payment_timeliness
USING iv_lifnr TYPE lifnr
iv_from TYPE datum
iv_to TYPE datum
CHANGING cv_score TYPE dec5_2.
DATA: lv_avg_days TYPE dec5_2,
lv_total_days TYPE i,
lv_cnt TYPE i,
lv_standard TYPE i VALUE 30.
" 读取付款凭证(清账日期 - 发票日期 = 实际付款天数)
SELECT bkpf~bldat AS invoice_date,
bseg~augdt AS clear_date
INTO TABLE @DATA(lt_pay)
FROM bkpf
INNER JOIN bseg ON bseg~belnr = bkpf~belnr AND bseg~bukrs = bkpf~bukrs
WHERE bseg~lifnr = @iv_lifnr
AND bkpf~bldat BETWEEN @iv_from AND @iv_to
AND bseg~augdt IS NOT NULL.
" 计算平均付款天数
LOOP AT lt_pay INTO DATA(ls_pay).
DATA(lv_diff) = ls_pay-clear_date - ls_pay-invoice_date.
IF lv_diff >= 0.
lv_total_days = lv_total_days + lv_diff.
lv_cnt = lv_cnt + 1.
ENDIF.
ENDLOOP.
IF lv_cnt = 0.
cv_score = 50. " 无付款记录给中等分
RETURN.
ENDIF.
lv_avg_days = lv_total_days / lv_cnt.
IF lv_avg_days <= lv_standard.
cv_score = 100.
ELSE.
cv_score = 100 - ( lv_avg_days - lv_standard ) * 2.
ENDIF.
IF cv_score < 0.
cv_score = 0.
ELSEIF cv_score > 100.
cv_score = 100.
ENDIF.
ENDFORM.
"=======================================================================
" 子程序4:质量合格率(25% 权重)
"=======================================================================
FORM calc_quality_rate
USING iv_lifnr TYPE lifnr
iv_from TYPE datum
iv_to TYPE datum
CHANGING cv_score TYPE dec5_2.
DATA: lv_total_qty TYPE menge_d,
lv_return_qty TYPE menge_d.
" 总入库数量
SELECT SUM( mseg~menge ) INTO @lv_total_qty
FROM mseg
INNER JOIN ekko ON ekko~ebeln = mseg~ebeln
WHERE ekko~lifnr = @iv_lifnr
AND ekko~bedat BETWEEN @iv_from AND @iv_to
AND mseg~bwart IN ('101', '105').
" 退货/拒收数量(102=冲销入库;122=退货;123=拒收)
SELECT SUM( mseg~menge ) INTO @lv_return_qty
FROM mseg
INNER JOIN ekko ON ekko~ebeln = mseg~ebeln
WHERE ekko~lifnr = @iv_lifnr
AND ekko~bedat BETWEEN @iv_from AND @iv_to
AND mseg~bwart IN ('102', '122', '123').
IF lv_total_qty = 0.
cv_score = 50.
RETURN.
ENDIF.
DATA(lv_rej_rate) = lv_return_qty * 100 / lv_total_qty.
" 每 0.1% 问题 -2.5 分
cv_score = 100 - ( lv_rej_rate * 25 ).
IF cv_score < 0.
cv_score = 0.
ELSEIF cv_score > 100.
cv_score = 100.
ENDIF.
ENDFORM.
"=======================================================================
" 子程序5:业务规模稳定性(20% 权重)
"=======================================================================
FORM calc_volume_stability
USING iv_lifnr TYPE lifnr
iv_from TYPE datum
iv_to TYPE datum
CHANGING cv_score TYPE dec5_2.
DATA: lv_po_count TYPE i,
lv_prev_po TYPE i.
" 过去 N 个月的 PO 数量
SELECT COUNT( DISTINCT ekko~ebeln ) INTO @lv_po_count
FROM ekko
INNER JOIN ekpo ON ekpo~ebeln = ekko~ebeln
WHERE ekko~lifnr = @iv_lifnr
AND ekko~bedat BETWEEN @iv_from AND @iv_to.
" 之前 N 个月的 PO 数量(对比趋势)
DATA: lv_prev_from TYPE datum,
lv_prev_to TYPE datum.
lv_prev_from = iv_from - ( p_months * 2 * 30 ).
lv_prev_to = iv_from - 1.
SELECT COUNT( DISTINCT ekko~ebeln ) INTO @lv_prev_po
FROM ekko
INNER JOIN ekpo ON ekpo~ebeln = ekko~ebeln
WHERE ekko~lifnr = @iv_lifnr
AND ekko~bedat BETWEEN @lv_prev_from AND @lv_prev_to.
IF lv_po_count + lv_prev_po = 0.
cv_score = 0.
RETURN.
ENDIF.
IF lv_prev_po = 0.
cv_score = 100. " 新供应商,过去无 PO,现在有 → 满分
ELSEIF lv_po_count >= lv_prev_po.
cv_score = 90. " 数量稳定或上升 → 90 分
ELSE.
DATA(lv_pct_change) = ( lv_prev_po - lv_po_count ) * 100 / lv_prev_po.
cv_score = 80 - lv_pct_change / 5.
ENDIF.
IF cv_score < 0.
cv_score = 0.
ELSEIF cv_score > 100.
cv_score = 100.
ENDIF.
ENDFORM.
"=======================================================================
" 子程序6:输出汇总 + ALV
"=======================================================================
FORM output_summary
USING it_results TYPE TABLE OF ty_vendor_result
iv_test TYPE flag.
DATA: lv_cnt_a TYPE i, lv_cnt_b TYPE i, lv_cnt_c TYPE i, lv_cnt_d TYPE i.
LOOP AT it_results INTO DATA(ls_r).
CASE ls_r-credit_level.
WHEN 'A'. lv_cnt_a = lv_cnt_a + 1.
WHEN 'B'. lv_cnt_b = lv_cnt_b + 1.
WHEN 'C'. lv_cnt_c = lv_cnt_c + 1.
WHEN 'D'. lv_cnt_d = lv_cnt_d + 1.
ENDCASE.
ENDLOOP.
WRITE: / '================================================'.
WRITE: / |供应商信用评估完成|.
WRITE: / '================================================'.
WRITE: / |评估供应商总数:{ lines( it_results ) }|.
WRITE: / |A 级(优秀): { lv_cnt_a }|.
WRITE: / |B 级(合格): { lv_cnt_b }|.
WRITE: / |C 级(待改进):{ lv_cnt_c }|.
WRITE: / |D 级(风险): { lv_cnt_d }|.
WRITE: / '------------------------------------------------'.
IF iv_test = 'X'.
WRITE: / |【测试模式】未回写信用等级|.
ELSE.
WRITE: / |信用等级已回写到 LFA1 表|.
ENDIF.
WRITE: / '================================================'.
" ALV 详细结果
TRY.
cl_salv_table=>factory(
IMPORTING r_salv_table = DATA(lo_alv)
CHANGING t_table = it_results ).
lo_alv->get_columns( )->set_optimize( 'X' ).
lo_alv->display( ).
CATCH cx_salv_msg.
ENDTRY.
ENDFORM.
五、供应商 MDG 同步接口开发
5.1 同步方案选型
| 方式 | 适用场景 | 优点 | 缺点 | 推荐度 |
|---|---|---|---|---|
| ALE / IDoc | 异步单向推送 | 解耦、失败可重发 | 配置复杂、有延迟 | ★★★★★ |
| RFC | 实时请求/响应 | 同步、双向交互 | 耦合高、需要 VPN | ★★★★☆ |
| OData | 跨平台/移动端 | RESTful、无状态 | 需要额外授权 | ★★★☆☆ |
| SOAP/WSDL | MDG 标准集成 | SAP 官方推荐 | 性能一般 | ★★★★☆ |
推荐:ALE/IDoc(主同步)+ RFC(查询接口)组合
5.2 IDoc 出站同步实现
abap
"=======================================================================
" BAdI 实现:ZIMPL_VENDOR_IDOC_OUT
" BAdI:BADI_PARTNER_UPDATE(供应商更新后触发)
" 功能:供应商更新后 → IDoc 推送到 MDG
" 方式:ALE / IDoc(消息类型:CREMAS / ZVENDOR)
"=======================================================================
CLASS cl_impl_vendor_idoc_out IMPLEMENTATION.
METHOD if_ex_partner_update~after_update.
DATA: lv_lifnr TYPE lifnr.
lv_lifnr = iv_businesspartner.
" 步骤1:读取完整的供应商数据
DATA: ls_lfa1 TYPE lfa1.
DATA: lt_lfb1 TYPE TABLE OF lfb1.
DATA: lt_lfbk TYPE TABLE OF lfbk.
SELECT SINGLE * FROM lfa1 INTO @ls_lfa1 WHERE lifnr = @lv_lifnr.
SELECT * FROM lfb1 INTO TABLE @lt_lfb1 WHERE lifnr = @lv_lifnr.
SELECT * FROM lfbk INTO TABLE @lt_lfbk WHERE lifnr = @lv_lifnr.
IF ls_lfa1 IS INITIAL.
RETURN.
ENDIF.
" 步骤2:构造 IDoc 数据记录(自定义结构 ZVENDOR_MASTER)
DATA: lt_idoc_data TYPE TABLE OF zvendor_master,
ls_idoc_data TYPE zvendor_master.
ls_idoc_data-mandt = ls_lfa1-mandt.
ls_idoc_data-lifnr = ls_lfa1-lifnr.
ls_idoc_data-name1 = ls_lfa1-name1.
ls_idoc_data-land1 = ls_lfa1-land1.
ls_idoc_data-ort01 = ls_lfa1-ort01.
ls_idoc_data-pstlz = ls_lfa1-pstlz.
ls_idoc_data-stras = ls_lfa1-stras.
ls_idoc_data-telf1 = ls_lfa1-telf1.
ls_idoc_data-stcd1 = ls_lfa1-stcd1. " 税号
ls_idoc_data-stcd2 = ls_lfa1-stcd2.
ls_idoc_data-zcredit_level = ls_lfa1-zcredit_level.
ls_idoc_data-zblacklist = ls_lfa1-zblacklist_flag.
ls_idoc_data-ziso_exp = ls_lfa1-ziso9001_exp.
ls_idoc_data-laeda = ls_lfa1-laeda.
ls_idoc_data-laeur = ls_lfa1-laeur.
" 公司代码级数据
LOOP AT lt_lfb1 INTO DATA(ls_b1).
ls_idoc_data-bukrs = ls_b1-bukrs.
ls_idoc_data-akont = ls_b1-akont.
ls_idoc_data-zterm = ls_b1-zterm.
APPEND ls_idoc_data TO lt_idoc_data.
ENDLOOP.
" 步骤3:调用标准 ALE IDoc 生成函数
DATA: lv_edidc TYPE edidc.
DATA: lt_edid TYPE TABLE OF edid.
DATA: lt_edidd TYPE TABLE OF edidd.
DATA: ls_edidd TYPE edidd.
" EDIDC(控制记录)
lv_edidc-mandt = sy-mandt.
lv_edidc-rectyp = '2'.
lv_edidc-statem = '0'.
lv_edidc-doctyp = 'ZVENDOR'. " 自定义消息类型
lv_edidc-cimtyp = 'MANDT'.
lv_edidc-rcvp = 'MDGCLNT800'.
lv_edidc-rcvprt = 'RECEIVE'.
lv_edidc-rcvmprt = 'RECEIVE'.
lv_edidc-sndp = 'SAPCLNT800'.
lv_edidc-sndprt = 'SEND'.
lv_edidc-sndmprt = 'SEND'.
lv_edidc-direct = '1'. " 立即发送
" EDIDD(数据记录)------注意:sdata 是引用类型,需通过指针传递
ls_edidd-segnam = 'ZSEG_VENDOR'.
ls_edidd-sdata = CONV zseg_vendor( lt_idoc_data ).
APPEND ls_edidd TO lt_edidd.
" 调用 MASTER_IDOC_DISTRIBUTE
CALL FUNCTION 'MASTER_IDOC_DISTRIBUTE'
EXPORTING
master_idoc_control = lv_edidc
TABLES
communication_idoc_control = lt_edid
master_idoc_data = lt_edidd.
IF sy-subrc = 0.
" 成功 → 写应用日志
DATA(lv_log_handle) = VALUE balloghndl( ).
CALL FUNCTION 'BAL_LOG_CREATE'
EXPORTING
i_s_log = VALUE #( object = 'ZMDG_SYNC'
subobject = 'VENDOR'
extnumber = CONV balnrext( lv_lifnr ) )
IMPORTING
e_log_handle = lv_log_handle.
CALL FUNCTION 'BAL_LOG_MSG_ADD'
EXPORTING
i_log_handle = lv_log_handle
i_s_msg = VALUE #( msgty = 'S'
msgid = 'ZMM_VENDOR_ENH'
msgno = '005'
msgv1 = lv_lifnr ).
CALL FUNCTION 'BAL_LOG_SAVE'
EXPORTING
i_log_handle = lv_log_handle.
COMMIT WORK.
ELSE.
" 失败 → 记录日志,后续重试
PERFORM log_idoc_error USING lv_lifnr lt_edid.
ENDIF.
ENDMETHOD.
ENDCLASS.
5.3 RFC 查询接口
abap
"=======================================================================
" RFC 函数:Z_VENDOR_GET_CREDIT
" 标记:Remote-Enabled(SE37 属性)
" 功能:MDG 查询 SAP 供应商信用等级
"=======================================================================
FUNCTION z_vendor_get_credit.
IMPORTING
iv_lifnr TYPE lifnr
EXPORTING
ev_credit_level TYPE char1
ev_credit_score TYPE dec5_2
ev_msgtype TYPE char1
ev_message TYPE char255
EXCEPTIONS
not_found.
SELECT SINGLE lifnr, zcredit_level, zcredit_score, zcredit_update
INTO @DATA(ls_vendor)
FROM lfa1
WHERE lifnr = @iv_lifnr.
IF sy-subrc <> 0.
ev_msgtype = 'E'.
ev_message = '供应商不存在'.
RAISE not_found.
ENDIF.
ev_credit_level = ls_vendor-zcredit_level.
ev_credit_score = ls_vendor-zcredit_score.
ev_msgtype = 'S'.
ev_message = |供应商 { iv_lifnr } 信用等级 { ls_vendor-zcredit_level }|.
ENDFUNCTION.
六、常见问题与排查
-
Q1:BADI_BUSINESSPLANT 和 EXIT_SAPMF02D_001 选哪个?
A:功能定位不同。
BADI_BUSINESSPLANT是面向对象 BAdI (支持多实现、过滤器),在供应商主数据保存的标准逻辑之后触发;EXIT_SAPMF02D_001是老式过程式出口(只有一个 INCLUDE、不支持多实现),在保存前触发。新开发用 BAdI,老项目兼容可以同时关注两者。 -
Q2:供应商资质效期过期了,要不要 E 消息阻止保存?
A:大多数业务场景不建议硬性阻止 ------影响太大(可能导致紧急采购无法进行)。推荐分级处理:①效期已过 → 警告 W 消息 + 自动拉入临时黑名单;②30 天内到期 → 提示 W 消息;③永久黑名单 → E 消息阻止修改。临时黑名单可以设置解封日期(ZUNBLOCK_DATE),过期自动解封。
-
Q3:信用评估算法的权重怎么选?
A:权重反映业务侧重------采购量大的企业应该履约率权重最高(50%+) ,质量敏感的企业(如医药)质量合格率权重最高 。本篇的 30/25/25/20 是通用权重,建议做成配置表可调整 。另外建议月度评估,季度/年度重新校准权重。
-
Q4:供应商 IDoc 推送到 MDG,失败了怎么重试?
A:ALE/IDoc 有完整的状态管理:①BD87 查看 IDoc 状态(3=成功/2=错误);②BD88 重发失败 IDoc;③如果是自定义 BAdI 触发的推送,建议写一个批量重试 Job (Z_VENDOR_IDOC_RETRY),每天扫描失败记录后重发。重要的是幂等设计------MDG 接收端必须保证重复推送不会产生重复数据。
-
Q5:供应商黑名单的 CI_LFA1 扩展字段和 ZT_VENDOR_BLACKLIST 配置表有什么区别?
A:
CI_LFA1是供应商主数据的一部分 ------LFA1 直接存黑名单标记,查起来方便,随供应商一起同步到 MDG;ZT_VENDOR_BLACKLIST是独立的管控表 ------可以加更多维度(黑名单类型、生效范围、创建人等),还可以做审批流程。推荐两者结合:CI_LFA1 存当前状态,ZT_VENDOR_BLACKLIST 存历史记录和审批信息。 -
Q6:信用等级自动赋值后,怎么在 PO/MIRO 中自动检查?
A:在我们之前开发的
ME_PROCESS_PO_CUST(PO)和EXIT_SAPLMRMH_014(MIRO)增强中,增加对LFA1-ZCREDIT_LEVEL的检查:①PO/MIRO 保存时,根据供应商的信用等级做不同处理;②D 级供应商 → E 消息阻止;③C 级 → W 消息警告 + 强制选择冻结标记;④A/B 级 → 正常。这体现了增强代码的协同复用。
七、总结
供应商主数据增强三大场景速查
| 场景 | 实现方式 | 触发点 | 核心参数 |
|---|---|---|---|
| 资质效期校验 | BADI_BUSINESSPLANT | 保存前 | IS_BUSINESSPARTNER + CI_LFA1 |
| 信用等级自动赋值 | 定时 Job | 每月 | PO/MIRO/MSEG 统计 + 算法评分 |
| MDG 同步 | BADI_PARTNER_UPDATE + IDoc | 保存后 | ZVENDOR 自定义结构 |
关键代码避坑速查
| 坑 | 错误做法 | 正确做法 |
|---|---|---|
| 供应商公司代码 | lfa1~bukrs |
lfb1~bukrs(在 LFB1 表中) |
| 凭证日期字段 | ekko~zterm |
ekko~bedat(ZTERM 是付款条件) |
| 行项目号比较 | ekko~ebelp = 'NEU' |
EBELP 是数字,不是字符;应改用其他条件 |
| 单行查询 | SELECT SINGLE ... INTO TABLE |
SELECT SINGLE ... INTO (单变量) |
| 追加字段访问 | is_lfa1-ziso9001 |
ASSIGN COMPONENT 'ZISO9001' OF STRUCTURE is_lfa1 |
| 更新函数提交 | COMMIT WORK |
不写,让框架统一提交 |
| 退货移动类型 | 只用 102 |
102/122/123 都要考虑 |
MM 模块增强全景(8 篇中的 6 篇已完成)
#mermaid-svg-dlmi0b0U3dTgR0Ht{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-dlmi0b0U3dTgR0Ht .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-dlmi0b0U3dTgR0Ht .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-dlmi0b0U3dTgR0Ht .error-icon{fill:#552222;}#mermaid-svg-dlmi0b0U3dTgR0Ht .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-dlmi0b0U3dTgR0Ht .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-dlmi0b0U3dTgR0Ht .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-dlmi0b0U3dTgR0Ht .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-dlmi0b0U3dTgR0Ht .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-dlmi0b0U3dTgR0Ht .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-dlmi0b0U3dTgR0Ht .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-dlmi0b0U3dTgR0Ht .marker{fill:#333333;stroke:#333333;}#mermaid-svg-dlmi0b0U3dTgR0Ht .marker.cross{stroke:#333333;}#mermaid-svg-dlmi0b0U3dTgR0Ht svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-dlmi0b0U3dTgR0Ht p{margin:0;}#mermaid-svg-dlmi0b0U3dTgR0Ht .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-dlmi0b0U3dTgR0Ht .cluster-label text{fill:#333;}#mermaid-svg-dlmi0b0U3dTgR0Ht .cluster-label span{color:#333;}#mermaid-svg-dlmi0b0U3dTgR0Ht .cluster-label span p{background-color:transparent;}#mermaid-svg-dlmi0b0U3dTgR0Ht .label text,#mermaid-svg-dlmi0b0U3dTgR0Ht span{fill:#333;color:#333;}#mermaid-svg-dlmi0b0U3dTgR0Ht .node rect,#mermaid-svg-dlmi0b0U3dTgR0Ht .node circle,#mermaid-svg-dlmi0b0U3dTgR0Ht .node ellipse,#mermaid-svg-dlmi0b0U3dTgR0Ht .node polygon,#mermaid-svg-dlmi0b0U3dTgR0Ht .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-dlmi0b0U3dTgR0Ht .rough-node .label text,#mermaid-svg-dlmi0b0U3dTgR0Ht .node .label text,#mermaid-svg-dlmi0b0U3dTgR0Ht .image-shape .label,#mermaid-svg-dlmi0b0U3dTgR0Ht .icon-shape .label{text-anchor:middle;}#mermaid-svg-dlmi0b0U3dTgR0Ht .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-dlmi0b0U3dTgR0Ht .rough-node .label,#mermaid-svg-dlmi0b0U3dTgR0Ht .node .label,#mermaid-svg-dlmi0b0U3dTgR0Ht .image-shape .label,#mermaid-svg-dlmi0b0U3dTgR0Ht .icon-shape .label{text-align:center;}#mermaid-svg-dlmi0b0U3dTgR0Ht .node.clickable{cursor:pointer;}#mermaid-svg-dlmi0b0U3dTgR0Ht .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-dlmi0b0U3dTgR0Ht .arrowheadPath{fill:#333333;}#mermaid-svg-dlmi0b0U3dTgR0Ht .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-dlmi0b0U3dTgR0Ht .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-dlmi0b0U3dTgR0Ht .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-dlmi0b0U3dTgR0Ht .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-dlmi0b0U3dTgR0Ht .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-dlmi0b0U3dTgR0Ht .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-dlmi0b0U3dTgR0Ht .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-dlmi0b0U3dTgR0Ht .cluster text{fill:#333;}#mermaid-svg-dlmi0b0U3dTgR0Ht .cluster span{color:#333;}#mermaid-svg-dlmi0b0U3dTgR0Ht 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-dlmi0b0U3dTgR0Ht .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-dlmi0b0U3dTgR0Ht rect.text{fill:none;stroke-width:0;}#mermaid-svg-dlmi0b0U3dTgR0Ht .icon-shape,#mermaid-svg-dlmi0b0U3dTgR0Ht .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-dlmi0b0U3dTgR0Ht .icon-shape p,#mermaid-svg-dlmi0b0U3dTgR0Ht .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-dlmi0b0U3dTgR0Ht .icon-shape .label rect,#mermaid-svg-dlmi0b0U3dTgR0Ht .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-dlmi0b0U3dTgR0Ht .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-dlmi0b0U3dTgR0Ht .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-dlmi0b0U3dTgR0Ht :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} MM 模块增强开发体系
已完成 P2P 全链路(6 篇)
待完成(2 篇)
采购订单增强
ME_PROCESS_PO_CUST
物料主数据增强
BADI_MATERIAL_CHECK
采购申请增强
ME_PROCESS_REQ_CUST
库存管理增强
MIGO_GOODS_MOVEMENT_POSTING
发票校验增强
EXIT_SAPLMRMH_014
供应商主数据增强
BADI_BUSINESSPLANT(本篇)
第七篇:MM 报表开发
第八篇:MM 接口开发
P2P 全链路增强闭环:供应商主数据 → PR(采购申请)→ PO(采购订单)→ GR(入库)→ IR(发票校验),形成完整的采购到付款增强体系。
本篇与下一篇的衔接
| 本篇(MM 供应商主数据) | 下一篇(MM 报表开发) |
|---|---|
| BADI_BUSINESSPLANT | ALV 报表 SALV + CL_SALV_TABLE |
| CI_LFA1 追加结构 | MKPF/MSEG/EKKO/EKPO 报表数据源 |
| 信用等级算法 + IDoc 同步 | 采购执行 + 库龄 + 供应商绩效分析 |
| 主数据治理 | 业务数据洞察 |
下一篇预告:《MM 模块报表开发:采购执行、库存分析与供应商绩效报表实战》------MM 模块的最后一篇专题:报表开发。下一篇讲解采购执行跟踪报表、库存账龄分析报表、供应商交付绩效分析报表、采购价格趋势分析报表的开发逻辑,结合 ALV 展示、数据钻取、批量导出等功能实现,附完整的报表开发代码模板。
作者 :爱喝水的鱼丶
版本记录:2026 年 9 月
💬 你在供应商主数据增强上做过哪些事情?信用等级自动评估你们是用算法算的还是人工评的?供应商资质效期过期你们是警告还是直接屏蔽?欢迎分享你的供应商管理经验。