ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SAP采购订单行项目增强:用BADI ME_GUI_PO_CUST实现自定义字段全指南

SAP采购订单行项目增强:用BADI ME_GUI_PO_CUST实现自定义字段全指南 做SAP MM顾问或者ABAP开发应该都遇到过这种需求业务要你在采购订单的行项目上加点东西比如质检备注、交期类型、内部审批标识、预算控制码……标准界面没有这个字段EKPO表里也放不下打印和报表还都得带上。这时候大多数人的第一反应是去SMOD里翻出口或者干脆想直接改标准程序。我的建议是别绕远路先看看BADI ME_GUI_PO_CUST——这是SAP给采购订单界面扩展提供的官方接口也是我给行项目加自定义字段时用得最顺手、坑也最清楚的一个增强点。这篇文章不打算讲那种新建一张Z表、加个附加结构、完事的简化版思路。我会把从数据字典设计、BADI实现类创建、自定义子屏幕开发到保存落库、值回显、断点调试的完整链路走一遍并且把我在项目里踩过的坑一并列出来。适合正在做MM增强、想给ME21N/ME22N/ME23N加行项目字段的ABAP开发也适合刚接手类似需求、被业务催得很急的顾问参考。1. 为什么是ME_GUI_PO_CUST行项目增强的主流方案怎么选1.1 业务场景采购订单行项目加自定义字段的典型需求先看一个最常见的真实场景。采购部门说有些物料是加急采购需要在采购订单行项目上标一个交期类型好让仓库收货时按优先级处理。又比如质检部门要求供应商来料必须带一个质检备注生产异常时方便追溯到批次。这些字段本质上是业务属性不是SAP标准表里已有的结构必须靠增强补进去。这类需求有几个共同点字段挂在行项目上而不是抬头需要在创建和修改采购订单时能录入后续在ME23N查看时要能回显最好还能在打印输出、报表、接口传输时被读取到。这些要求决定了我们选择的增强方案不能只是有地方存数还得把数据显示到界面上并且要能在标准业务流中稳定传递。如果只是把字段加到EKPO的附加结构里但界面上不显示业务录不进去那等于没做。反过来如果只用子屏幕显示但没落到能和其他单据联动的表结构后续报表取数又会很麻烦。所以一个完整的增强方案一定是表结构 界面展示 数据处理三者一起做的。1.2 三种实现方式对比为什么BADI是更稳的路我曾经见过有人直接在标准程序里加代码把ME21N的屏幕和逻辑改掉最后项目升级的时候一片狼藉所有自定义代码全部冲突光返工就花了两周。更常见的是用隐式增强Enhancement Spot / Enhancement Section在标准Form里插一段逻辑这种方法在某些场景下能用但做界面字段扩展时没有稳定的出口而且很容易和后面讲的BADI功能重叠。我整理了一个简单的对比表方便你快速判断实现方式优点缺点风险BADI ME_GUI_PO_CUST官方标准出口界面和逻辑分离升级兼容性好需要开发子屏幕方法较多有一定学习成本低隐式增强代码插入位置灵活不用建子屏幕要找对增强点某些场景无合适出口代码分散难维护中直接改标准程序想改哪改哪最直接升级必冲突代码审计不过严重违规极高选了BADI之后为什么偏偏是ME_GUI_PO_CUST因为SAP在ME21N、ME22N、ME23N这套采购订单处理逻辑里专门留了针对行项目item的增强实现。该BADI能让你读取抬头和行项目数据、返回自定义子屏幕、在保存处理过程中介入逻辑、写入自定义存储覆盖了行项目自定义字段从显示到落库的全部环节。1.3 ME_GUI_PO_CUST的工作机制先搞懂它在哪个环节触发这个BADI挂在采购订单的GUI处理过程里。简单说当用户打开ME21N、ME22N、ME23N或者程序内部触发行项目加载、保存时SAP会在对应节点调用BADI实现类的方法。几个关键方法要搞清楚GET_SUBSCREEN返回一个子屏幕号SAP会把该子屏幕嵌进行项目明细区域形成一个新的页签。这是你展示自定义字段的入口。GET_DATA在界面显示行项目数据时触发适合从自定义表读取值然后传给子屏幕显示。PROCESS_ITEM在保存前/处理过程中触发适合做校验、赋值、修改行项目数据等逻辑。SAVE_TO_DB在保存阶段触发此时订单号已经生成适合把自定义字段真正写库。LOAD_FROM_DB从数据库加载自定义数据时触发和GET_DATA有点类似具体以你的SAP版本接口为准。如果你是第一次用这个BADI最容易犯的错是把所有逻辑都堆在PROCESS_ITEM里结果新建订单时发现采购订单号还是空的存进去一堆空数据。这个问题我们在后面的避坑章节会详细说。2. 开工前的数据设计表、附加结构与字段规划2.1 自定义表怎么建别把字段全塞进EKPO先明确一个原则界面要显示、业务要录入的字段不一定非要物理存在于EKPO里。虽然给EKPO加附加结构很方便但后续遇到表字段膨胀、标准接口传输、数据迁移时附加结构维护成本会变高。我的习惯是在行项目扩展字段不多几个到十几个、且不需要高频SQL关联查询的情况下单独建一张Z表存储扩展数据主键为MANDT EBELN EBELP。这样原表结构干净自定义数据集中管理排查问题也方便。假设我们要加两个字段质检备注ZZQLT_NOTECHAR50交期类型ZZDLV_TYPECHAR2比如01常规、02紧急、03加急建表ZPO_ITEM_EXT字段如下字段数据元素类型/长度说明MANDTMANDTCLNT 3客户端EBELNEBELNCHAR 10采购订单号EBELPEBELPNUMC 5行项目号ZZQLT_NOTEZZ_QLT_NOTECHAR 50质检备注ZZDLV_TYPEZZ_DLV_TYPECHAR 2交期类型ERNAMERNAMCHAR 12创建人ERDATERDATDATS 8创建日期ERZETERZETTIMS 6创建时间这里有两个设计要点。第一主键不要用GUID直接用订单号行号因为后续查询和调试都直观而且业务上本来就是按订单行项目做唯一标识。第二表里加上ERNAM、ERDAT、ERZET这类审计字段虽然会增加几个字段但上线后追溯数据从哪来、谁改的会省很多扯皮时间。2.2 给EKPO加附加结构显示与取数的关键既然自定义数据放在Z表里那为什么还要给EKPO加附加结构因为这关系到屏幕字段怎么和行项目数据绑定。实际开发中你可以有两个选择第一种给EKPO加一个附加结构ZEKPO_ITEM_EXT里面放ZZQLT_NOTE和ZZDLV_TYPE。这样在ME21N后台的行项目内表中这些字段天然存在后续做打印增强、报表查询、接口映射时能直接引用EKPO-ZZQLT_NOTE不用到处JOIN Z表。第二种完全不加附加结构只在子屏幕和Z表之间传值。这种方案简单但后续其他增强点想读取这些字段时没有统一出口代码里得反复从Z表查麻烦。我的做法是两者结合EKPO附加结构加字段保证标准结构里能带上这些业务属性实际落库仍然写在Z表里。这样标准逻辑在读行项目数据时不会丢字段自定义数据又有独立存储两者通过EBELNEBELP关联。附加结构建好之后在SE11里查看EKPO能在附加结构页签里看到它。激活时SAP会提示将对象纳入传输请求记得放进开发机的请求里否则后续传输容易漏。2.3 单页字段和自定义页字段的区别你得知道自己做的是哪种如果你在网上搜采购订单增强会看到单页字段和自定义页字段Custom Tab两种说法。这里统一解释一下。单页字段指的是把自定义字段直接加到标准项目明细的某个已有页签里比如放到数量页签旁边紧挨着标准字段显示。这种做法往往需要在标准屏幕上做Subscreen替换或者通过BADI去动态修改屏幕属性实现难度较高而且屏幕布局在不同SAP版本里差异很大维护成本高。自定义页字段则是通过ME_GUI_PO_CUST的GET_SUBSCREEN方法返回一个全新的子屏幕SAP会自动把它显示成行项目明细区域的一个新页签。比如页签名叫附加数据里面放我们的质检备注和交期类型。这个方案的优点是子屏幕完全由自己控制布局、字段、格式随便调不碰标准屏幕升级兼容性好。本篇文章主方案就是自定义页字段方式。这也是我多次比较后的结论——一旦你掌握了这个套路以后再遇到加字段需求只需要换一套字段、换一张Z表逻辑完全通用。3. 完整实操从建对象到测试3.1 创建数据字典对象先在SE11里创建两个数据元素。第一个是ZZ_QLT_NOTE域ZZ_QLT_NOTECHAR50第二个是ZZ_DLV_TYPE域ZZ_DLV_TYPECHAR2。数据元素和域的建议命名都带ZZ或YY前缀这是SAP社区普遍认可的自定义命名规范能有效避免和标准对象冲突。然后建表ZPO_ITEM_EXT按2.1节的字段定义创建主键MANDT、EBELN、EBELP。最后创建附加结构ZEKPO_ITEM_EXT把ZZQLT_NOTE和ZZDLV_TYPE两个字段放进去附加到EKPO表上。这里有两个注意点。第一给EKPO加附加结构时字段名一定要和数据元素一致别在结构里重新定义类型否则后面子屏幕字段绑定会出问题。第二激活顺序有讲究先激活数据元素和域再激活表最后激活附加结构。如果你先激活附加结构系统会因为找不到域报错。创建表的时候如果项目里启用了数据归档建议不要给这张扩展表设置保留期限除非你已经明确知道归档策略。不然以后查历史采购订单的自定义字段归档一开取数会变得很绕。3.2 创建BADI实现类事务码SE19输入增强点名称ME_GUI_PO_CUST点创建系统会生成一个实现类命名建议用ZCL_IM_PO_ITEM_EXT这种风格。SE19里创建完成后进入实现类能看到接口方法清单。其中GET_SUBSCREEN、GET_DATA、PROCESS_ITEM、SAVE_TO_DB、LOAD_FROM_DB是我们要重点处理的。如果你的SAP版本方法清单里有PUT_DATA或其他方法也没有关系按需实现即可不需要实现的方法保留为空就行。这里有个容易被新手忽略的操作BADI实现类创建后必须去SE19的实现属性里检查激活状态。很多时候代码写完了但BADI实现没激活或者激活了却没有勾选多个使用标志导致程序运行时根本不会调你的类。我会在避坑章节再强调一次这里先记住写完激活激活完再看BADI属性。3.3 创建子屏幕程序子屏幕程序命名ZR_PO_ITEM_EXT用SE80创建程序然后在程序下创建屏幕0100。屏幕布局比较简单放两个文本字段和两个输入框。屏幕字段建议定义成ZZ_QLT_NOTE和ZZ_DLV_TYPE也就是和附加结构字段同名同类型这样逻辑上更统一。注意子屏幕的屏幕字段是在DYNPRO的属性选项卡里手动添加的不是自动映射EKPO字段。你可以定义一个屏幕字段ZZ_QLT_NOTE类型CHAR50输入框属性勾上输入和输出在程序属性里把字段绑定到ABAP程序的同名变量上。屏幕流逻辑PBO和PAI如下PROCESS BEFORE OUTPUT. MODULE status_0100. PROCESS AFTER INPUT. MODULE user_command_0100.PBO模块里写取值回显逻辑PAI模块里写保存前传值逻辑具体代码在3.4节和BADI方法配合展示。另外一个细节子屏幕程序的GUI标题也可以设置成附加数据这样页签显示时有个可读的标题。如果标题没生效大部分时候是子屏幕程序根本没有被正确加载或者屏幕号返回错误。3.4 BADI方法实现从读取、显示到保存这是整个增强的核心。先看GET_SUBSCREEN方法它很简单返回子屏幕号即可METHOD if_ex_me_gui_po_cust~get_subscreen. re_subscr 0100. ENDMETHOD.这里返回的0100就是子屏幕程序ZR_PO_ITEM_EXT里的屏幕号。SAP会把该子屏幕嵌入到行项目明细区域显示成新页签。然后是GET_DATA方法。它的作用是当SAP打开或切换行项目时从Z表读取数据并传给子屏幕。这里有一个关键经验BADI方法本身拿不到DYNPRO屏幕字段的引用所以常规做法是通过ABAP内存EXPORT TO MEMORY / IMPORT FROM MEMORY在BADI实现类和子屏幕程序之间传值。METHOD if_ex_me_gui_po_cust~get_data. DATA: ls_item_ext TYPE zpo_item_ext. CHECK im_item IS NOT INITIAL. SELECT SINGLE * FROM zpo_item_ext INTO ls_item_ext WHERE ebeln im_item-ebeln AND ebelp im_item-ebelp. IF sy-subrc 0. EXPORT zzqlt_note ls_item_ext-zzqlt_note zzdlv_type ls_item_ext-zzdlv_type TO MEMORY ID ZPO_ITEM_EXT. ELSE. EXPORT zzqlt_note zzdlv_type TO MEMORY ID ZPO_ITEM_EXT. ENDIF. ENDMETHOD.子屏幕PBO模块从ABAP内存取回值并赋值给屏幕字段MODULE status_0100 OUTPUT. DATA: lv_zzqlt_note TYPE zpo_item_ext-zzqlt_note, lv_zzdlv_type TYPE zpo_item_ext-zzdlv_type. IMPORT zzqlt_note lv_zzqlt_note zzdlv_type lv_zzdlv_type FROM MEMORY ID ZPO_ITEM_EXT. zz_qlt_note lv_zzqlt_note. zz_dlv_type lv_zzdlv_type. ENDMODULE.接下来是用户录入后的传值。在PAI里屏幕字段被输入后马上EXPORT到同一个内存ID这样后续保存逻辑能拿到用户输入的值MODULE user_command_0100 INPUT. EXPORT zzqlt_note zz_qlt_note zzdlv_type zz_dlv_type TO MEMORY ID ZPO_ITEM_EXT. ENDMODULE.注意这个PAI模块在用户点击保存、回车、切换行时都会被调用。我建议不要在这里写数据落库逻辑只做内存赋值真正的写库放到BADI的SAVE_TO_DB方法里职责更清晰。最后是保存落库。我这里强烈建议用SAVE_TO_DB方法而不是PROCESS_ITEM原因就是新建采购订单时PROCESS_ITEM触发阶段采购订单号可能还没生成写Z表会得到空主键。而SAVE_TO_DB在保存过程中触发订单号已经分配完成。METHOD if_ex_me_gui_po_cust~save_to_db. DATA: ls_item_ext TYPE zpo_item_ext. CHECK im_item IS NOT INITIAL. IMPORT zzqlt_note ls_item_ext-zzqlt_note zzdlv_type ls_item_ext-zzdlv_type FROM MEMORY ID ZPO_ITEM_EXT. ls_item_ext-mandt sy-mandt. ls_item_ext-ebeln im_item-ebeln. ls_item_ext-ebelp im_item-ebelp. ls_item_ext-ernam sy-uname. ls_item_ext-erdat sy-datum. ls_item_ext-erzet sy-uzeit. MODIFY zpo_item_ext FROM ls_item_ext. IF sy-subrc 0. MESSAGE 自定义字段保存失败请检查ZPO_ITEM_EXT表 TYPE E. ENDIF. ENDMETHOD.如果你的SAP版本里SAVE_TO_DB方法没有IM_ITEM参数那就在GET_SUBSCREEN或者GET_DATA阶段把当前行项目号存到内存里SAVE_TO_DB再取出来逻辑一样。重点思想是落库时机一定要在订单号生成之后。如果在PROCESS_ITEM里做数据校验比如交期类型必须非空可以用METHOD if_ex_me_gui_po_cust~process_item. DATA: lv_zzdlv_type TYPE zpo_item_ext-zzdlv_type. IMPORT zzdlv_type lv_zzdlv_type FROM MEMORY ID ZPO_ITEM_EXT. IF lv_zzdlv_type IS INITIAL. MESSAGE 交期类型不能为空 TYPE E. ENDIF. ENDMETHOD.这样保存时就会被拦截业务录不完整没法入库。3.5 激活、检查ATC、传输和手工测试所有对象创建完后按以下顺序激活数据元素、域表ZPO_ITEM_EXT附加结构ZEKPO_ITEM_EXT子屏幕程序和屏幕BADI实现类激活完不要急着测先跑一遍ATC事务码SAT或SE80里的ATC检查。ATC能查出很多低级问题比如变量未声明、方法调用签名错误、权限检查缺失。我在项目里要求所有增强代码必须过ATC才能传输这条规矩救过我好几次。然后就是手工测试。进ME21N创建一个采购订单展开行项目明细区域这时应该能看到一个附加数据的页签。在页签里输入质检备注和交期类型保存。用SE16N查ZPO_ITEM_EXT表确认数据已经落库。再回ME23N打开同一张订单切换行项目页签上的字段值应该能被回显出来。这里要特别测一个场景新建订单外部给号/内部给号环境下各测一遍。外部给号时订单号在保存前已经存在PROCESS_ITEM里直接写库可能侥幸成功内部给号时订单号在保存后才生成如果你用的PROCESS_ITEM写库Z表里就会多一堆空主键记录等到下次回显时SELECT SINGLE查不到界面显示空白。这也是我为什么前面一直强调用SAVE_TO_DB。还要注意如果项目用了Fiori的采购订单应用比如Manage Purchase Orders这个BADI主要作用在SAP GUI的ME21N/ME22N/ME23N流程中Fiori界面走的是OData服务字段是否需要额外暴露要评估Fiori侧的扩展方案。别等上线了业务用Fiori录单发现没有这个页签再回来喊你救火。4. 避坑实录常见问题与独家经验4.1 常见问题速查表现象可能原因处理方法BADI实现不生效实现类未激活或同增强点已有其他激活实现SE19检查激活状态查看该BADI已有实现列表必要时在实现属性中处理多个使用子屏幕不显示GET_SUBSCREEN未实现或返回屏幕号错误子屏幕程序未激活在GET_SUBSCREEN里打断点确认返回值激活子屏幕程序保存后Z表无数据用的PROCESS_ITEM写库内部给号时订单号为空内存ID不一致改用SAVE_TO_DB落库统一内存ID界面回显空白GET_DATA未触发或IMPORT内存失败屏幕字段名不一致GET_DATA打断点检查PBO里IMPORT语句和屏幕字段名切换行项目时值串行ABAP内存ID是会话级全局的行切换时未更新内存确保GET_DATA在行切换时重新SELECT并EXPORT或在子屏幕PBO中每次都刷新附加结构激活报错字段在EKPO中已存在或数据元素未激活换字段名先激活数据元素再激活附加结构传输到生产机后页面报短转储传输请求漏了子屏幕程序或表对象激活顺序不对检查请求任务完整性生产机按顺序激活对象4.2 最容易翻车的几个细节第一PROCESS_ITEM里写库的问题。这是我在多个项目里反复踩的坑也是很多刚接触BADI的人最容易犯的错误。新建采购订单内部给号时PROCESS_ITEM触发时EBELN还是空往Z表里写主键就是空字符串留下一堆垃圾数据。等后续ME23N回显时SELECT SINGLE查不到有效的订单号记录界面字段全空白。解决办法有两个首选是SAVE_TO_DB里写库次选是PROCESS_ITEM里先判断EBELN是否为空为空就用内存暂存等后续再补写。我用SAVE_TO_DB之后这个问题再没出现过。第二ABAP内存ID的串值问题。EXPORT TO MEMORY ID是当前会话内全局的意味着用户打开第一行写了备注A切到第二行如果GET_DATA没有重新写入内存子屏幕PBO拿到的还是备注A等于上一行的值串到了下一行。解决方法是确保GET_DATA按当前行项目实时重新SELECT并EXPORT子屏幕每显示一次都从内存取最新值。如果你的代码在性能上很讲究可以用实例属性做短路缓存但一定要在行项目切换时清掉缓存。第三BADI“多个使用”标志。同一个增强点ME_GUI_PO_CUST系统里可能已经被别的开发做过实现。如果你再创建一个实现默认情况下SAP可能只调用第一个实现你的代码就不会执行。解决办法是在SE19的实现清单里检查BADI定义是否允许“多个使用”。如果允许多个实现可以共存如果不允许那就要找项目负责人确认是替代原有的实现还是把逻辑合并进去。这个坑在大型项目里非常常见因为ME_GUI_PO_CUST是老的增强点项目前期可能被其他顾问用过。第四GET_DATA被频繁调用的性能问题。打开一张100行的采购订单GET_DATA可能被触发多次每次触发都去Z表SELECT一次再加上没有索引或表数据量大界面会明显卡顿。我的优化习惯是在GET_DATA里先把需要的字段一次性按订单号读到内表再按当前行项目从内表取数避免逐行SELECT。如果订单行数确实很大还可以在内存ID里缓存一张表切行时只从内表取值。第五屏幕字段和附加结构字段混淆。很多人在子屏幕上定义了字段ZZQLT_NOTE又给EKPO附加结构加了ZZQLT_NOTE以为屏幕会自动绑定到EKPO字段结果怎么都不显示。实际情况是子屏幕DYNPRO的字段是独立的ABAP内存变量跟EKPO表字段没有自动映射关系。你必须在PBO里手动赋值PAI里手动取回。这一点理解了整个增强逻辑就顺了。第六传输遗漏。一个完整的采购订单行项目增强涉及的传输对象很多数据元素、域、表、附加结构、BADI实现、程序、屏幕、甚至可能还有文本元素。漏一个到生产机轻则该功能失效重则激活报错。我建议在传输前用SE03或E070查看器把所有对象整理到一个请求里并在传输说明中写清楚激活顺序。生产机激活顺序如果和开发机不一致经常会出现表存在但字段不存在的怪异报错。4.3 调试方法20分钟摸清触发链路如果增强不生效最快的排查方式不是看代码而是直接打断点。打开SE24选择BADI实现类进入对应方法打断点。然后运行事务码ME21N按SHDB录一段操作脚本完整执行创建、修改、保存的流程。断点命中时重点观察im_item-ebeln、im_item-ebelp是否有值这决定了你能否在主键中引用订单号。GET_SUBSCREEN的re_subscr是否返回0100没返回或返回空页签永远不会出现。内存ID里是否有值IMPORT的时候如果报内存ID不存在说明GET_DATA没执行或者行项目切换逻辑没触发。如果断点在GET_DATA里根本不命中多半是BADI实现没被激活或者没有勾选启用。这时候回到SE19看看你的实现是否处于激活状态以及实现类是否被错误地标记为不活动的。如果你不确定行项目切换时SAP到底调用了哪些方法也可以在SE19里把所有方法都打断点然后重跑一遍就能看到调用顺序一般是打开订单 - GET_DATA - GET_SUBSCREEN切行 - GET_DATA再触发保存 - PROCESS_ITEM - SAVE_TO_DB。摸清这个顺序之后你就知道哪个方法该干什么了。写在最后的一个小建议每次做这种界面增强我都习惯先录一段用户操作脚本把创建、修改、显示、保存的路径完整跑一遍再根据真实轨迹去对照BADI的触发时序。花20分钟做这件事比对着方法文档猜来猜去稳妥得多。ME_GUI_PO_CUST看着方法多其实每类需求只用得上其中一两个你把GET_SUBSCREEN、GET_DATA、SAVE_TO_DB这三件套用熟行项目自定义字段的活基本都能拿下来。如果你后续遇到STO、寄售采购这类特殊采购类型也不用担心这套增强在标准采购订单和框架协议上都适用业务类型不同不会影响BADI的触发。真要说边界反而是Fiori单据和后台批处理场景要单独确认这一点在项目范围评估时提前和业务对齐就好。
返回列表