
SAP SD顾问都躲不掉的一个需求销售订单抬头要加点客户自定义的东西。比如销售组内部的订单备注、客户合同号、质检状态、物流特殊要求……标准表VBAK里没这字段用户又天天催咋办做SD增强绕不开一个经典的BADIBADI_SLS_HEAD_SCR_CUS。这玩意儿专门管VA01/VA02/VA03销售订单抬头屏幕的自定义字段它能在订单抬头页签上追加你的专属子屏幕还能把屏幕字段和自定义存储表打通实现增删改查全流程。这篇文章我就拿一个真实做过的需求来拆——给销售订单抬头加“销售备注”和“审批状态”两个自定义字段从建函数组、画屏幕、写BADI方法到调试坑一步步说清楚代码直接能抄。1. 为什么选BADI_SLS_HEAD_SCR_CUS而不是其他增强接触过SAP增强的人都知道给销售订单加字段的路径有好几条第一代增强USER_EXIT、第二代增强CUSTOMER_FUNCTION、新一代BADI还有隐式增强。很多老项目里还在用MV45AFZZ这种用户出口往VBAK上塞字段但那是上世纪的做法代码写死在标准程序里升级一冲就掉而且多人协同开发容易冲突。BADI_SLS_HEAD_SCR_CUS是官方专门为销售订单抬头屏幕增强预留的BADI它最大的优势在于屏幕增强和逻辑增强分离BADI方法里只处理数据流转屏幕本身通过函数组维护互不干扰。不修改标准代码升级无忧S/4 HANA和ECC都能用。一个BADI可以有多个实施不同业务场景各挂各的实施互不影响。这个BADI挂在销售订单抬头处理的流程里不管是VA01创建、VA02修改还是VA03显示它都会被触发。所以只要做完这一套你加的字段在这三个事务里都能正常工作。1.1 BADI涉及的核心方法和触发时机BADI_SLS_HEAD_SCR_CUS一共有三个方法GET_DATA从内存/数据库读取自定义字段的值填充到屏幕字段上用于进入VA03查看或VA02修改时回显。PUT_DATA屏幕字段值传给自定义存储区域在保存订单前把数据落库。DELETE_DATA删除订单时同步清理自定义数据。实际开发中PUT_DATA和DELETE_DATA用得多GET_DATA主要负责回显。三个方法配合起来才能实现一条完整的自定义数据生命周期。另外要强调一点这个BADI不是直接在屏幕里写逻辑的它只是给你预留了一个“挂载点”。真正要在屏幕上显示出字段必须自己做函数组子屏幕。这也是很多初学者卡壳的地方——BADI激活了、方法也写了但看不清字段原因就是子屏幕压根没挂上去。2. 需求确认和表结构设计动手之前先把业务需求聊透。我之前做过的那个项目是给某制造型企业的销售订单加两个字段一个文本型“内部备注”一个下拉选择“审批状态”。业务方要求备注字段长度200字符只在抬头页签显示审批状态有“未审批”、“已审批”、“已驳回”三个值默认“未审批”两个字段都允许VA02修改VA03只读。这个需求在标准表VBAK里找不到合适字段所以需要自建存储表。2.1 自建存储表设计自定义字段建议单独建表存储不直接往VBAK里加附加字段append structure。原因很简单VBAK是核心单据表在它上面加字段所有读取VBAK的程序都会受影响查询性能和数据传输都有隐患。独立存表可以把影响范围限制在最小的范围内。我建的表结构大概是这样的表名: ZTSD_SALES_NOTE MANDT MANDT CLNT 3 客户端 VBELN VBELN CHAR 10 销售订单号 ZNOTE ZNOTE CHAR 200 内部备注 ZAPPROVE CHAR 1 X 审批状态这里要注意主键只设MANDTVBELN一个订单一条记录。如果业务上需要订单行项目级别的自定义字段那就要加POSNR行号作为主键的一部分但BADI_SLS_HEAD_SCR_CUS是抬头级别的行项目字段要做的话得另找增强点。2.2 数据字典元素设计为了避免散落各处的硬编码我建议为字段建数据元素ZNOTE长度200的字符型用于备注文本。ZAPPROVE长度1的字符型附带域值检查只允许“ ”(未审批)、X已审批、E已驳回三个值。域值检查的好处是用户在VA02里直接敲值系统会自动校验不符合的值根本输不进去。这点比程序里写IF判断要稳得多因为屏幕字段绑定数据元素后值检查发生在PAI之前用户连保存的机会都没有。数据库表建好后记得在SE11里激活然后再去SM30维护数据。这是一个好习惯——先手工录入几条测试数据后面调试时可以直接看效果。3. 函数组和子屏幕的实现过程函数组是整个增强的“门面”因为用户在VA01/VA02屏幕上看到的东西就是从这个函数组的子屏幕里渲染出来的。3.1 创建函数组ZSD_SALES_SCREENSE80里创建函数组命名可以根据项目规范来比如ZSD_SALES_SCREEN、ZSD_SALES_ADDON都行。函数组建好以后在里面需要做几件事定义全局变量用于屏幕和BADI方法之间传递数据创建子屏幕Screen画上自定义字段编写PBO/PAI控制逻辑提供Function Module给BADI方法调用也可以直接在BADI里引用函数组的全局变量但推荐封装成FM规范。函数组里最关键的是全局变量它得和数据库表字段对应。我一般在函数组TOP里定义DATA: gs_ztsd_sales_note TYPE ztsd_sales_note.这个结构体在打开屏幕时被GET_DATA方法填充关闭屏幕时被PUT_DATA方法读取。如果多个BADI实施或者多个子屏幕共用一个函数组变量命名要加前缀区分免得互相覆盖。3.2 画子屏幕布局和属性设置在函数组里新建一个Screen屏幕编号建议用9001或者9002之类的自定义编号。屏幕类型选“子屏幕”这样它才能嵌入到标准订单抬头屏幕里。屏幕布局我画了两行第一行文本标签“内部备注”后面跟一个输入框绑定字段GS_ZTSD_SALES_NOTE-ZNOTE第二行文本标签“审批状态”后面跟一个下拉框绑定字段GS_ZTSD_SALES_NOTE-ZAPPROVE。这里有个容易被忽略的细节子屏幕的大小要控制好。SAP标准订单抬头屏幕的Tab区域高度有限子屏幕画得太长会把下面的标准按钮顶出去导致布局错乱。我的经验是子屏幕高度控制在5行以内字段不要超过4个。如果字段实在多可以用多行Tab页或者把子屏幕做得精炼一点只放关键信息。另外下拉框的值列表要提前维护。在屏幕属性里把ZAPPROVE字段的“值列表”属性设置为“字段值”它就会自动读取域值检查里定义的三个值。不需要写额外的PBO代码去填充下拉选项省不少事。3.3 PBO/PAI逻辑实现子屏幕的PBO和PAI代码不多但每个字符都有用。PBOProcess Before OutputPROCESS BEFORE OUTPUT. MODULE status_9001. MODULE fill_zapprove_desc.其中fill_zapprove_desc模块负责把状态值翻译成可读文本如果下拉框直接绑定字段且启用了域值检查这个模块其实可以不写。但我习惯写上因为显示模式下VA03如果数据是从旧系统迁移过来的可能存在脏数据显示描述会更友好。PAIProcess After InputPROCESS AFTER INPUT. MODULE check_zapprove ON CHAIN-REQUEST. FIELD gs_ztsd_sales_note-zapprove MODULE check_zapprove.check_zapprove模块里可以做业务校验比如MODULE check_zapprove INPUT. IF gs_ztsd_sales_note-zapprove NOT IN (, X, E). MESSAGE 审批状态值不正确 TYPE E. ENDIF. ENDMODULE.注意域值检查已经保证了基本合法性这个模块主要防的是程序内部传进来的非法值。如果业务方还有更复杂的逻辑比如已审批的订单不允许改备注那就得在PAI里加一层判断再结合标准订单的状态一起做。3.4 Function Module封装我在函数组里建了一个FM叫ZSD_SALES_NOTE_GET作用是返回当前订单的自定义数据。BADI的GET_DATA方法调用这个FM获取数据PUT_DATA时则直接用函数组的全局变量GS_ZTSD_SALES_NOTE把值赋给BADI里的导出参数。FM代码很简单FUNCTION zsd_sales_note_get. *---------------------------------------------------------------------- **本地接口 * IMPORTING * VALUE(IV_VBELN) TYPE VBELN * EXPORTING * VALUE(ES_ZTSD_SALES_NOTE) TYPE ZTSD_SALES_NOTE *---------------------------------------------------------------------- SELECT SINGLE * INTO es_ztsd_sales_note FROM ztsd_sales_note WHERE vbeln iv_vbeln. ENDFUNCTION.SELECT SINGLE查不到会返回初始值不会出异常。如果项目性能要求很高可以考虑加缓存但销售订单屏幕打开频率不算极端一次单条查询足以。4. BADI实施与三个方法详解函数组和屏幕就绪后真正穿针引线的是BADI实施。SE19里创建BADI实施输入BADI名称BADI_SLS_HEAD_SCR_CUS给实施起个名字比如ZSD_SLS_HEAD_SCR_CUS。4.1 GET_DATA方法实现GET_DATA的触发时机打开VA03查看订单时或者VA02进入修改模式时系统需要把自定义字段的值从存储表读到屏幕字段上。参数含义IV_VBELN当前销售订单号IV_TRTYP处理类型A创建V修改H显示CS_HEAD用于传递自定义字段数据的结构里面包含一个特殊字段DOCDATA这是BADI专门为自定义数据保留的容器。在GET_DATA里我做了两件事从数据库表读数据把数据塞进CS_HEAD-DOCDATA。METHOD get_data. DATA: ls_ztsd_sales_note TYPE ztsd_sales_note. CLEAR: cs_head-docdata. IF iv_vbeln IS INITIAL. RETURN. ENDIF. CALL FUNCTION ZSD_SALES_NOTE_GET EXPORTING iv_vbeln iv_vbeln IMPORTING es_ztsd_sales_note ls_ztsd_sales_note. MOVE-CORRESPONDING ls_ztsd_sales_note TO cs_head-docdata. ENDMETHOD.这里有个非常关键的细节CS_HEAD-DOCDATA是BADI方法里自定义结构的一个容器字段它本身是BAL_S_CHANGED_DOCUMENT这种标准文档变更结构不是直接塞业务数据的。那业务数据怎么传过去答案是——在BADI实施里定义一个属性Attribute来承接。我一般这样处理在BADI实施类里定义一个属性比如GT_CUSTOM_DATA TYPE TABLE OF ztsd_sales_noteGET_DATA把这个属性填充好在PBO触发时函数组的全局变量从属性取值渲染到屏幕。这样说可能绕了。实际操作中SAP官方文档和社区给出的经典做法是BADI实施类里定义一个公共属性GET_DATA负责往属性里放值函数组的PBO从属性取值PAI时函数组把值写回属性PUT_DATA从属性里取值存库。这个模式我在多个项目里验证过完全可行。4.2 PUT_DATA方法实现PUT_DATA触发的时机订单保存时VA01保存、VA02保存系统把屏幕字段的值传递过来需要在此时把值更新到存储表。METHOD put_data. DATA: ls_ztsd_sales_note TYPE ztsd_sales_note. MOVE-CORRESPONDING cs_head-docdata TO ls_ztsd_sales_note. ls_ztsd_sales_note-vbeln iv_vbeln. MODIFY ztsd_sales_note FROM ls_ztsd_sales_note. IF sy-subrc 0. MESSAGE 自定义字段保存失败 TYPE E. ENDIF. ENDMETHOD.MODIFY语句会自动判断是INSERT还是UPDATE省去先SELECT再MODIFY的麻烦。如果表里没有记录就插入一条有记录就更新。这是最简洁的写法。注意PUT_DATA并不是只在用户点击保存时才触发。在某些操作里比如切换订单类型、复制订单也可能会触发PUT_DATA。所以方法里一定要考虑订单号为空的情况。更进一步如果是复制订单VA01 “复制”功能新建系统会先调用PUT_DATA把旧订单数据写到内存然后通过GET_DATA把数据带过来。此时IV_VBELN可能是新订单号但数据库里还没有这条记录直接MODIFY会INSERT一条空数据可能造成脏数据。我项目里加了一个判断IF iv_vbeln IS INITIAL OR iv_trtyp A. RETURN. ENDIF.TRTYP为A代表创建模式创建模式下数据库本来就没有记录PUT_DATA不应该执行。但这里要注意如果做的是创建时也需要落库自建表那这个判断要调整。不同业务场景逻辑不同关键是要理解方法触发点和业务场景的匹配关系。4.3 DELETE_DATA方法实现DELETE_DATA参数里带的是删除的订单号列表IT_VBELN。删除销售订单VA02里删除整单或者批量删除程序时同步清理自建表数据避免脏数据堆积。METHOD delete_data. CHECK it_vbeln IS NOT INITIAL. DELETE FROM ztsd_sales_note WHERE vbeln IN it_vbeln. ENDMETHOD.这个方法实现起来最轻松但最容易忘。很多项目上线后运维发现自建表越来越大一查全是已删除订单的残留就是因为DELETE_DATA没写或者写错了。4.4 激活实施前的过滤设置BADI实施建好后在SE19的实施定义里有个“过滤”页签。如果项目里有多个销售组织、多个订单类型不同业务需要不同的字段组合可以按需过滤。比如只让订单类型OR生效就设置过滤器值为“OR”。不过BADI_SLS_HEAD_SCR_CUS的过滤字段是CT_SALES_HEADER之类的结构不是简单的订单类型字符串。要按订单类型过滤得在GET_DATA/PUT_DATA方法内部判断CS_HEAD里的订单类型字段。过滤器一般用来区分内部/外部订单或者按销售组织过滤用起来不复杂但容易配错建议不熟悉的情况下尽量不用直接在方法里写业务判断更直观。5. 子屏幕挂载到标准屏幕的完整步骤BADI方法写完了但如果在VA01/VA02里看不到自定义字段说明子屏幕还没挂上去。这一步很多新手踩坑逻辑其实很清晰BADI_SLS_HEAD_SCR_CUS只是提供了数据通道真正改变UI的是函数组的子屏幕与标准屏幕的关联。5.1 查找标准屏幕挂载点VA01/VA02的抬头屏幕挂在程序SAPMV45A里屏幕号是2000系列比如2001、2002、2003不同业务类型对应不同屏幕。要让子屏幕显示出来需要在标准屏幕的Tab条上增加一个Tab页或者在屏幕的某个子区域里嵌入子屏幕。实际操作时通常用**屏幕修改Screen Exit**的方式。进入SE80输入程序名SAPMV45A展开“屏幕”找到抬头屏幕2001。双击进入屏幕编辑器在Tab条控件上右键选择“创建Tab页”然后在新的Tab页里拖入“子屏幕”控件子屏幕名称填你函数组里的那个屏幕比如ZSD_SALES_SCREEN_9001。但这里有个限制这一步在SE80里直接改标准程序是需要访问键的。SAP允许通过“增强项目”的方式修改标准屏幕但必须走SAP的增强机制Spare Screen / Customer Screen。实际上BADI_SLS_HEAD_SCR_CUS对应的标准屏幕已经自带了“客户屏幕”区域不一定需要你手动去Tab条上挂。很多实施项目里顾问直接通过**屏幕增强的可视化工具如SAP Screen Personas或者Enhancement Spot里的Screen Enhancement**挂上子屏幕不需要直接面对SAPMV45A的复杂代码。我的习惯是先看看标准程序里有没有预留的客户子屏幕区域。SAPMV45A的抬头屏幕里在页签“销售”旁边预留了SUBSCREEN的区域给BADI_SLS_HEAD_SCR_CUS使用只需要在增强点里配置子屏幕的赋值即可。如果没有预留再考虑手工改Tab条。5.2 标准做法通过BADI实施维护子屏幕信息在SE19的BADI实施里有一个方法叫GET_HEADER_INFO有的版本叫INITIAL_DATA或HEADER_INFO它负责返回子屏幕信息给标准程序。标准程序在运行时通过这个方法的返回值确定子屏幕要显示哪些内容。实际开发中很多顾问会在BADI实施里这样实现METHOD get_header_info. 设置子屏幕组名称标准程序会把它挂在Tab上 cs_info-screen_group ZSDF. 自定义屏幕组 ENDMETHOD.但更常见的是直接跳过GET_HEADER_INFO把子屏幕挂在函数组里通过PBO的CALL SUBSCREEN语句动态调用。这个写法要求对SAP屏幕编程非常熟。为了简化我推荐一个稳妥路径在SE80里通过增强模式打开SAPMV45A屏幕2001在Tab条上新增一个自定义Tab页页内直接嵌入子屏幕。具体步骤SE80输入程序SAPMV45A回车点击“显示/更改”图标如果提示锁定先解锁找到屏幕2001进入屏幕编辑器右键Tab条控件选择“创建Tab页”新Tab页标题写“备注信息”功能码自定义比如ZNOT在Tab页上拖入子屏幕区域填上函数组和屏幕号保存并激活。这样操作后VA01/VA02的抬头页签上就会多一个“备注信息”页签点击进去能看到自定义字段。5.3 子屏幕显示模式控制在VA03显示模式下子屏幕里的字段应该只读。这个可以用函数组的PBO逻辑控制。标准订单的数据模式可以通过全局变量SY-UCOMM或者标准程序里的状态变量传递但BADI实施里比较简洁的做法是在函数组里定义一个全局变量GV_TRTYP在GET_DATA方法里赋值A/V/HPBO里根据它设置字段属性PROCESS BEFORE OUTPUT. MODULE status_9001. MODULE set_screen_active. MODULE set_screen_active OUTPUT. IF gv_trtyp H. LOOP AT SCREEN. screen-input 0. MODIFY SCREEN. ENDLOOP. ENDIF. ENDMODULE.注意只读控制必须放在PBO里通过LOOP AT SCREEN修改每个字段的输入属性。直接在主程序的PBO里改字段属性也可以但子屏幕有自己独立的PBO所以必须在子屏幕自己的PBO里改才生效。6. 完整函数组代码示例可直接复制参考这部分我贴一个精简但完整的函数组示例方便你直接照着搭。代码基于我上一个小项目的实现去掉了业务敏感信息保留了完整流程。6.1 函数组TOP定义FUNCTION-POOL ZSD_SALES_NOTE. MESSAGE-ID ... TABLES: ztsd_sales_note. DATA: gs_ztsd_sales_note TYPE ztsd_sales_note. DATA: gv_trtyp TYPE char1. DATA: gv_vbeln TYPE vbeln.6.2 子屏幕PBOPROCESS BEFORE OUTPUT. MODULE status_9001. MODULE set_screen_active.MODULE status_9001 OUTPUT. SET PF-STATUS SCREEN_9001. 这里可以设置标题、图标等 ENDMODULE. MODULE set_screen_active OUTPUT. IF gv_trtyp H. LOOP AT SCREEN. screen-input 0. MODIFY SCREEN. ENDLOOP. ENDIF. ENDMODULE.6.3 子屏幕PAIPROCESS AFTER INPUT. MODULE check_zapprove.MODULE check_zapprove INPUT. IF gs_ztsd_sales_note-zapprove IS NOT INITIAL AND gs_ztsd_sales_note-zapprove X AND gs_ztsd_sales_note-zapprove E. MESSAGE 审批状态只能为空、X已审批或E已驳回 TYPE E. ENDIF. ENDMODULE.6.4 BADI实施方法完整代码CLASS zcl_badi_sls_head_scr_cus IMPLEMENTATION. METHOD get_data. DATA: ls_ztsd_sales_note TYPE ztsd_sales_note. gv_vbeln iv_vbeln. gv_trtyp iv_trtyp. CHECK iv_vbeln IS NOT INITIAL. CALL FUNCTION ZSD_SALES_NOTE_GET EXPORTING iv_vbeln iv_vbeln IMPORTING es_ztsd_sales_note ls_ztsd_sales_note. MOVE-CORRESPONDING ls_ztsd_sales_note TO cs_head-docdata. ENDMETHOD. METHOD put_data. DATA: ls_ztsd_sales_note TYPE ztsd_sales_note. CHECK iv_vbeln IS NOT INITIAL. CHECK iv_trtyp A. 创建模式下不落库防止脏数据 MOVE-CORRESPONDING cs_head-docdata TO ls_ztsd_sales_note. ls_ztsd_sales_note-vbeln iv_vbeln. MODIFY ztsd_sales_note FROM ls_ztsd_sales_note. IF sy-subrc 0. MESSAGE 自定义字段保存失败 TYPE E. ENDIF. ENDMETHOD. METHOD delete_data. CHECK it_vbeln IS NOT INITIAL. DELETE FROM ztsd_sales_note WHERE vbeln IN it_vbeln. ENDMETHOD. ENDCLASS.完整函数组里还需要在PBO里把GS_ZTSD_SALES_NOTE的值从CS_HEAD-DOCDATA里带出来。这个传递逻辑我在PUT_DATA里直接用MOVE-CORRESPONDING把屏幕结构传给数据库结构在GET_DATA里反向传递。实际屏幕字段在函数组全局变量里它们之间如何同步答案就在BADI方法里标准程序在PVO阶段会调用GET_DATA此时CS_HEAD-DOCDATA被填充标准程序把CS_HEAD-DOCDATA里的业务字段值赋给函数组全局变量对应的内存区域这部分是通过标准程序的屏幕数据传递机制实现的SAP自动完成PBO显示时函数组全局变量已经有值了直接显示到屏幕字段。以我的经验从CS_HEAD-DOCDATA到函数组全局变量的同步是CASE BY CASE看的某些版本需要你在BADI方法里手工赋值给函数组全局变量。稳妥的办法是在GET_DATA里把数据同时写入函数组全局变量MOVE-CORRESPONDING ls_ztsd_sales_note TO gs_ztsd_sales_note.在PUT_DATA里先从函数组全局变量取数MOVE-CORRESPONDING gs_ztsd_sales_note TO ls_ztsd_sales_note.这样双保险不管SAP自动同步机制有没有生效屏幕字段和数据库字段都能对得上。7. 常见调试问题和排查心得我做完这个增强后特意测试了一轮完整流程踩了几个坑列出来给后来人提个醒。7.1 问题1VA02里能回显但保存后数据库没数据这个现象很典型进VA02看备注字段上次输入的还在点击保存后去SE16N看表却是空的。原因多半是PUT_DATA没被触发或者触发时IV_VBELN为空。排查思路SE19进入BADI实施在PUT_DATA里打个断点看保存时有没有进方法确认IV_VBELN是否有值如果为空检查是不是传参错误确认CS_HEAD-DOCDATA里是否真的有值如果没有说明屏幕字段没有同步过来。我那次遇到的就是函数组全局变量和BADI方法之间数据没同步PUT_DATA拿到的DOCDATA是空的。后来在GET_DATA/PUT_DATA里都加了MOVE-CORRESPONDING同步逻辑问题解决。7.2 问题2子屏幕显示出来了但字段全是灰色不可编辑这个问题几乎都是PBO里LOOP AT SCREEN全字段设了只读。检查GV_TRTYP是不是被错误设置成了H。VA02打开时BADI里IV_TRTYP应该是V但函数组全局变量GV_TRTYP是在GET_DATA里赋值的。如果GET_DATA因为某种原因没执行GV_TRTYP可能还是初始值PBO里就会走默认逻辑。我的建议是在set_screen_active里不要无脑只看GV_TRTYP还要判断屏幕当前的编辑模式。更简单的做法是只读控制只针对特定屏幕字段不要全表LOOP比如IF gv_trtyp H. LOOP AT SCREEN. IF screen-name GS_ZTSD_SALES_NOTE-ZAPPROVE OR screen-name GS_ZTSD_SALES_NOTE-ZNOTE. screen-input 0. MODIFY SCREEN. ENDIF. ENDLOOP. ENDIF.这样即使GV_TRTYP判断出错影响的也只是这两个字段不会把整个页签都弄灰。7.3 问题3下拉框不显示值列表ZAPPROVE字段绑定了域值检查但VA02里下拉框点开后只有空选项。这种一般都是因为域值检查没有激活或者屏幕字段没有正确绑定数据元素。检查步骤SE11看数据元素ZAPPROVE的域定义确认值检查里有值屏幕编辑器里双击字段ZAPPROVE确认“数据元素”正确屏幕属性里确认“值列表”选择“字段值”。如果都正确还是不显示可能是屏幕的输入模板Input Template问题。在屏幕编辑器里选中字段右键“属性”把“值列表”改成“字段值”重新激活屏幕基本能解决。7.4 问题4VA03显示时备注字段显示了但打印/输出时取不到这个我项目里也遇到过。BADI_SLS_HEAD_SCR_CUS只管屏幕UI销售订单打印比如打印发票、交货单走的是专门的数据提取逻辑它不会自动读你的自建表。如果打印模板也要显示备注需要在输出程序里比如bill打印的ABAP代码额外去读ZTSD_SALES_NOTE表。这个可以加在打印程序的数据获取逻辑里或者调用USER_EXIT。这块不是BADI的职责范围千万别指望增强屏幕字段就能自动出现在所有输出里。8. 扩展建议多字段多效果的进阶玩法这个BADI的功能不止于加几个文本字段。我后来在一个项目里做了更复杂的效果根据订单类型控制自定义字段是否显示、是否必填、是否只读实现方式还是在PBO里做动态字段属性控制。比如订单类型OR和ZOR显示不同的字段组合IF gs_ztsd_sales_note-vbeln IS NOT INITIAL. 根据CS_HEAD里的AUART判断 ENDIF.不过要注意GET_DATA只能拿到IV_VBELN拿不到订单类型。要想在屏幕PBO里判断订单类型得在BADI的GET_DATA里把订单类型一起存到函数组全局变量然后PBO里判断。另外如果需要在保存时做更强的校验比如备注必填、审批状态必须已通过可以在PUT_DATA里加逻辑IF ls_ztsd_sales_note-znote IS INITIAL AND sy-batch IS INITIAL. MESSAGE 内部备注不能为空 TYPE E. ENDIF.这种校验放在PUT_DATA里比放在PAI里更可靠因为它覆盖了所有数据入口不光是VA01/VA02屏幕包括BAPI、RFC调用也会走这个方法。这套增强做完以后销售订单抬头就多了两个业务字段用户用下来基本无感觉得这本来就是SAP该有的功能。真正的开发量其实不大核心就是把函数组、子屏幕、BADI三个东西的逻辑串清楚。我个人最想提醒的一句话是别被网上那些零散的代码片段带偏。这个增强里代码本身不复杂复杂的是数据流转的时序——GET_DATA在什么时候被调用PPVO和PAI的先后顺序函数组全局变量在哪里被赋值这些理清楚了代码怎么写都对。理不清楚抄了满屏代码照样跑不出预期效果。如果你正在做这个增强建议先把上面的架构图画明白再动手写代码。做完之后记得用ST05开一下SQL跟踪确认保存时确实DELETE/UPDATE了自己的表这比肉眼看着屏幕绿灯更有说服力。