ARTICLE DETAIL

资讯详情

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

SAP公司间STO:PGI后自动创建内向交货单的BADI实现与配置解析

SAP公司间STO:PGI后自动创建内向交货单的BADI实现与配置解析 开头先讲个我今年上半年在项目上遇到的真实场景两个公司代码之间的库存转储STO发货工厂每天走几十张外向交货单VL02N一发货过账PGI对方兄弟工厂的仓库主管就开始在电话里催——“你们的到货预告呢系统里什么都看不到我仓库排班、收货月台都没法安排。”当时我们查了一圈问题就出在公司间STO流程里少了一环外向交货单PGI之后系统没有自动创建内向交货单Inbound Delivery。其实这个需求在SAP项目实施里不算冷门尤其是上了EWM/WM、或者按ASN管理收货的工厂内向交货单基本就是收货侧的唯一“到货情报”。这篇文章我不讲那些过于零散的功能点而是沿着一条完整的实现路径走一遍公司间STO的单据链路、标准配置能做什么、BADI增强怎么在PGI后自动创建内向交货单、关键字段怎么映射、上线后最容易踩的几个坑。内容偏ABAP和后台配置但思路对所有SAP顾问都有参考价值。1. 单据链路补全先从公司间STO看清“四种单据”的走向1.1 公司间STO到底在跑什么公司间STOStock Transport Order库存转储订单是一个跨公司代码的采购流程。采购方公司代码建一张普通的采购订单但抬头勾选了“公司间”Intercompany标志系统保存时会自动在发货方公司代码生成一张对应的销售订单订单类型通常是UB。这张UB销售订单就是后面一系列交货、开票动作的源头。从主子流程看单据是这样串起来的采购方创建STO采购订单ME21N订单类型NB勾选Intercompany。系统自动生成发货方的UB类型销售订单VA02/VA03能看到。发货方根据UB销售订单创建外向交货单VL10B/VL10C。仓库做发货过账也就是PGIVL02N里点“发货过账”按钮。发货方做公司间开票VF04/VF01采购方收到公司间发票后做发票校验。采购方仓库做收货MIGO 101或者103101。在这个链条里第6步才是货物真正进入采购方工厂的时点。但第4步PGI一旦完成货物在账面上已经属于采购方只是物理上还在路上。这中间就出现了一个“信息真空期”——发运方的外向交货单已经过账收货方却没有任何单据提前通知仓库准备收货。1.2 内向交货单在这个链条里充当什么角色内向交货单Inbound Delivery解决的就是这个“信息真空期”的问题。它在货物还没到工厂之前就给收货仓库提供了一份标准化的到货预告什么物料、多少数量、哪个供应商发的、预计哪天到。收货仓库拿到这张单子可以提前安排月台、人力、质检批次也可以在货物一到就根据内向交货单做收货或转储确认。现实中很多项目在没上EWM的时候并不强制要求内向交货单采购方等货物到了直接MIGO收货即可。但一旦收货仓库上了WM或EWM或者业务有“必须按到货通知安排卸货”的要求内向交货单就是刚需。1.3 为什么“PGI后自动触发”是最顺手的触发点内向交货单的创建时机理论上可以在UB销售订单生成后、外向交货单创建后、PGI后任何一个节点触发。但从我们实际业务验证来看PGI后触发是最合理的UB销售订单刚生成时实际发运的物料和数量还没有确定此刻创建的内向交货单很可能和最终发货不一致。外向交货单刚创建时只代表仓库计划发货不代表货真的发出去。如果凭这张单子创建内向交货单容易出现“说好了发却没发”的虚假预告。PGI一过货物所有权正式转移发运数量、批次、序列号都已经尘埃落定。在这个点触发内向交货单信息最准确业务上最解释得通。所以要实现的核心逻辑就是外向交货单发生PGI的那一刻系统自动、同步地创建一张对应的内向交货单。2. 先翻标准功能OVL3外向交货单类型里的内向交货单配置2.1 标准功能确实存在只是很多人不知道很多ABAP顾问一听到“自动创建”第一反应就是写增强。但在这类STO场景里SAP是提供了标准配置项的只是它藏在比较深的后台配置里加上日常项目里用得少容易被忽略。路径是SPRO → Logistics Execution → Shipping → Deliveries → Define Delivery Types事务代码OVL3进入后找到项目里STO转储实际使用的外向交货单类型通常是从UB销售订单创建的那种但不排除项目自定义类型。双击进入类型详情把滚动条拉到中间字段区能找到一组和内向交货单有关的字段常见的有“内向交货单类型”Inbound delivery type、“更新内向交货单”Update inbound delivery之类的配置项。不同版本SAP字段位置略有差异有的在“General Control”区块有的挪到了其他页签下但用F1帮助搜Inbound Delivery一定能定位到。2.2 标准触发的逻辑和适用边界只要在外向交货单类型里维护了内向交货单类型比如标准的EL当这张外向交货单在VL02N里执行发货过账且状态变为完全过账时系统会自动以外向交货单作为参考生成一张内向交货单。这个功能本质上就是“发货过账以后自动基于外向交货单复制数据”不涉及任何ABAP开发。但我们项目最终没有走这条标准路原因有三个内向交货单的数量、工厂、供应商全部从外向交货单原样复制业务上要求“一张外向交货单拆成多张内向交货单按仓库分发”的需求无法满足。标准逻辑的触发条件是“完全过账”如果一张外向交货单分两次PGI第一次部分过账不会触发必须等第二次全部过账才产生一张全量数量的内向交货单到货预告的时效性就打了折扣。我们收货侧在创建内向交货单时必须把自定义的收货预约号、质检标识一同带过去标准功能没有扩展点。所以如果你的业务恰好就是简单的“一单一单、全量过账、无自定义字段”我建议你直接测一遍标准配置别一上来就写代码能省不少事。如果和我上面说的一样有拆分、合并、自定义字段、部分过账的需求那就进入增强方案。3. 增强落地用LE_SHP_DELIVERY_PROC在PGI后同步建房3.1 为什么选这个BADI而不是隐式增强在确定要写增强之后第一个问题是选增强点。网上能查到的做法五花八门有人用隐式增强直接挂在WS_DELIVERY_UPDATE_2里有人用MV50AFZ1用户出口也有人甚至去改标准程序。我的建议是优先用BADI LE_SHP_DELIVERY_PROC理由是显式增强点后续升级和运维都清晰可控。方法POST_DELIVERY_SAVE的执行时机恰好在外向交货单数据已经保存到数据库、但整个数据库LUW尚未提交的阶段。这个时机做后续单据创建很合适——能读到最新状态也能在同一个LUW里和主业务保持事务一致性。参数里直接给到了更新前后的抬头和项目数据IM_LIKP、IM_LIKP_OLD、IM_TLIPS、IM_TLIPS_OLD判断“这次保存是否发生了PGI”特别方便不需要再去数据库里对比旧值。其他常见的BADI比如LE_SHP_OUTPUT_DELIVERY或一些过账后触发的接口类BADI不是不能用但它们要么侧重输出要么触发时机偏晚对“与PGI同步创建”这种需求来说都不如POST_DELIVERY_SAVE顺手。3.2 BADI实现的核心步骤在SE19里创建一个增强实现选择增强点LE_SHP_DELIVERY_PROC然后方法重定义时选POST_DELIVERY_SAVE。代码逻辑我拆成五步讲。第一步判断单据类型。只有STO转储相关的外向交货单才需要处理。常用的判断字段是LIKP-VBTYP公司间转储交货和工厂间转储交货取值不同项目上可以按实际情况判断。我们是同时兼容了“J”和“6”两种取值避免漏掉流程变体。第二步判断是否发生了PGI。通过对比IM_LIKP-WBSTK货物移动状态和IM_LIKP_OLD-WBSTK来判断。如果当前状态是C完全过账而旧状态不是C说明本次保存动作完成了完全发货过账如果需要支持部分过账就触发则要额外处理状态B的情况。我们最终采用“完全过账才创建全量内向交货单”的方案。第三步防重判断。把所有已建立的内外单对应关系写进一张自定义日志表创建前查表创建后插表保证同一个外向交货单不会被重复触发。第四步组装数据并调用BAPI。从外向交货单抬头和项目中取出物料、数量、销售订单等信息反查STO采购订单再映射到内向交货单的抬头和项目结构最后调用BAPI_INBOUND_DELIVERY_CREATE。第五步提交和写日志。调用BAPI_TRANSACTION_COMMIT提交然后把外向交货单号、内向交货单号、状态、错误消息一并写入日志表。3.3 核心代码骨架下面这段代码是我从项目里裁剪出来的核心逻辑数据结构上做了一些简化但主链路是完整的可以作为参考骨架。METHOD if_ex_le_shp_delivery_proc~post_delivery_save. DATA: lt_return TYPE TABLE OF bapiret2, ls_inb_header TYPE bapiinbdlv_header_in, ls_inb_headerx TYPE bapiinbdlv_header_inx, lt_inb_items TYPE TABLE OF bapiinbdlv_item_in, lt_inb_itemsx TYPE TABLE OF bapiinbdlv_item_inx, ls_inb_item TYPE bapiinbdlv_item_in, ls_inb_itemx TYPE bapiinbdlv_item_inx, ls_inb_out TYPE bapiinbdlv_header_out, ls_lips TYPE lips, lv_po_number TYPE ekpo-ebeln, lv_po_item TYPE ekpo-ebelp, lv_recv_plant TYPE ekpo-werks, lv_vendor TYPE ekko-lifnr, lv_count TYPE i. 1. 只处理STO转储交货单取值按项目实际情况调整 CHECK im_likp-vbtyp EQ 6 OR im_likp-vbtyp EQ J. 2. 仅当本次保存动作触发PGI完全过账时才创建 CHECK im_likp-wbstk EQ C AND im_likp_old-wbstk NE C. 3. 防重检查日志表是否已存在 SELECT COUNT(*) FROM zsto_inb_log INTO lv_count WHERE out_delivery im_likp-vbeln. CHECK lv_count EQ 0. 4. 取交货单第一个项目反查STO采购订单 READ TABLE im_tlips INTO ls_lips INDEX 1. SELECT SINGLE vgbel vgpos FROM vbap INTO (lv_po_number, lv_po_item) WHERE vbeln ls_lips-vgbel AND posnr ls_lips-vgpos. 5. 从采购订单获取收货工厂和供应商 SELECT SINGLE werks FROM ekpo INTO lv_recv_plant WHERE ebeln lv_po_number AND ebelp lv_po_item. SELECT SINGLE lifnr FROM ekko INTO lv_vendor WHERE ebeln lv_po_number. 6. 组装内向交货单抬头 ls_inb_header-delivery_type EL. 内向交货单类型 ls_inb_header-plant lv_recv_plant. 收货工厂 ls_inb_header-purch_no lv_po_number. STO采购订单号 ls_inb_header-suppl_vend lv_vendor. 发货方供应商 ls_inb_header-doc_date sy-datum. ls_inb_header-posting_date sy-datum. ls_inb_headerx-delivery_type abap_true. ls_inb_headerx-plant abap_true. ls_inb_headerx-purch_no abap_true. ls_inb_headerx-suppl_vend abap_true. ls_inb_headerx-doc_date abap_true. ls_inb_headerx-posting_date abap_true. 7. 循环项目组装行项目数据 LOOP AT im_tlips INTO ls_lips. CLEAR: ls_inb_item, ls_inb_itemx. ls_inb_item-material ls_lips-matnr. ls_inb_item-plant lv_recv_plant. ls_inb_item-entry_qnt ls_lips-lfimg. ls_inb_item-entry_uom ls_lips-vrkme. ls_inb_item-po_number lv_po_number. ls_inb_item-po_item lv_po_item. APPEND ls_inb_item TO lt_inb_items. ls_inb_itemx-material abap_true. ls_inb_itemx-plant abap_true. ls_inb_itemx-entry_qnt abap_true. ls_inb_itemx-entry_uom abap_true. ls_inb_itemx-po_number abap_true. ls_inb_itemx-po_item abap_true. APPEND ls_inb_itemx TO lt_inb_itemsx. ENDLOOP. 8. 调用BAPI创建内向交货单 CALL FUNCTION BAPI_INBOUND_DELIVERY_CREATE EXPORTING inb_delivery_header_in ls_inb_header inb_delivery_header_inx ls_inb_headerx IMPORTING inb_delivery_header_out ls_inb_out TABLES inb_delivery_items_in lt_inb_items inb_delivery_items_inx lt_inb_itemsx return lt_return. 9. 提交注意提交时机和调用链见第5章 CALL FUNCTION BAPI_TRANSACTION_COMMIT EXPORTING wait abap_true. 10. 写日志表 INSERT zsto_inb_log FROM ... ENDMETHOD.代码里的SELECT SINGLE从VBAP反查PO号、从EKPO/EKKO取收货工厂和供应商是整段逻辑里最容易被忽略但最关键的地方。如果这一步取错内向交货单建到别的工厂下面后面收货和质检全乱套。4. 数据从哪来外向交货单到内向交货单的关键字段映射4.1 一张表讲清字段的来龙去脉只把代码扔出来是不够的字段映射的逻辑必须讲透。很多新手在这个环节最容易翻车外向交货单里明明有工厂有供应商为什么不能直接复制过去因为外向交货单上的工厂是发货工厂而内向交货单上的工厂必须是收货工厂这两个在跨公司STO场景下是两个完全不同的公司代码。供应商也同理外向交货单的收货方是采购方公司代码内向交货单的供应商是发货方公司代码。所以不能直接复制必须走“反查采购订单”这条路。核心字段映射关系如下内向交货单字段来源说明DELIVERY_TYPE外向内交货单类型如EL按项目配置PLANTEKPO-WERKSSTO采购订单行项目的工厂收货工厂不是发货工厂PURCH_NOVBAP-VGBELUB销售订单行项目反查出的采购订单号通过LIPS→VBAP→EKPO链路SUPPL_VENDEKKO-LIFNRSTO采购订单抬头的供应商发货方公司代码对应的供应商MATERIALLIPS-MATNR物料号直接从外向交货单行项目拿ENTRY_QNTLIPS-LFIMG已过账数量ENTRY_UOMLIPS-VRKME销售单位PO_NUMBERVBAP-VGBEL同PURCH_NO行项目上的采购订单号PO_ITEMVBAP-VGPOS同上链路采购订单行项目号4.2 反查PO号的完整链路这段链路我在项目里踩过坑所以要单独拉出来说。外向交货单行项目LIPS里有两个字段VGBEL和VGPOS这两个字段存的是“参考销售订单号/行项目号”。也就是说对于从UB销售订单创建的外向交货单LIPS-VGBEL本身不是STO采购订单号而是那张自动生成的UB销售订单号。要拿到STO采购订单号得拿着这个UB销售订单行项目去查VBAP表LIPS-VGBEL LIPS-VGPOS → VBAP-VBELN VBAP-POSNR → 取VBAP-VGBEL VBAP-VGPOS → 这才是STO采购订单号和行项目号。这里要注意VBAP-VGBEL这个字段在普通销售订单里一般是空的只有在UB这种“由采购订单自动生成的销售订单”里才会填入源采购订单号。所以这条链路只适用于STO自动生成的销售订单不能套用到普通销售交货流程里。拿到STO采购订单号和行项目号之后EKPO-WERKS就是收货工厂EKKO-LIFNR就是供应商不会再搞错。4.3 部分过账时的数量处理关于数量我建议你在一开始就和业务顾问明确规则是每次过账都增量创建还是完全过账后一次创建。这两种不同的规则拿到LIPS-LFIMG之后的处理逻辑完全不同。我们的做法是“完全过账后一次创建”所以代码里直接用IM_LIKP-WBSTK C作为触发条件行项目数量直接取LIPS-LFIMG。这样建出来的内向交货单和外向交货单是1:1对账关系后续按单收货、按单查差异都很清晰。如果你选“部分过账增量创建”那么需要在代码里从IM_TLIPS_OLD取上一次过账的数量用本次过账数量减去旧数量得到增量数量再传给内向交货单项目。逻辑不复杂但比完全过账方案多一层数量计算而且同一个外向交货单会对应多张内向交货单后续对账容易乱。除非业务有强需求否则我建议优先做完全过账方案。5. 实战排雷重复创建、部分过账、批量PGI与提交时机5.1 坑一同一个外向交货单被重复创建我们在集成测试时就遇到过同一个外向交货单在VL02N里点了两次发货过账结果生成了两张一模一样的内向交货单。原因是POST_DELIVERY_SAVE这个增强点在单据保存时就会被调用如果第一次调用时创建失败但事务仍然提交了第二次操作时就会重复触发。解决办法就是日志表防重这也是我代码里第三步必须存在的原因。而且日志表插入动作要放在创建成功之后不能放在创建之前——否则事务回滚了日志还在单向交单根本没有生成系统就永远不会再补。很多项目出现“漏单”就是这个顺序写反了。5.2 坑二判断PGI状态不能只看当前值只看IM_LIKP-WBSTK等于C就触发有可能会在“一张已经过账过的旧单子被修改重新保存”时重复创建。所以必须同时比较IM_LIKP_OLD-WBSTK。只有当旧状态不是C、新状态是C才能断定“本次保存动作完成了从部分过账或未过账到完全过账的跃迁”。同理如果一张单子已经被完全过账之后因为某种原因被冲销再重新过账旧值和新值会再次满足“不等于C到等于C”的条件。这时候日志表防重就派上用场了——只要这张外向交货单已经关联过内向交货单就不管它状态怎么变都不再自动创建。至于冲销场景要不要生成一张新的负向内向交货单那属于另一个业务需求不在本文讨论范围但要提前和业务确认清楚。5.3 坑三批量PGI时的性能和事务边界VL02N支持批量过账项目里常用的方式有VL60统一发货过账、BDC批导、或者直接来回写BAPI_OUTB_DELIVERY_CONFIRM_DECOMM的批处理程序。这些场景下一次运行会连续处理几十甚至上百张外向交货单POST_DELIVERY_SAVE也会被调用同样多次。第一个风险是性能。我们的增强里虽然有SELECT反查采购订单但几十张单子还好如果一次几百张能明显感觉到过账变慢。解决思路是减少循环内查询把VBAP、EKPO、EKKO的查询改成批量SELECT用FOR ALL ENTRIES或者一次选出所有需要的PO号再按行项目填充避免每张单子都走三次数据库往返。第二个风险是提交冲突。在POST_DELIVERY_SAVE里调用BAPI_TRANSACTION_COMMIT会立即提交整个LUW。在批量PGI程序中这个提交点可能与外层程序的提交点冲突严重时会出现“数据库更新被回滚但程序还在继续跑”的诡异现象。稳妥做法是如果项目批量过账用的是自定义程序尽量在调用过账BAPI之后统一处理内向交货单不要在BADI里各自提交。如果只能在BADI里做至少要加一个会话级锁或内存标志防止同一个调用链里重复提交。5.4 坑四VBTYP判断条件过严导致漏单我在代码里写的是im_likp-vbtyp EQ 6 OR im_likp-vbtyp EQ J但不同项目的STO链路配置不同有的公司间STO走的是标准UB订单生成的外向交货单VBTYP确实是6但有的项目把UB订单改了订单类型或复制了一份生成的VBTYP可能就变成自定义值了。上线前最好用生产数据扫一遍实际外向交货单的VBTYP确认判断条件全部覆盖不要想当然。顺便说一个配套的坑如果业务流程里还混着普通销售发货、退货、借项单等交货单增强里一定要把这些单子的VBTYP排除掉。否则某个渠道的销售订单PGI时也跑去创建内向交货单收货工厂都取不到日志表里全是错误记录。5.5 坑五EWM/WM对内向交货单的“二次处理”如果收货工厂启用了EWM内向交货单创建之后还会通过CIF传输到EWM侧由EWM的PPF动作做后续的车次排定、仓位分配、收货处理。这种情况下单纯在ECC里调用BAPI_INBOUND_DELIVERY_CREATE还不够还要确保相关自定义字段、批次、序列号都提前放到内向交货单里否则CIF传输过去之后EWM侧拿不到完整数据照样没法收货。WM相对简单一些内向交货单创建后可以触发WM的TR转储需求但前提是在内向交货单类型里配置了WM相关的移动类型和转储单创建逻辑。这些都建议在上线前用一张真实物料跑通全链路不要等到生产环境再验证。6. 上线前最值得做的一步手工补单机制与内外向单据勾稽6.1 为什么一定要有补单机制再健壮的增强也有出错的时候。后台作业卡住、BAPI报错、数据不一致、人为改单……都可能导致某个外向交货单PGI后没生成内向交货单。如果没有一套补单机制业务人员只能干等影响收货安排。我们项目的做法是做了一张自定义日志表ZSTO_INB_LOG字段大致是外向交货单号、内向交货单号、STO采购订单号、销售订单号、发货工厂、收货工厂、创建日期时间、状态、返回消息。增强里每处理一张单子都会往这张表里写一条记录成功也好失败也好都留痕。然后基于这张表写了一个简单的报表程序ZR_STO_INB_REBUILD查询条件包括外向交货单号、日期范围、状态。报表能把所有“已过账但日志表里没有成功记录”的外向交货单列出来选中之后重新走一遍创建逻辑。这个报表在UAT阶段和上线初期几乎天天用是把数据拉回正轨的关键工具。6.2 内外向单据怎么勾稽和核对日志表的勾稽价值平时不明显一旦财务或者物流要按月对账它的作用就出来了。我们每个月末会跑一张对账清单左边是外向交货单的过账数据带出PGI日期、数量、销售订单号右边是内向交货单的创建数据带出创建日期、数量、STO采购订单号按STO采购订单号加物料做SUM对比。数量对不上就说明中间丢单了直接去日志表查失败记录定位很快。另一个有用的勾稽字段是销售订单号。公司间STO里一张UB销售订单可能对应多个STO采购订单行项目但销售订单号本身是唯一的。把销售订单号同时记在日志表里以后无论从外向侧还是内向侧追溯都能快速找到对应的整条单据链。6.3 最后的建议能配置不增强能增强不硬编码这个项目完整走一遍下来我最大的体会是遇到“PGI后自动创建内向交货单”这类需求先花半天时间把标准配置研究明白再决定要不要写代码。标准功能能解决的场景虽然有限但它能帮你验证业务流程是否走得通也能帮你把增强的逻辑定义得更准确。很多团队放着OVL3里现成的内向交货单类型配置不用上来就写BADI结果写出来的逻辑还要处理标准功能本来就能规避的边界情况白白增加了运维成本。另外补单机制一定要在开发阶段就设计进去不要等项目上线出问题了再来加。日志表不影响主流程又能通过报表随时对账是所有自动触发类增强里性价比最高的一层保险。这个增强方案本身并不复杂真正的复杂度在业务规则的确认和边界情况的处理上。如果你们项目也有类似需求建议先拉着物流、财务、仓库把“什么时候创建、数量怎么算、要不要合并、失败怎么补”这四个问题说清楚再动手开发后面会顺很多。
返回列表