SAP公司间STO:PGI后自动创建内向交货单的BADI增强实现

发布时间:2026/9/30 10:40:42
SAP公司间STO:PGI后自动创建内向交货单的BADI增强实现 在公司间STO流程里发货方做完PGIPost Goods Issue发货过账后收货方往往还是“两眼一抹黑”——不知道货什么时候发出、发了多少、什么时候能到。这时候如果能让系统在PGI后自动帮收货方创建一张内向交货单整个物流链路就顺了。这篇文章就围绕这个需求展开讲清楚SAP公司间STO流程中外向交货单PGI后自动触发内向交货单的实现思路和落地方法。如果你是做SAP SD、MM模块的顾问或者是准备接这类需求的ABAP开发又或者是企业里负责供应链IT的同事这篇文章应该能帮你省下不少调研时间。我会从业务流程讲起到方案选型再到BADI增强的代码实现和上线前的配置检查最后把最常见的坑都列一遍尽量做到可以直接照着做。1. 先理清公司间STO的完整链路1.1 典型业务场景是怎么走的先说场景。假设集团下有两个公司代码1000是供应公司负责生产2000是销售公司负责面向客户的销售。两个公司属于不同法人工厂之间要调拨物料这就会用到公司间STOStock Transport Order库存转储订单。在SAP里公司间STO普遍采用三步法业务单据流转大概是这样的2000公司在系统里用ME21N创建一张STO采购订单采购订单类型是NB供应商维护成1000公司同时指定供货工厂Supplying Plant为1000工厂。系统通过输出类型或消息控制比如标准的M1消息在1000公司自动生成一张公司间销售订单订单类型通常是CB。1000公司针对这张CB销售订单用VL10E或者VL01N创建外向交货单。仓库用VL02N对外向交货单做PGI发货过账此时产生物料凭证卖方库存减少。这是关键节点PGI完成后系统需要在2000公司自动创建一张内向交货单作为2000工厂仓库收货的依据。2000公司仓库收货后通过VL32N对内向交货单做收货确认或者直接MIGO 101过账。后续再做发票校验MIRO和公司间发票开票VF01。这个流程里第5步的“自动创建内向交货单”是很多项目里业务方会提的典型需求。我第一次接触这个需求的时候业务方说得很直白“我们的收货仓库需要一个通知不然货到了都不知道还要反反复复查邮件、打电话。”1.2 为什么PGI后需要自动创建内向交货单可能有人会问买方直接用MIGO对采购订单做101收货不就行了为什么非要一张内向交货单我在项目里总结下来主要有几个原因。第一内向交货单在功能上相当于一张ASNAdvanced Shipping Notice预到货通知。买方仓库收到内向交货单后可以提前知道什么料、多少数量、大概什么时间到可以提前安排仓位、人员和设备。没有内向交货单收货员只能拿着纸质单据或者凭经验等货很容易出现混乱。第二如果买方工厂上了WM模块或者要对接EWM内向交货单是触发WM转储需求、上架任务等后续动作的来源单据。没有内向交货单仓库自动化流程就跑不起来。第三跨境调拨场景下报关、物流交接需要一张正式的单据来对应实物。内向交货单上的单据号可以直接用来做关务数据引用比从采购订单去推物流信息方便得多。第四内向交货单能帮助买方校验收货数量。如果卖方发运了100箱但内向交货单只创建了90箱收货时就能及时发现差异而不是等到期末对账才发现问题。1.3 需求边界哪些情况下可以不做当然也不是所有STO场景都必须做内向交货单。如果两个工厂距离很近物流简单收货方直接用MIGO对采购订单收货就完全足够那确实没必要增加复杂度。另外如果系统里启用的是两步法STO同一公司代码下的工厂间转储流程相对简单一般也不走“卖方销售订单买方采购订单”的模式内向交货单的需求自然就弱很多。所以在动手实现之前建议先跟业务方把需求边界聊清楚哪些公司间场景需要内向交货单是全部物料还是部分物料有没有分批到货的情况这些问题的答案会直接影响技术方案的设计。2. 方案选型标准功能、输出类型还是增强开发2.1 SAP标准功能能做到什么程度先摸着良心说SAP对这个需求有一些标准的功能基础但往往不能直接“开箱即用”。标准做法之一是通过外向交货单的输出类型来做文章。比如在标准系统里外向交货单的RD00Delivery或DESADVDespatch Advice输出类型可以在PGI后自动触发消息发送。如果买方和卖方是两套系统还可以通过EDI把发货通知发给买方买方再基于这个EDI消息创建自己的内向交货单。但如果是同一套SAP系统单实例里的公司间STO这种做法反而绕了一圈还要额外配置EDI和IDOC维护成本不低。标准做法之二是买方直接使用事务代码VL31N手工基于采购订单创建内向交货单。这个操作本身是标准的但需求要求的是“自动触发”手工操作不满足要求。至于PPFPost Processing Framework在EWM场景里确实很强大可以通过配置在特定业务事件时触发后台动作。但是在传统ECC/S/4 HANA的交货单模块里要给外向交货单配置PPF动作来创建内向交货单配置复杂度比较高而且调试起来不如增强直观。如果你的项目用的是S/4 HANA 2020以上版本又有EWM在跑倒是值得考虑PPF方案否则我更倾向于做增强。2.2 三个可行方案的优缺点对比我梳理了三个主流的实现方案用一个表格来对比方案实现方式优点缺点方案A输出类型自定义程序配置一个输出类型PGI后触发程序程序通过后台作业创建内向交货单不用改交货单保存逻辑架构上隔离输出类型处理的触发时机有延迟调试麻烦对代码能力要求也没低多少方案BBADI增强在交货单过账的BADI中调用BAPI同步或异步创建内向交货单触发时机精确数据来源直接事务性强可加日志和重试机制需要ABAP开发要考虑幂等、异常、性能等问题方案CPPF Action在交货单配置PPF动作通过后处理框架触发面向未来在EWM集成场景有优势传统交货单场景配置繁琐维护的人要懂PPF框架上手门槛高2.3 我推荐方案的判断逻辑如果项目是传统的ECC或者S/4 HANA单实例公司间STO我会直接推荐方案B用BADI增强实现。原因也很简单第一可控性强。代码里可以打日志、加断点、做各种条件判断出问题能很快定位。输出类型和PPF方案一旦出问题要排查的环节多而且很多配置项在标准里是黑盒。第二事务一致性容易把握。在交货单过账的BADI里触发创建内向交货单可以在同一个逻辑单元里处理避免出现“PGI成功但内向交货单没建出来”这种尴尬情况。第三可扩展性好。做增强的时候可以在代码里做公司代码、工厂、物料类型、订单类型等多个维度的控制后面业务方提新需求改代码比改配置更灵活。如果你的系统是分散架构卖方和买方分别在不同SAP系统那就另当别论了那种场景下走EDI/IDOC反而是正解。这篇文章主要讲的是单实例场景下的实现。3. BADI增强核心逻辑与落地代码3.1 选对增强点MB_DELIVERY_CHANGE~POST_DOCUMENT要实现PGI后自动创建内向交货单第一步是选对增强点。SAP里跟交货单相关的BADI不少但我推荐的是MB_DELIVERY_CHANGE这个BADI的POST_DOCUMENT方法。为什么推荐这个因为这个方法是在物料凭证过账之后被调用的能直接拿到物料凭证的抬头信息包括物料凭证号、凭证年份和移动类型。这比在LE_SHP_DELIVERY_PROC里通过交货单状态去判断“是否已过账”要更准确、更直接。在POST_DOCUMENT方法里导入参数GOODSMVT_HEADER类型MBGMHD里面有物料凭证号和凭证年份GOODSMVT_CODE类型MBGMB里面有移动类型。从这个入口进去通过MSEG表就能反查交货单号、交货单项目、物料、数量、单位这些关键信息。需要特别提醒的是这个BADI不仅仅会在VL02N过账时触发MIGO、MB1B等物料过账动作同样会触发。所以增强里必须对移动类型做前置判断否则会把所有物料过账都卷进来。3.2 从交货单到采购订单的追溯逻辑创建内向交货单的BAPI是基于采购订单来创建的。所以核心问题变成给定一张外向交货单怎么找到对应的STO采购订单号追溯路径是这样的从MSEG表根据物料凭证号、项目找到外向交货单号LFBNR和交货单项目LFPOS。从LIPS表根据交货单号、项目找到参考销售订单号VGBEL和销售订单行项目号VGPOS。这里VGBEL对应的是1000公司的公司间销售订单CB类型。从VBAP表根据销售订单号、项目找到STO采购订单号VGBEL和采购订单行项目VGPOS。这里VGBEL是2000公司创建的那张STO采购订单号。在实际项目里第三步可能会有例外。有些客户的Copy Control配置会把采购订单号放在VBAK/VBAP的标准字段里但也有项目是用客户增强字段或者自定义表来记录关联关系的。所以这里我建议在项目上先拿真实数据验证一下确认字段值能对上再写死。3.3 调用BAPI创建内向交货单找到采购订单号之后就可以调用BAPIBAPI_INBOUND_DELIVERY_CREATE来创建内向交货单。下面是一个最小可用的骨架代码。需要注意这个BAPI在不同SAP版本里的字段略有差异S/4 HANA里某些字段名可能跟ECC不同但大体结构是稳定的。METHOD if_ex_mb_delivery_change~post_document. DATA: ls_mseg TYPE mseg, lv_delnr TYPE vbeln_vl, lv_delpos TYPE posnr_vl, lv_so_number TYPE vbeln_va, lv_so_item TYPE posnr_va, lv_po_number TYPE ebeln, lv_po_item TYPE ebelp, lv_matnr TYPE matnr, lv_menge TYPE menge_d, lv_meins TYPE meins, lv_inbdeliv TYPE vbeln_vl, lt_return TYPE TABLE OF bapiret2. 1. 只处理公司间STO发货过账移动类型 643公司间在途发货 645公司间直接发货 IF goodsmvt_code-bmng_bwart NE 643 AND goodsmvt_code-bmng_bwart NE 645. RETURN. ENDIF. 2. 反查外向交货单及物料信息 SELECT SINGLE lfbnr lfpos matnr menge meins INTO (lv_delnr, lv_delpos, lv_matnr, lv_menge, lv_meins) FROM mseg WHERE mblnr goodsmvt_header-mblnr AND mjahr goodsmvt_header-mjahr AND bwart goodsmvt_code-bmng_bwart AND lfbnr . IF sy-subrc 0 OR lv_delnr IS INITIAL. RETURN. ENDIF. 3. 追溯公司间销售订单号 SELECT SINGLE vgbel vgpos INTO (lv_so_number, lv_so_item) FROM lips WHERE vbeln lv_delnr AND posnr lv_delpos. IF sy-subrc 0 OR lv_so_number IS INITIAL. RETURN. ENDIF. 4. 追溯STO采购订单号 SELECT SINGLE vgbel vgpos INTO (lv_po_number, lv_po_item) FROM vbap WHERE vbeln lv_so_number AND posnr lv_so_item. IF sy-subrc 0 OR lv_po_number IS INITIAL. RETURN. ENDIF. 5. 检查是否已创建过避免重复 SELECT SINGLE mandt FROM zsto_inb_log WHERE deliv_nr lv_delnr AND deliv_pos lv_delpos. IF sy-subrc 0. RETURN. ENDIF. 6. 调用BAPI创建内向交货单 PERFORM create_inbound_delivery USING lv_po_number lv_po_item lv_matnr lv_menge lv_meins CHANGING lv_inbdeliv lt_return. 7. 写日志 PERFORM write_log USING lv_delnr lv_delpos lv_inbdeliv lt_return. ENDMETHOD.BAPI调用部分可以单独封装成一个PERFORM或者方法核心逻辑是这样的FORM create_inbound_delivery USING iv_po_number TYPE ebeln iv_po_item TYPE ebelp iv_matnr TYPE matnr iv_menge TYPE menge_d iv_meins TYPE meins CHANGING ev_inbdeliv TYPE vbeln_vl et_return TYPE bapiret2_t. DATA: ls_header TYPE bapiobdlvhdr, ls_headerx TYPE bapiobdlvhdrx, ls_item TYPE bapiobdlvitm, ls_itemx TYPE bapiobdlvitmx, lt_items TYPE TABLE OF bapiobdlvitm, lt_itemsx TYPE TABLE OF bapiobdlvitmx. 抬头信息类型EL是标准的内向交货单类型 ls_header-doc_type EL. ls_header-purch_no iv_po_number. ls_header-pstng_date sy-datum. ls_header-mvt_ind R. R表示收货过账 ls_headerx-doc_type abap_true. ls_headerx-purch_no abap_true. ls_headerx-pstng_date abap_true. ls_headerx-mvt_ind abap_true. 项目信息 ls_item-purch_no_c iv_po_number. ls_item-po_item iv_po_item. ls_item-material iv_matnr. ls_item-entry_qnt iv_menge. ls_item-entry_uom iv_meins. ls_itemx-purch_no_c abap_true. ls_itemx-po_item abap_true. ls_itemx-material abap_true. ls_itemx-entry_qnt abap_true. ls_itemx-entry_uom abap_true. APPEND ls_item TO lt_items. APPEND ls_itemx TO lt_itemsx. CALL FUNCTION BAPI_INBOUND_DELIVERY_CREATE EXPORTING inb_delivery_header ls_header inb_delivery_headerx ls_headerx IMPORTING inb_delivery_number ev_inbdeliv TABLES inb_delivery_items lt_items inb_delivery_itemsx lt_itemsx returns et_return. ENDFORM.关于事务提交要特别提醒在BADI里面调用BAPI提交逻辑要设计好。如果直接在BADI里COMMIT WORK可能会把物料凭证过账的事务也一起提交掉这本身不一定有问题但要做好异常处理设计。如果项目对数据一致性要求高我更推荐用“异步方案”也就是后面3.5节讲的待处理表后台作业模式。3.4 幂等性设计与日志记录做增强最怕的就是重复创建。PGI的BADI在极端情况下可能会被触发两次或者系统异常重试的时候又跑了一遍如果没做幂等控制买方就会出现一张以上的内向交货单后续收货会非常混乱。我的做法是建一张自定义日志表ZSTO_INB_LOG关键字段包括字段类型说明MANDTCLNT客户端DELIV_NRCHAR10外向交货单号DELIV_POSCHAR6外向交货单项目INB_DELIVCHAR10创建的内向交货单号CREATED_DATEDATS创建日期CREATED_TIMETIMS创建时间STATUSCHAR1S成功 / E失败 / P处理中MESSAGECHAR255返回消息每次进入增强逻辑后先按DELIV_NR DELIV_POS查一下这张表如果已经是成功状态直接退出不做任何处理。同时最好在表上建一个唯一索引DELIV_NR DELIV_POS双保险防止并发场景下的重复创建。日志的写入时机也很关键。如果BAPI返回成功就往表里插入一条状态为S的记录并保留内向交货单号。如果失败插入状态为E的记录保留错误消息。这样即使批量过账事后也能通过这张表快速定位问题。3.5 异步方案的扩展思路同步方案虽然实现简单但有一个隐患BADI里调用BAPI的过程如果比较慢会拖慢VL02N的过账响应时间。想象一下仓库操作员明明只是做一个PGI结果要等好几秒甚至更久体验会非常差。在数据量大、或者外部系统调用频繁的项目上我会建议改成异步方案。思路很简单BADI里不做BAPI调用只把交货单号、项目、数量、单位、采购订单号插入一张待处理表ZSTO_INB_PENDING状态置为“待处理”。后台安排一个Job每隔1~2分钟扫描一次待处理表。Job调用BAPI创建内向交货单成功后更新状态为“成功”记录内向交货单号失败则保留记录并记录错误信息可以设置重试次数上限。重试超过次数上限的记录发送邮件给IT支持人员人工介入。异步方案的优点很明显PGI过账响应时间几乎不受影响BAPI失败后可以自动重试还能方便地做统计和监控。缺点是架构上多了一张表和一套Job需要额外的开发和运维成本。4. 上线前要检查的配置清单4.1 内向交货单类型与项目类别增强代码写好了不代表就能跑通。我见过不少项目代码逻辑完全没问题但BAPI一调用就报错折腾半天最后发现是后台配置漏了。所以上线前下面几项配置一定要检查。第一内向交货单类型。系统里最常用的是EL内向交货单-采购订单。可以用事务代码VL32N或者后台配置路径“后勤执行-装运-交货-内向交货单-定义内向交货单类型”检查这个类型是否存在交付类型必须设置正确。第二内向交货单的项目类别。在SPRO里配置交货单项目类别要确保EL下的项目类别允许创建内向交货单。如果不确定可以在VL31N里先手工基于一张测试采购订单创建一次看能不能成功。手工能成功再回头查增强代码手工都失败那就是配置问题不用去折腾代码。4.2 采购订单项目类别的“Inbound Delivery”标记这是最容易被漏掉的一项。采购订单的项目类别上有一个“内向交货单”的指示符。如果这个标记没勾上BAPIBAPI_INBOUND_DELIVERY_CREATE会直接报错提示该项目类别不允许内向交货。这个配置在SPRO里的路径大致是物料管理-采购-采购订单-定义项目类别或者叫“设置项目类别”找到NB订单类型的项目类别勾上“内向交货”相关的标识。勾选之后新建的采购订单才会生效。如果客户的STO采购订单已经存在需要检查历史单据是否受影响。如果项目上线前开发环境里已经建了测试PO要记得用ME22N打开单据手工勾选或重新激活项目类别的默认值。4.3 移动类型、库存地点与WM/EWM配置创建内向交货单只是第一步收货的时候还会涉及移动类型和库存地点。公司间STO三步法场景下卖方PGI一般是移动类型643或645买方收货一般用101。这里的101对应的库存类型、是否受批次管理影响、是否需要质量检验都要根据实际业务在后台配置好。如果买方是WM管理的仓库内向交货单创建后系统要能自动生成TRTransfer Requirement或者WM任务。这时候还需要检查WM的Movement Type配置确保内向交货单的收货动作能正确触发WM流程。如果是EWM则要看是否启用了Inbound Delivery Notification的逻辑配置层面要确认ERExpected Receipt能正常创建。4.4 权限、批处理与监控不要小看权限问题。创建一个内向交货单需要相应的权限对象。如果BADI执行用户是后台作业用户或者系统用户要确保该用户对内向交货单的创建、修改有权限否则BAPI会报权限不足。另外如果用了异步方案后台作业的调度配置要在上线前就安排好建议用SM37做一次任务状态检查。日志表的监控报表也要做出来否则出了故障都不知道。我习惯在项目上做一个简单的Z报表或者查询视图IT和关键用户每天上班第一件事看一眼有没有失败状态的记录。5. 常见问题与排查实录5.1 BAPI报错速查表这里把我在项目上实际遇到过的报错整理出来按频率排序报错信息原因分析处理方式PO item category does not allow inbound delivery采购订单项目类别没有勾选内向交货检查SPRO项目类别配置修改后重新创建POPlant 工厂 does not allow inbound delivery工厂级别未激活内向交货功能检查工厂的收货参数开启内向交货Enter storage location交货单项目缺少库存地点在项目数据或物料主数据中维护库存地点Message type EL is not defined内向交货单类型未配置检查OMB2/VL32N的后台配置Authorization missing当前用户无创建内向交货单权限调整权限对象或在后台Job里使用专用用户Quantity 数量 exceeds open quantity过账数量超过采购订单未收货数量检查采购订单历史确认是否有重复过账或PO已被部分收货5.2 重复创建与漏创建重复创建的问题在上线初期比较常见。我排查过好几个案例最后发现都是因为幂等逻辑没做好。比如在BADI里只判断了“是否存在内向交货单”但判断条件写宽了导致逻辑在部分PGI场景下失效。最稳妥的做法就是前面讲的日志表唯一索引配合状态判断。漏创建的问题最常见的触发点是移动类型不对。有些项目的公司间STO没有用643/645而是用了其他的自定义移动类型增强代码里没覆盖到结果PGI是做了但内向交货单压根没触发。排查思路很简单先去MSEG表看这笔PGI的移动类型是什么再回来对代码里的判断条件。另外如果客户有“退货STO”或者“STO的退货”场景发货和收货方向是反的移动类型也可能是反向的。这些特殊场景要在需求阶段就跟业务确认清楚不要默认只处理643/645。5.3 部分PGI、取消PGI等异常场景处理系统里可以做一个程序对比目标数量和已收货数量找到差异后邮件提醒。这个程序也可以作为上线后的长期监控工具。部分PGI的处理是常见需求。外向交货单可以分批过账第一次过50箱第二次过50箱如果代码里每次都取LIPS上的整个项目数量去创建内向交货单第一次就会把数量建满了第二次就会失败或者数量超额。我的处理方式是不要直接取交货单数量而是取本次物料凭证的过账数量MSEG-MENGE。这样每次PGI就创建一张对应数量的小的内向交货单。如果业务希望“累计一张”那就需要改成先查已有内向交货单再更新数量逻辑会复杂一些。我建议优先走“一单一数量”的方式业务上也好对账。取消PGIVL09是另一个容易出问题的场景。PGI被取消后已创建的内向交货单并不会自动消失需要在增强里加逻辑取消PGI时把对应的内向交货单做删除或冲销。这个逻辑可以在BADIMB_DOCUMENT_BADI~BEFORE_UPDATE里处理或者干脆通过日志表后台比对发现PGI被冲销后自动删除对应内向交货单。不管用哪种方式这个场景一定要测试到位。5.4 性能优化与测试建议性能问题通常出现在大批量过账的场景。比如用户用VL10E批量创建了上百张交货单然后集体过账。如果BADI里每张交货单都同步调用BAPI创建内向交货单系统响应会明显变慢。优化手段有两个方向一是把同步改成异步也就是前面说的待处理表后台作业二是如果必须同步可以对交货单做并发控制或者尽量简化代码逻辑减少不必要的数据库查询。我在项目上遇到过那种一个BADI里嵌套了七八个SELECT的代码性能极差后来全部改成一次性查表赋值速度立竿见影。测试建议方面至少要把这些场景覆盖全全量PGI、部分PGI、多次部分PGI、取消PGI、重复过账、多个交货单同时过账、不同公司代码下的STO、不同工厂、启用批次和不启用批次、启用WM和不启用WM。每个场景都要验证内向交货单是否创建成功、数量和单位是否正确、日志是否完整。6. 实操心得与几个建议代码做完了配置也调通了最后分享几点实际操作中的体会。第一个建议增强代码里尽量少用“魔法数字”和“魔法字符串”。比如移动类型643/645、交货单类型EL这些值不同客户很可能不一样最好放到自定义配置表里由顾问在实施时维护。否则换个客户代码里到处要改维护成本很高。第二个建议日志表一定要认真设计。很多项目上线后最终业务方抱怨“不知道这个增强到底干了什么”就是因为开发时没考虑可观测性。日志表里记录清楚时间戳、操作者、入参、结果和报错信息问题定位效率会高非常多。第三个建议不要过度设计。如果客户的场景很少一个月就几百张交货单那老老实实用同步方案代码简单直接反而好维护。不要一上来就整一套待处理表消息队列定时任务复杂度的上升往往会带来新的故障点。第四个建议测试环境里模拟PGI之前先把内向交货单类型EL和采购订单项目类别配置好否则BAPI报错会让你误以为是代码问题浪费大量排查时间。这个增强本身并不复杂真正复杂的往往是业务边界和异常场景。把需求聊透配置检查好代码不一定要写多少行但一定要把日志和幂等做好。希望这篇文章能帮你少走一些弯路项目的STO流程早点跑顺。