ARTICLE DETAIL

资讯详情

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

SAP PP生产订单增强:WORKORDER_UPDATE BADI实战与TECO校验

SAP PP生产订单增强:WORKORDER_UPDATE BADI实战与TECO校验 搞SAP PP的顾问和开发大概率都遇到过这种需求生产订单在创建、修改、下达、TECO的一瞬间业务部门突然要你卡一道逻辑——数量超了不让过、物料用错了要拦截、状态变化要自动触发通知。以前大家习惯找USEREXIT或者写隐式增强但稍微正规点的项目现在基本都会让你走BADI。这次要聊的就是PP生产订单增强里最常用的一个BADIWORKORDER_UPDATE。这个BADI说白了就是SAP给PP生产订单预留的“后门”在订单创建、修改、删除、TECO技术完成等关键动作发生时允许你插入自己的校验逻辑或自定义数据处理。它解决了什么问题举个最典型的场景生产订单要技术完成TECO时系统默认不会拦你哪怕订单上还有未完工的数量、未结算的差异照样能TECO。但财务那边肯定不干因为TECO之后订单就不能再发生物料移动和费用过账导致差异挂在那结不平。用WORKORDER_UPDATE在TECO前加一道校验就能把这类问题提前堵住。这篇文章适合谁看一是做PP模块的顾问想搞清楚生产订单增强怎么做、哪里下手二是ABAP开发接下来要实施WORKORDER_UPDATE、不知道参数怎么取、底表怎么读三是正准备做S4升级、想把老增强迁移到BADI体系的人。我会把从找增强点到写实施类、再到踩坑排错的过程全部拆开讲尽量做到看完就能照着自己改。1. WORKORDER_UPDATE整体设计思路与选型考量1.1 为什么首选WORKORDER_UPDATE而不是老式USEREXITPP生产订单的增强历史上经历过好几代。早期项目里常见的是USEREXIT_SAVE_DOCUMENT这类出口挂在CMOD/SMOD下面比如PPCO0001、PPCO0002这些增强项目里就有针对订单保存的用户出口。后来SAP逐步推荐用BADI再到现在S4里很多地方建议用增强点Enhancement Spot甚至业务事件Business Events但WORKORDER_UPDATE这个BADI一直没有被废弃而且几乎每个PP项目都在用。为什么它地位这么稳核心原因是触发时机和覆盖范围。WORKORDER_UPDATE挂在生产订单的更新任务Update Task里覆盖了创建、删除、修改、TECO、下达、反下达、关闭、删除标记等几乎所有订单状态变化动作。而且它是在事务结束、数据即将提交到数据库之前触发的这意味着你能拿到一整单完整、干净的前后数据对比做校验、做数据处理都是最合适的时机。我做过对比在同一个需求下用USEREXIT和用WORKORDER_UPDATE实现难度差别不大但可维护性完全两码事。USEREXIT的代码分散在多个增强项目里签名固定、作用域模糊到后期排查问题往往要翻半天。WORKORDER_UPDATE方法名清晰参数结构规范每个方法管一类动作哪里出了问题一目了然。所以在稍有规模的项目里增强方案评审时基本都不接受新的USEREXIT了。1.2 BADI六个方法拆解每个方法管什么事WORKORDER_UPDATE实施类的接口名是IF_EX_WORKORDER_UPDATE一共六个方法。很多人一上来就想在UPDATE方法里全写完其实不对这个BADI的设计逻辑是针对不同动作提供不同入口选错方法会导致代码在一些场景下不触发或者触发时机不符合预期。六个方法分别是CREATE生产订单创建时触发通常在订单保存时执行。DELETE订单删除时触发拿到的是删除前的完整数据。UPDATE修改已存在订单时触发最常用、逻辑最复杂。TEC技术完成TECO时触发。PROCESS_ORDER_SAVE整个订单保存流程的统一收口触发点。DEQUEUE订单数据解锁后触发。实际开发中UPDATE和TEC用得最多。UPDATE能拿到修改前后的数据差适合做各种业务校验TEC专门卡技术完成适合做完工检查。CREATE和DELETE相对简单就是在新单创建流程末尾或者删除流程里插入自定义逻辑。PROCESS_ORDER_SAVE是所有动作最终都会经过的一个收口点适合做那些无论订单发生什么操作都必须执行的逻辑比如写自定义日志表。DEQUEUE用得比较少我一般只在需要处理解锁后动作时才会碰它。1.3 方法触发时机与Update Task机制很多开发第一次写WORKORDER_UPDATE会遇到“代码明明写了但没反应”“调试感觉时序不对”这类问题。搞清楚它的底层触发机制很重要。这个BADI的调用发生在生产订单保存的更新任务Update Task里。所谓更新任务是SAP为了保障数据一致性做的一个机制主事务里不直接写数据库而是把数据变更打包放进一个任务队列等主事务确认成功后更新任务才真正把数据写入数据库。WORKORDER_UPDATE就挂在这个更新过程中因此代码里你不能直接COMMIT WORK也不能再触发需要和主事务交互的同步对话比如弹出对话框让用户选择否则会直接短转储或者数据不一致。但好处也很明显在更新任务里你能拿到一组“前像”和“后像”数据。比如修改数量前是一百改后是八十BADI里能同时看到IS_ORDER_HEADER_OLD和IS_ORDER_HEADER两个结构。这意味着你可以做精确的变更检测而不只是拿到最终值做判断。另外一个需要注意的点是因为它在更新任务里执行如果代码里抛出异常或做了错误的RAISE订单保存过程会回滚。这个特性有时候是救命稻草但有时候也是坑——后面排查章节我会专门讲。2. 核心细节解析参数、底表与状态读取2.1 关键的几个方法参数解读以最常用的UPDATE方法为例它的签名里这几个参数基本每个项目都会用到IS_ORDER_HEADER修改后的生产订单抬头数据结构为ORDER_HEADER。IS_ORDER_HEADER_OLD修改前的订单抬头数据用来做前后比较。IT_ORDER_POSITION修改后的订单行项目组件内表包含物料、数量、工厂、库存地点等。IT_ORDER_POSITION_OLD修改前的行项目内表。IT_ORDER_STATUS订单的状态信息表。IT_ORDER_OPERATION工序数据内表。IT_ORDER_SEQUENCE工序序列内表。订单抬头里最常用到的字段包括AUFNR订单号、AUART订单类型、WERKS工厂、GAMNG订单总量、GMEIN订单单位、PDATV基本开始日期等。行项目里重点看POSNR项目号、MATNR物料、BDMNG需求数量、ENMNG已收货数量、WEMNG已移动数量、LGORT库存地点等。我每次写增强前的第一步都是先用SE24或者直接DEBUG把传入结构打印出来看一眼搞清楚这条路径上数据到底长什么样。很多新手喜欢直接从网上抄一段代码就上结果结构字段名都传错了这种低级错误其实很容易避免。2.2 生产订单底表ABAP里怎么关联读数据只靠BADI传参往往不够很多校验需要查订单关联的底表数据。PP生产订单有一组核心底表搞增强必须烂熟于心AUFK订单主记录表订单号、订单类型、工厂、状态都在这里。AFKO订单头数据表包含订单数量、基本日期、结算规则号等AUFNR是主键。AFPO订单项表物料、数量、交货日期、相关的采购凭证都在这里。AFVC工序表工序号、工作中心、控制码、标准值都在这里。JEST对象状态表通过OBJNR关联订单STAT字段存状态代码。JCDS状态变更记录表能看到订单状态的历史变更轨迹。AFVV工序数量/日期/值。实战中TECO前的校验经常需要判断订单当前处于哪个状态。订单状态的存储逻辑是JEST表里存的是对象状态如I0001表示已下达、I0045表示已技术完成但读起来不方便因为状态代码不是给人看的。更推荐用函数STATUS_READ传入订单的OBJNR直接取外部状态文本。CALL FUNCTION STATUS_READ EXPORTING objnr lv_objnr TABLES status lt_status EXCEPTIONS object_not_found 1 OTHERS 2.这段代码取到的LT_STATUS里包含最低状态编号、外部状态编码如I0045和状态文本。判断是否已TECO只需检查外部状态编码是否为I0045。这套逻辑在TEC方法里几乎是标配。2.3 状态变化检测如何精准判断“刚刚发生了TECO”这里补充一个非常实用的经验在WORKORDER_UPDATE的UPDATE方法里做状态类校验时不要只判断当前状态一定要比较状态变化。状态变化的信息存在JCDS表里或者通过其他状态历史函数读取。有一次客户提了一个需求生产订单一旦完成TECO就不允许任何人在修改订单里的数量和物料。如果用旧状态对比新状态就能精准识别“这次保存发生了TECO动作”CLEAR lv_flag_teco. SORT lt_new_status BY stat. SORT lt_old_status BY stat. LOOP AT lt_new_status INTO ls_status_new. READ TABLE lt_old_status TRANSPORTING NO FIELDS WITH TABLE KEY stat ls_status_new-stat. IF sy-subrc 0. lv_flag_teco X. 新状态存在旧状态不存在说明刚进入该状态 ENDIF. ENDLOOP.我记得有次项目上线后测试发现本来该拦截的逻辑漏了一部分查半天才发现是同一个订单TECO后再次保存时新旧状态里都包含I0045简单的“等于”判断根本拦不住。后来改成上面这种“旧状态里不存在”的判断方式才真正解决了问题。针对这个坑我后面排查章节还会再提一次。3. 实操过程从创建BADI实施到部署启用3.1 先找增强点还是先建实施类实操顺序步骤这个BADI属于PPCO003增强点Enhancement Spot老版本里是BADI定义S4里依然兼容。实操完全可以按下面步骤做事务码SE19创建增强实施Classic BADI或者用SE18查询BADI定义。输入BADI名称WORKORDER_UPDATE点击“创建实施”。给出实施类名称比如ZCL_IM_WO_UPDATE_VALID。注意命名规范建议带上项目代号或模块名。系统自动生成实施类和接口双击方法列表里需要实现的方法进入代码编辑器。写代码激活然后测试。这里很多人会卡在第一步SE18里能查到WORKORDER_UPDATE的定义但SE19里输入名称提示不存在。我碰到过这种情况原因是实施名称和BADI名称混淆了。SE18查的是定义名称SE19创建时需要在上面选择“Classic BADI”输入定义名WORKORDER_UPDATE然后再给它起实施名。如果你的项目用的是新增强基础设施Enhancement Spot则在SPRO里找到生产订单的“增强”配置节点或者直接在SE18里搜WORKORDER_UPDATE查看它绑定的Spot名称用SE20去创建Spot增强实施。S4 1909之后新项目可能会这样走但老项目双轨并行也能兼容。3.2 实操案例生产订单数量上限按物料组卡控理论讲太多容易晕我直接以一个真实需求为例走一遍完整的增强编码过程。客户要求某个物料组下的半成品生产订单数量不允许超过5000件。选用TEC还是UPDATE这里要和业务确认是下达时就卡还是保存时就卡一般是保存时就卡所以放UPDATE方法里。代码思路METHOD IF_EX_WORKORDER_UPDATE~UPDATE. DATA: ls_order TYPE order_header, ls_position TYPE order_position, lv_auart TYPE aufk-auart, lv_matkl TYPE mara-matkl, lv_gamng TYPE afko-gamng. ls_order is_order_header. IF ls_order-aufnr IS INITIAL. RETURN. ENDIF. 只卡生产订单类型PP01其他类型放行 SELECT SINGLE auart FROM aufk INTO lv_auart WHERE aufnr ls_order-aufnr. CHECK lv_auart PP01. LOOP AT it_order_position INTO ls_position. 取物料组 SELECT SINGLE matkl FROM mara INTO lv_matkl WHERE matnr ls_position-matnr. CHECK lv_matkl A001. IF ls_position-bdmng 5000. MESSAGE e398(00) WITH 订单数量超过物料组最大生产数量5000无法保存 RAISING error_in_update. ENDIF. ENDLOOP. ENDMETHOD.这段代码里有几个细节值得展开为什么有些查询用SELECT SINGLE、有些用READ TABLE因为BADI传入的行项目内表已经带了物料等信息可以直接读但物料组MARA表不会随订单存储必须再查。MESSAGE语句里为什么带RAISING error_in_update这是BADI回调的关键。如果不带RAISING消息只是显示但不会阻止保存订单照样存进去。带了RAISING系统会把异常抛给调用方触发保存回滚。这里我特意先判断订单类型避免影响到所有流程。实际项目里增强代码往往会因为“影响面太大”而引发麻烦所以能用订单类型、工厂、物料组做前置过滤的一定要加。3.3 实操案例TECO前强制检查已完工数量第二个高频需求订单TECO前生产数量必须大于等于订单计划数量否则禁止TECO。这在WORKORDER_UPDATE的TEC方法里实现最合适。TEC方法的关键参数是IS_ORDER_HEADER和IT_ORDER_POSITION同样可以拿到订单号和组件行项目。我的实现思路是按订单号读取AFPO获取计划数量再通过功能模块或者表关联获取实际入库数量——入库数量其实直接可以从AFPO的ENMNG字段拿这是“订单项已收货物料数量”包含了全部已确认的收货动作。代码大致如下METHOD IF_EX_WORKORDER_UPDATE~TEC. DATA: lv_aufnr TYPE aufk-aufnr, lv_gamng TYPE afko-gamng, lv_enmng TYPE afpo-enmng. lv_aufnr is_order_header-aufnr. CHECK NOT lv_aufnr IS INITIAL. SELECT SINGLE gamng FROM afko INTO lv_gamng WHERE aufnr lv_aufnr. SELECT SUM( enmng ) FROM afpo INTO lv_enmng WHERE aufnr lv_aufnr. IF lv_enmng lv_gamng. MESSAGE e398(00) WITH 订单已收货数量小于计划数量不允许技术完成 RAISING error_in_update. ENDIF. ENDMETHOD.这个逻辑看着简单但有一个非常容易忽略的坑SUM查询返回空值。如果AFPO里一条收货记录都没有lv_enmng会是空而不是0空值跟数字比较会直接异常。写的时候要记得加CLEAR lv_enmng或者IF lv_enmng IS INITIAL. lv_enmng 0. ENDIF.。别笑这个坑我亲眼见过同事栽进去线上直接炸了。4. 常见问题与排查技巧实录4.1 BADI不触发或找不到先排除增强开关问题所有BADI增强先确认有没有在实施里激活。这个看起来很简单但项目里经常出现“明明挂了增强跑起来没反应”的情况最后发现实施类是Inactive状态。激活之后如果还没反应就要考虑BADI的Filter设置。WORKORDER_UPDATE这个BADI没有标准的过滤条件但如果项目上有人配置了全局增强过滤会导致部分工厂或订单类型不触发。另一个非常隐蔽的原因是同一BADI被多个实施类实现。系统会按照实施顺序依次执行所有实施如果你的代码里没有做充分的入口条件限制而另一个实施先抛了消息或异常你的代码就可能根本执行不到。排查方式是用SE18查BADI的“实施列表”看看有没有多个Z类同时在实现以及它们的排列顺序。4.2 Update Task里的坑不能COMMIT、不能弹窗、不能异步前面说过WORKORDER_UPDATE在Update Task里执行这个背景决定了很多事不能做。最常见的坑就是不能在BADI里调用BAPI做进一步的数据写入甚至不能COMMIT WORK因为整个更新任务还没有确认完成你强行提交会导致数据库状态混乱。有一次为了在TECO时顺便给自定义表写状态日志我在TEC方法里调了BAPI_PRODORD_TECH_COMPLETE做嵌套变更结果逻辑上看起来对但数据对不上——外层事务在里层已经COMMIT之后又回滚了自定义表的日志留着生产订单状态却回到未TECO两边完全不同步。后来改成直接在方法里更新自定义表不调BAPI跟着主事务一起提交回滚问题才解决。还有一个容易踩的坑是不要在这个BADI里调用需要用户交互的同步RFC比如发邮件同步调用或者调用弹窗。更新任务的RFC上下文跟对话会话不是一回事弹窗根本弹不出来可能还会直接导致提交挂死。如果一定要发邮件通知建议把信息写到一个自定义通知表里由另外一个定时任务去读表发送绕开更新任务的限制。4.3 消息类型选择用E消息拦截还是W消息放行写完校验逻辑消息类型怎么选这个看似是细节实际影响很大。如果业务要求“必须完全拦住”用E类型消息配合RAISING如果只是提示性警告业务可以强制继续有两个方案一是消息类型用W但要注意带W消息的BADI在执行到RAISING时照样回滚所以要么干脆不带RAISING只发W消息要么在发W消息后使用CHECK退出方法不再执行后续阻塞逻辑。我实际项目中的习惯是需求变化初期先用W消息观察一段时间的命中量确认逻辑没有问题且命中确实是异常数据后再切成E消息。这样能避免一些意料之外的数据问题导致业务完全跑不了。上线第一周先“只警告”命中几次、业务反馈、确认数据异常后第二周再改成“强制拦截”这是规避风险的一个非常实用的节奏。4.4 生产订单结不平和TECO强校验的关系热搜词里单独提到了“生产订单结不平”。这个问题表面上是月末结算时差异挂在订单上没有结掉导致CO报表不平。很多项目里结不平的根源不是结算规则配错而是订单被TECO的时候还有很多移动没做完、确认没做完、甚至收货数量都对不上。TECO之后这些动作全被锁死想补救只能强行解除TECO来回折腾费时费力。在WORKORDER_UPDATE的TEC方法里做强校验等于从源头截住这种“不清不楚就完工”的订单。实际项目中我一般建议至少做三类校验订单已收数量对比计划数量差异不得超过一个容忍百分比。订单还有未确认的工序报工不能TECO。订单仍有未处理的交货/采购关联不能TECO。校验规则可以在客户侧做成配置表维度为工厂订单类型控制方式这样业务规则变化不需要改代码顾问自己去配就行。这个思路在很多项目里都得到了好评因为减少了开发介入次数也让规则本身可追溯、可审计。4.5 性能排查SELECT多了会不会拖慢保存有时候BADI里逻辑多了订单保存会明显变慢。尤其是批量维护生产订单或者在Mass Processing场景下一条逻辑被循环几千次慢得没法用。排查思路有两个一是看有没有在循环内做多余的SELECT。比如上文的物料组查询如果循环里有10条组件就查了10次MARA。可以改成先把所有物料收集到内表一次SELECT查完再用内表关联。SAP不像关系型数据库那样能随意JOIN但在ABAP里做内表关联还是很快的。二是注意别在BADI里重复读取同一个订单的数据。多次调用相同SELECT的唯一性很差可以通过共享内存或者方法内静态变量缓存。例如STATICS: BEGIN OF s_cache, matnr TYPE mara-matnr, matkl TYPE mara-matkl, END OF s_cache.用STATICS缓存当天第一次查询到的物料组后续同物料直接命中不重复下数据库。这个技巧在循环量大时收益非常明显。但要注意BADI方法调用同一个实例会复用STATICS变量如果订单跨天处理缓存数据最好带日期或者做成多级缓存别把昨天的物料组带进今天的逻辑里。5. 我踩过的一些坑和最后的建议写BADI增强大多数时候不是不会写代码而是对业务理解不透彻、对触发机制理解不到位、对异常边界考虑不周。WORKORDER_UPDATE作为PP生产订单最核心的增强入口用法不难难的是把规则设计好把边界情况处理干净。有一次做S4升级客户老系统里有大量USEREXIT逻辑其中一部分就是要迁移到WORKORDER_UPDATE上。迁移过程中我发现老出口里的很多代码其实是在不合适的时机运行的——比如在数据未完全收集时做数量校验某些逻辑只能依赖直接读表而不是使用接口标准参数导致迁移后行为出现偏差。这件事给我的经验是换一个技术实现一定要重新回看业务逻辑本身而不是机械地把代码搬过去。借着重新实现的机会优化校验逻辑、梳理数据流比单纯平移更有价值。再分享一个小技巧BADI里所有的自定义消息尽量使用消息类管理而不是直接在代码里写死文本。客户改一句提示语是很频繁的事如果每句话都硬编码在源代码里后续光改提示语就要传输无数个请求。集中放到SE91消息类里让顾问自己改长文本开发和后续运维都会轻松很多。这段实战经验在你之后的项目里大概率用得上。WORKORDER_UPDATE看起来就是个普通接口但把它用熟了PP模块的核心增强需求你基本就能覆盖六成以上。剩下的四成无非是组合使用其他增强点或者做数据落库思路都是相通的。
返回列表