ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

SAP物料主数据BAPI客制化字段EXTENSIONIN落库实战

SAP物料主数据BAPI客制化字段EXTENSIONIN落库实战 客户提的需求特别朴素物料主数据上再加两个字段一个记品牌一个记产地MM01 里手工能填批量导入的时候也得能填。手工那条线半天就搞完了SE11 建个追加结构、屏幕字段自动就出来了批量那条线我照着网上的示例写了EXTENSIONIN程序跑完返回的消息全是绿灯去 MARA 里一查两个字段全是空的——一条警告都没有。这不是段子是我第一次用BAPI_MATERIAL_SAVEDATA写 BAPI 客制化字段的真实经历。问题不在 BAPI也不在权限而在于EXTENSIONIN这个参数的设计逻辑和大多数人想的不一样它给的不是字段名 值的键值对而是一段没有分隔符的定长字符串BAPI 内部靠数据字典里的字段清单去切。你切错一个字节后面全串位你少补一个空格结果就静默丢失。顺着这篇我把从建字段、拼字符串、跑 BAPI、到上线后排错这一整条链路拆开讲。适合已经会写 ABAP、但第一次碰物料主数据客制化字段落库的人也适合被字段不落库折磨过几轮、想搞清楚底层规则的老手。1. 想抄示例代码就踩坑先得看懂 BAPI_MATERIAL_SAVEDATA 的参数分层1.1 头、视图、扩展三层参数各管什么BAPI_MATERIAL_SAVEDATA的参数不是随便堆的它按数据落在哪张表分层设计。物料主数据在 ECC/S4 里是一堆表的集合MARA 存客户端层MARC 存工厂层MARD 存库存地点层MVKE 存销售视图MBEW 存评估视图。BAPI 的参数也是这么切的。参数类型对应表/作用HEADDATABAPIMATHEAD物料号、物料类型、行业领域、要处理哪些视图的开关CLIENTDATABAPI_MARA客户端层的标准字段比如基本计量单位、物料组CLIENTDATAXBAPI_MARAX上面那些字段哪些需要更新字符 X 打标PLANTDATABAPI_MARC工厂层标准字段比如 MRP 控制者、采购类型PLANTDATAXBAPI_MARCX工厂层字段的更新标志EXTENSIONINBAPIPAREX 内表客制化字段的值EXTENSIONINXBAPIPAREX 内表客制化字段的更新标志这张表看明白很多事情就顺了。比如你要改的是工厂层的客制化字段光填 EXTENSIONIN 不够PLANTDATAX里也得把对应开关打开否则 BAPI 根本不去处理工厂层这一块。1.2 客制化字段为什么塞不进 BAPI_MARABAPI_MARA是 SAP 生成的固定结构字段清单是死的。BAPI 内部把 CLIENTDATA 往 MARA 上搬的时候走的是生成好的映射代码就是那些MAP2I_BAPI_MARA之类的例行程序它只认识结构里声明的那些字段。你在 MARA 上追加了 ZZBRANDBAPI_MARA里没有这个组件映射代码自然当它不存在。所以客制化字段唯一能走的通道就是 EXTENSIONIN。SAP 给这个通道的设计思路是通用容器 运行期解析你告诉它一个 DDIC 结构名它去数据字典里读这个结构的字段清单名称、类型、长度、顺序然后按偏移量把字符串切开回填到内部对应的表字段上。1.3 那个 4×240 的容器到底长什么样BAPIPAREX这个结构本身非常简单 BAPIPAREX 的近似结构标准结构不要改 STRUCTURE CHAR 30 —— 扩展结构名 VALUEPART1 CHAR 240 —— 数据段 1 VALUEPART2 CHAR 240 —— 数据段 2 VALUEPART3 CHAR 240 —— 数据段 3 VALUEPART4 CHAR 240 —— 数据段 4四个 240 拼起来一共 960 个字符这是你能装客制化字段的全部空间。超过就得拆成多条但同一个结构名在 EXTENSIONIN 里传两次后一条大概率会覆盖前一条所以字段总长度控制在 960 以内是最省心的做法。还有一个细节STRUCTURE 只有 30 位。你的追加结构名如果超过 30 个字符连传都传不进去。2. 建字段这一步就埋着雷MARA 追加结构和 BAPI_TE_MARA 得成对维护2.1 追加结构怎么建字段怎么定在 SE11 里进 MARA用附加结构Append Structure新建一个名字建议统一ZA或ZZ前缀加上用途比如ZAMARA_PROD。字段定义上有几条经验优先用字符型。CHAR 拼接最不容易出错数字、金额、数量这些类型在拼接前都得转成字符转换规则一错就是静默丢数。如果业务上只是存个编码就老老实实 CHAR。长度留一点余量。品牌字段现在 10 个字符够明年客户要写全称了就是 30。追加结构改字段长度是动的表结构改动成本远比一次性定义长一点高。别和标准字段撞名。追加结构的字段名在 MARA 命名空间里必须唯一报错提示一般很直白但撞名之后你改名字、改代码、重跑测试一来一回就是半天。命名避免用ZZ开头后面直接跟标准字段名比如ZZMATNR容易在代码检索的时候误伤也容易被后续做增强的同事误以为是标准字段。字段定义完、激活MM01 的屏幕会自动多出这些字段这是追加结构比自建 Z 表最爽的地方不用写任何增强。2.2 为什么 BAPI_TE_MARA 也要加一遍这一步是绝大多数人第一次做会漏掉的。BAPI 要解析 VALUEPART得先知道你那些字段长什么样、排在哪个位置。它读的就是 DDIC 里的结构定义。所以你需要把字段名、类型、长度、顺序完全一致的字段追加到BAPI_TE_MARA上客户端层如果还要控制更新标志就再追加到BAPI_TE_MARAX。这里有个容易翻车的点追加结构在 SE11 里一次只能归属一个表或结构所以 MARA 一份、BAPI_TE_MARA 一份是两个独立的追加结构。改一处忘一处是常规操作我见过改完 MARA 直接上线的结果 BAPI 那边字段清单对不上值全串到了后面的位置上。建议写一个几十行的小报表把两个结构的组件清单名称、长度拉出来对比一遍上线前跑一次比人眼核对靠谱得多。这种检查报表可能十分钟就能写完但能省掉一次生产事故。2.3 顺序是硬约束中间插字段等于埋雷追加结构里的字段顺序就是 VALUEPART 里的拼接顺序。如果你后来在中间插入一个新字段之前所有已经写好的接口程序拼接逻辑全部错位——不是报错是错位值会跑到别的字段上去这种数据污染比程序崩溃可怕得多。我的做法是把接口里要用的客制化字段一次性排在追加结构的最前面业务上以后可能加的字段全部排在后面并且在新字段加进来之前先在接口侧留好位置占位。另外拼接逻辑一定要封装成通用的、靠 DDIC 元数据驱动的函数不要每个程序里手写一遍字符串拼接。顺便说一个选型上的取舍很多人纠结客制化属性到底该放 MARA 追加结构还是自建 Z 表方案优点代价追加结构挂 MARAMM01/MM02 自动出字段BAPI 走 EXTENSIONIN 就能写标准报表 SELECT 直接取动标准表结构每次升级/打补丁要检查字段名全局唯一不能存多值自建 Z 表不动标准表结构灵活能存多值、多行标准事务不认要自建维护视图BAPI 得自己二次开发标准查询取不到只存单值的描述性属性比如品牌、产地、原厂型号我的倾向是直接挂 MARA省事。涉及到一对多的关系比如一个物料多个替代供应商编码别硬塞用 Z 表。3. VALUEPART 的拼接规则90% 的值串位都出在这里3.1 顺序、长度、补齐三个条件必须同时成立拼接规则说白了就一句话按结构里字段的先后顺序把每个字段的值补齐到它自己的长度然后首尾相连中间不加任何分隔符。举一个具体的例子。追加结构里有三个字段顺序字段名类型长度1ZZBRANDCHAR102ZZORIGINCHAR203ZZMODELCHAR25值分别是NOKIA、CHINA、X20。那么 VALUEPART1 的内容应该是NOKIA CHINA X20 ↑ 右补5个空格 ↑ 右补15个空格 ↑ 右补22个空格拼完之后 55 个字符第 56 位到第 240 位全是空格。ABAP 里用字符串模板的 WIDTH 选项最省事DATA: lv_brand TYPE zzbrand, lv_origin TYPE zzorigin, lv_model TYPE zzmodel, lv_vp TYPE bapiparex-valuepart1. lv_vp |{ lv_brand WIDTH 10 }{ lv_origin WIDTH 20 }{ lv_model WIDTH 25 }|.注意WIDTH 是最小宽度值超长的时候不会被截断而是原样输出。所以拼之前一定要自己做长度校验超长就报错退出千万别让 BAPI 拿到一个超长的字符串——那样后面所有字段都得往后挪写进去的就是一坨垃圾数据。3.2 单字段场景的假成功陷阱如果你的追加结构里只有一个字段拼接基本上不用做直接把值赋给 VALUEPART1 也能成功。原因很简单BAPI 从第 1 位开始切切到字段长度为止多出来的空格它不关心。这个巧合坑过很多人。开发阶段只测了一个字段觉得这不是挺简单的嘛等业务说到再加一个字段的时候代码直接就不对了而且表现是值写进去了但内容不对不是报错。所以哪怕只有一个字段我也建议一开始就按定长拼接的规范来写别给自己挖坑。3.3 数值、日期、数量字段的字符化处理一旦字段类型不是 CHAR拼接前必须先转成字符而且转换规则要精确对齐 DDIC 里的定义。日期字段DATS直接用 WRITE 转出来就是YYYYMMDD八位和 DDIC 里的存储格式一致直接拼就行。要注意的是别从内表里拿一个已经是2024.01.15这种展示格式的字符串那是给自己找麻烦。数量、金额字段QUAN/CURR/DEC麻烦点在十进制位。DDIC 里定义的是 13 位整数 3 位小数你传进去的字符串就必须是固定的 16 位符号位另算小数位数少一位切出来的偏移量就全乱了。DATA: lv_qty_char TYPE c LENGTH 30, lv_qty TYPE zzqty. 假设是 QUAN 13,3 WRITE lv_qty TO lv_qty_char NO-GROUPING DECIMALS 3. CONDENSE lv_qty_char NO-GAPS. 去掉千分位分组留下的空格几个关键点NO-GROUPING必须加否则数值会被加上千分位分隔符长度直接变DECIMALS的数字必须和字段定义里的小数位完全一致负数要特别注意负号会占掉一个位置如果字段定义时没考虑符号位就会把前面一个字段的最后一位顶掉。我踩过最离谱的一次是数量字段用了DECIMALS 2字段定义是 3 位小数结果写入的数量整体缩小了十倍——因为切出来的字符串被当成整数部分处理了。这种错误不报任何消息只能靠业务对账发现。3.4 先打印偏移量再动笔写代码排查拼接问题最有效的办法不是加断点是先把手写的拼接逻辑和 DDIC 的实际情况对一遍。写个小程序把结构的组件清单和累计偏移打出来DATA: lo_str TYPE REF TO cl_abap_structdescr, lv_off TYPE i, lv_len TYPE i. lo_str ? cl_abap_structdescrdescribe_by_name( ZAMARA_PROD ). WRITE: / 字段名, 30 长度, 40 起始偏移, 55 结束偏移. LOOP AT lo_str-components INTO DATA(ls_comp). lv_off lv_len. lv_len lv_len ls_comp-length. WRITE: / ls_comp-name, 30 ls_comp-length, 40 lv_off, 55 lv_len. ENDLOOP.输出会长成这样字段名 长度 起始偏移 结束偏移 ZZBRAND 10 0 10 ZZORIGIN 20 10 30 ZZMODEL 25 30 55拿着这张表你手写的拼接结果逐位核对一遍比在 BAPI 内部打十个断点都快。我现在的习惯是凡是新增或调整客制化字段第一件事就是跑一遍这个把偏移量截图贴到变更单里后面谁再改字段先看这张图。4. 把 BAPI 真正跑通新建、修改、工厂层三种场景4.1 新建物料的调用骨架新建相对简单把视图开关和必填字段准备好加上扩展就行。DATA: ls_head TYPE bapimathead, ls_client TYPE bapi_mara, ls_clix TYPE bapi_marax, lt_extin TYPE STANDARD TABLE OF bapiparex, lt_ret TYPE STANDARD TABLE OF bapiret2, ls_extin TYPE bapiparex. 物料号先用转换例程补前导零这是最常见的低级错误之一 CALL FUNCTION CONVERSION_EXIT_MATN1_INPUT EXPORTING input lv_matnr_ext IMPORTING output ls_head-material. ls_head-matl_type FERT. ls_head-ind_sector M. ls_head-basic_view X. 要处理基本视图 ls_client-base_uom PC. ls_clix-base_uom X. ls_client-matl_group 001. ls_clix-matl_group X. 客制化字段 ls_extin-structure BAPI_TE_MARA. ls_extin-valuepart1 |{ lv_brand WIDTH 10 }{ lv_origin WIDTH 20 }{ lv_model WIDTH 25 }|. APPEND ls_extin TO lt_extin. CALL FUNCTION BAPI_MATERIAL_SAVEDATA EXPORTING headdata ls_head clientdata ls_client clientdatax ls_clix TABLES extensionin lt_extin return lt_ret. 检查返回消息 LOOP AT lt_ret TRANSPORTING NO FIELDS WHERE type CA EA. EXIT. ENDLOOP. IF sy-subrc 0. CALL FUNCTION BAPI_TRANSACTION_ROLLBACK. ELSE. CALL FUNCTION BAPI_TRANSACTION_COMMIT EXPORTING wait X. ENDIF.两个细节值得单独说。物料号用CONVERSION_EXIT_MATN1_INPUT补前导零这个几乎所有第一次写物料 BAPI 的人都漏过——传进去的物料号格式不对返回的消息往往还是空的感觉很玄学。另一个是 COMMIT 的WAIT X不加这个参数COMMIT 是异步的你紧接着去 SELECT MARA 可能查不到数据测试的时候会误判成没写进去。还有内部编号的场景如果 HEADDATA-MATERIAL 留空让系统自动取号BAPI 不一定会把生成的新物料号回传给你。稳妥的做法是先用编号范围函数把号取出来再以外部编号的方式传进去这样程序自己就知道建了哪个物料后续打日志、写对照表都方便。4.2 修改场景EXTENSIONINX 的 X 标志千万别省这是第二个高频坑。物料已经存在你要改客制化字段的值只传 EXTENSIONIN 不传 EXTENSIONINXBAPI 有可能直接忽略这个字段——不报错就是不改。因为 BAPI 的逻辑和 BAPI_MARAX 是一样的它需要你明确告诉它这个字段我要更新。DATA: lt_extinx TYPE STANDARD TABLE OF bapiparex, ls_extinx TYPE bapiparex. ls_extinx-structure BAPI_TE_MARAX. ls_extinx-valuepart1 |{ X WIDTH 10 }{ X WIDTH 20 }|. 只改前两个字段 APPEND ls_extinx TO lt_extinx.拼接规则和值结构完全一样按字段顺序排按长度补齐。如果你的 X 结构里字段定义成了 CHAR(1)那就直接拼XX如果照搬了原字段长度就得X后面补空格到原长度。这里没有统一标准取决于你建 X 结构时怎么定的做之前先在测试机确认一遍。注意如果同一个物料在修改时既有标准字段又有客制化字段EXTENSIONIN 里只需要放客制化字段不要混着标准字段一起传容易把不相关的字段覆盖掉。4.3 工厂层客制化字段要走 BAPI_TE_MARC如果客制化字段是挂在 MARC 上的比如该工厂的专用标签处理方式一样但有三处必须同步调整追加结构建在 MARC 和BAPI_TE_MARC上EXTENSIONIN 里的STRUCTURE填BAPI_TE_MARC调用时还要填PLANTDATA比如工厂号和PLANTDATAX并且保证PLANTDATAX-PLANT打成 X否则工厂层整个不处理。这一点很容易被忽略工厂层字段一次只能针对一个工厂传值。如果你要同时给三个工厂写同一个客制化值得分三次调用每次一个工厂。别想着在 EXTENSIONIN 里传三组值那个位置是按结构字段切分的不是按行切分的硬凑的结果就是三个工厂拿到同一份错位的数据。4.4 顺手说一句采购订单那边的同类做法同样的思路在采购订单类 BAPI 上也能看到价格这类标准字段走 POITEM 里的标准字段客制化字段依旧是 EXTENSIONIN 那一套结构挂在 EKPO 对应的扩展结构上。区别在于采购订单的价格背后还挂着条件类型改净价的时候如果只改了字段值、没有同步处理条件记录后续收货和发票校验出来的金额会跟你预期对不上报错还特别晚。所以这类字段值背后有业务逻辑的场景我的做法是先确认标准事务里改这个字段会触发哪些连带动作MM02 里改一个字段底下可能同时更新好几张表然后照着来而不是只盯着一两个字段。5. 程序跑通了别急着上线后面这几关才是真正花时间的5.1 字段没落库按这个顺序查我给一个自己常用的排查顺序基本能覆盖 95% 的情况先确认字段本身存在。SE16 或者 SE11 看 MARA 里有没有这个字段没有就是追加结构没激活。再确认 BAPI 侧的字段清单。跑一下第 3.4 节那个偏移量报表对比 BAPI_TE_MARA 和 MARA 两边字段的名称、长度、顺序。两边对不上先修结构。打印拼接结果逐字节核对。把 VALUEPART1 用WRITE出来量一下第一个字段后面到底有几个空格。检查 EXTENSIONINX。修改场景没有 X 标志字段不会更新。检查 COMMIT。返回表里一条 E 都没有但数据没变八成是压根没 COMMIT或者 COMMIT 之后被异常分支 ROLLBACK 了。检查权限。BAPI 内部会做物料主数据的权限检查用批处理用户跑的时候授权对象没配全表现是消息里飘一条看起来无关紧要的提示然后什么都没保存。这六步走完还是不行才考虑打调试。而且我在 SE37 里单跑 BAPI 的时候习惯先把 EXTENSIONIN 用一个很小的值测通比如单个字段、值就是ABC确认链路是通的再往上加复杂度。一次性把二十个字段塞进去调试那是自找麻烦。5.2 批量场景的性能和锁BAPI_MATERIAL_SAVEDATA底层走的逻辑和 MM01/MM02 差不多单条调用几百毫秒很正常。三千条物料老老实实循环调跑到天亮都不稀奇。几个能落地的做法分批 COMMIT。每 500 到 1000 条 COMMIT 一次而不是一条一 COMMIT也不是全部跑完才 COMMIT 一次。前者慢后者一旦中间出错整批回滚。用后台作业。前台跑大批量超时和会话断开都是隐患。注意物料锁。BAPI 会给物料加锁如果同时还有别的程序在改同一批物料会撞锁报错。看到锁相关的返回消息先别急着重试先把并发降下来。真的上几十万条别用这个 BAPI 硬扛评估一下批量导入工具或者主数据平台侧的能力把活交给更合适的通道。5.3 升级、IDoc 分发、变更留痕三件上线后才会想起来的事。升级追加结构动的是标准表 MARA每次打补丁或者升级都要检查一遍字段还在不在、屏幕还在不在。SAP 没有义务通知你这件事。我习惯在升级检查清单里固定加上一条。IDoc 分发如果这个系统还开了 ALE客制化字段不会自动跟着 IDoc 走需要在对应的段类型里加上字段否则分发到下游系统就是丢字段。这个坑特别隐蔽因为本系统看起来一切正常。变更留痕客制化字段默认不进变更凭证谁在什么时候把这个值从 A 改成了 B标准事务里查不到。如果业务上有审计要求得自己写日志表在 BAPI 调用前后打点或者在字段上做额外的变更记录配置。这件事一定要在需求评审阶段就提出来等业务来查历史的时候再补数据已经丢了。5.4 什么情况下该停下来重新选方案做久了会发现有些场景其实不该硬上 EXTENSIONIN客制化字段超过十个、总长度接近 960 字符拼接逻辑会变得非常脆弱任何一次字段顺序调整都是全量回归。这时候该考虑把这些属性挪到自建表只保留少数几个真正需要标准事务维护的字段在 MARA 上。字段需要多值、多语言、带有效期MARA 的单值追加结构根本表达不了硬塞的结果是后来加一堆 Z 表去补还不如一开始就想清楚。产线已经上了主数据治理平台客制化字段的扩展应该走平台自己的扩展模型而不是继续在 ABAP 里手拼字符串。这条路虽然前期投入大但长期维护成本低得多。我个人的经验是客制化字段这件事前期花在建结构和封装拼接函数上的时间和后期省下来的排错时间完全成正比。前几次做的时候图快每个程序里手写一遍字符串拼接等到第三个程序要改字段顺序的时候你就知道什么叫还债了。现在我的做法是固定一个通用的拼接工具输入结构名和字段值内表输出 BAPIPAREX 内表所有接口统一调用它字段顺序变了只需要改 DDIC 和这个函数里的映射关系业务代码一行都不用动。
返回列表