做SAP项目的朋友应该都有体会,尤其是制造业或者外贸零售行业,仓库每天几千行销售订单等着转交货单,光靠VL01N一台台手工点,手指都要点出腱鞘炎,更别提同时还容易把装运点、交货日期填错。所以“根据订单自动/批量创建出库单”几乎是每个SD项目都绕不开的需求,而这块的核心就落在几个关键的函数身上。
这一篇是系列的第二篇。上一篇我们从业务角度梳理了销售订单到交货单的完整链路,也把几个常用BAPI的作用和适用范围做了总览。这一篇更直接一点,我把实际项目中真正用得最频、踩坑最多的几个“创建类”函数逐个拉出来讲,包括参数怎么填、代码大概怎么组织、项目上遇到过的那些奇怪报错怎么排查。如果你是刚接手SD开发的ABAP顾问,或者想搞清楚交货单搬砖逻辑的业务顾问,这篇应该能帮你省掉不少翻SAP NOTE的时间。
1. 先理清楚:从销售订单到出库单,系统到底走了一条什么路
1.1 业务链条先回顾一下
销售订单(VA01)确认后,想要真正把货发给客户,系统里必须存在一张“外向交货单”(Outbound Delivery),也就是咱们常说的出库单。这张单子形成后,仓库才能按它拣货(PGI前拣配)、复核、装运,然后做发货过账(PGI)过账库存成本。
整个链路的触发点就是我们要讲的函数——根据销售订单的数据,让系统自动生成一张交货单。SAP里这块走得是经典的“抬头 + 行项目 + 装运点 + 批次/序列号”多层结构,所以函数也大多不是单一函数搞定,而是“主导函数 + 配套增强 + 后续更新函数”的组合。
很多刚学SD开发的朋友总问一个问题:“既然VL01N能手工创建,为什么还要写函数批量搞?” 实际项目里80%的原因不是替代手工操作,而是要做接口。比如CRM或者自建的电商订单系统推送订单到SAP,SAP创建完销售订单后,需要立刻自动创建交货单扔给WMS去发货,这个动作必须程序化。另外一些周期性的批发订单,一天上千条,也不可能让人在SAP里慢慢点出来。所以,函数列表就是我们在这种场景下的核心工具。
1.2 本期函数清单总览
既然标题叫“函数列表的介绍”,我先把本篇会展开讲到的函数用一张表放在前面,后面逐个用一个一个小节来拆。这样你读到后面对应章节时,脑子里有个整体地图。
| 函数名 | 作用 | 调用时机/适用场景 |
|---|---|---|
| BAPI_OUTB_DELIVERY_CREATE_SLS | 基于销售订单创建外向交货单 | 最核心,普通销售订单、退货单都能用 |
| BAPI_OUTB_DELIVERY_CREATE_STO | 基于STO单据创建外向交货单 | 公司间/跨工厂调拨的STO场景 |
| WS_DELIVERY_UPDATE_2 | 创建后再修改交货单(行项目、数量、批次等) | 交货单需要追加行、改量、拆批次时 |
| WS_DELIVERY_UPDATE | WS_DELIVERY_UPDATE_2的旧版更新函数 | 老项目维护,新项目不建议再用 |
| BAPI_GOODSMVT_CREATE | 做发货过账(PGI) | 交货单确认后冲库存、生成会计凭证 |
| BAPI_SHIPMENT_CREATE | 创建装运单 | 需要整车/物流计划,跟交货单联动时 |
需要提醒一句:你大概率不会只调用其中一个函数就收工。比如你调BAPI_OUTB_DELIVERY_CREATE_SLS创建完,发现数据不对,回头又调WS_DELIVERY_UPDATE_2去改行项目,最后过账又调BAPI_GOODSMVT_CREATE。所以这篇不仅是“单个函数怎么用”,也在帮你建立一套“组合拳”的思维。
2. BAPI_OUTB_DELIVERY_CREATE_SLS:创建出库单的主武器
2.1 核心参数逐个拆
这个函数是SD顾问和ABAP顾问日常打交道最多的一个BAPI,全称顾名思义就是“基于销售订单创建外向交货”。它的导入参数有几组很关键,先说清楚它们分别管什么:
- SALES_ORDER:结构类型 BAPIDLVREF,主要填销售订单号(VBELN)和行项目(POSNR)。注意,如果你只填订单号不填行项目,系统会尝试根据订单的完整交货计划去拆单;有部分行项目不参与交货时,这个字段就非常重要。
- DELIVERY_HEADER:结构类型 BAPISHPDLVUPD,用来填交货单抬头信息,其中最常用的就是SHIP_POINT(装运点)、DELIV_DATE(交货日期)、DUE_DATE(到期日)、FACT_DATE(拣配/实际发货日期)。这些日期如果不传,系统会按销售订单里的计划行默认值带出,但你会经常发现默认值不是用户要的,所以建议显式传值。
- DELIVERY_HEADER_X:这个X结构是SAP BAPI风格的“更新开关”,相当于告诉系统“哪些字段我要改”。很多新项目出问题,就是只填了DELIVERY_HEADER、没填X结构,结果字段更新不进去。
- EXTENSION_IN:BAPIPAREX 类型的内部表,用来传一些自定义增强字段。SD交货单增强一般会用到这个参数,比如挂一些客制化的文本、自定义字段。
- SYNCHRONOUS:同步/异步开关。批量创建时我一般习惯置为“X”同步执行,这样函数返回后立即能拿到交货单号,方便后续处理,省得还得轮询去查。
导出参数里 DELIVERY_NUMBER 是新建成功的交货单号,RETURN是BAPIRETURN结构,里面放着成功消息、警告、报错信息。千万记得:RETURN的类型在成熟ECC和S4版本里有差异,某些版本返回的是BAPIRETURN2(细节信息更多),自己写代码前先SE37看参数列表,别凭记忆硬写。
2.2 一个能直接跑的代码示例
下面这段是我在项目里会用的基础模板,已经去掉具体的公司数据,保留主干逻辑。逻辑上先通过SELECT把销售订单行项目查出来,再循环去创建交货单。
DATA: ls_sales_order TYPE bapidlvref, lt_sales_order TYPE TABLE OF bapidlvref, lt_sales_item TYPE TABLE OF bapidlvreftem, ls_delivery_header TYPE bapishpdlvupd, ls_delivery_header_x TYPE bapishpdlvupdx, ls_extension_in TYPE bapiparex, lt_extension_in TYPE TABLE OF bapiparex, lv_delivery_no TYPE bapishpdlvupd-deliv_numb, lt_return TYPE TABLE OF bapiret2, ls_return LIKE LINE OF lt_return. * 1. 准备销售订单行项目 SELECT vbeln, posnr INTO TABLE @DATA(lt_vbap) FROM vbap WHERE vbeln = @p_vbeln. LOOP AT lt_vbap INTO DATA(ls_vbap). ls_sales_order-vbeln = ls_vbap-vbeln. ls_sales_order-posnr = ls_vbap-posnr. APPEND ls_sales_order TO lt_sales_order. ENDLOOP. * 2. 准备交货单抬头(按项目需求传值) ls_delivery_header-deliv_date = p_deliv_date. " 交货日期 ls_delivery_header-ship_point = p_ship_point. " 装运点 ls_delivery_header-due_date = sy-datum. ls_delivery_header_fact_date = sy-datum. " 拣配日期 ls_delivery_header_x-deliv_date = abap_true. ls_delivery_header_x-ship_point = abap_true. ls_delivery_header_x-due_date = abap_true. ls_delivery_header_x_fact_date = abap_true. * 3. 如果有增强字段,往EXTENSION_IN里丢 * ls_extension_in-structure = 'ZEDLVHEADER'. * ... (对大字段做赋值) * APPEND ls_extension_in TO lt_extension_in. * 4. 调用BAPI CALL FUNCTION 'BAPI_OUTB_DELIVERY_CREATE_SLS' EXPORTING sales_order = lt_sales_order[] delivery_header = ls_delivery_header delivery_header_x = ls_delivery_header_x synchronously = abap_true * extension_in = lt_extension_in[] IMPORTING delivery_number = lv_delivery_no return = ls_return. * 5. 根据返回消息处理成功/失败 IF ls_return-type = 'S' OR ls_return-type = 'E'. IF ls_return-type = 'E'. ROLLBACK WORK. ELSE. IF lv_delivery_no IS NOT INITIAL. COMMIT WORK. ENDIF. ENDIF. ENDIF.代码看着不复杂,但我必须提醒几个细节:
第一,ls_delivery_header和ls_delivery_header_x里各字段的赋值顺序容易乱。很多项目在DEBUG时发现明明传了交货日期但进到VL01N界面时日期是空的,十有八九是忘记传X结构。X结构的作用不是填字段值,而是告诉BAPI“我要用外部值覆盖内部默认值”。这个理解到位,以后调任何BAPI都不会犯迷糊。
第二,RETURN的结构。上面我用的是bapiret2,因为新版本BAPI导出参数已经升级。如果你系统还是老内核ECC,可能要用bapiret。建议在SE37里看一眼函数的“导出参数”页签,以系统实际为准。
第三,COMMIT WORK的位置。如果在循环里调BAPI,不要每个单都COMMIT,一批处理完成后统一COMMIT提交,性能能好很多,事务也更好控制。
2.3 最容易踩到的三个坑
坑一:销售订单虽然存在,但系统提示“没有可交货的项目/交货已冻结”。原因多半是订单行项目未确认、交货冻结、甚至Credit被锁。这时候别急着怀疑BAPI,先去VA03看一眼后台表VBAP里LFGSA(交货冻结)、LET01这些状态,或者用VL01N手工创建一下,看系统到底报什么。
坑二:缺装运点。即便SLS版函数能从销售订单带出装运点,但订单的后台配置“装运条件 + 载重组 + 装运点确定”如果不完整,BAPI也会直接报错。这属于配置问题,开发把问题抛给SD顾问,别自己在那儿瞎猜。
坑三:批量创建时突然崩溃,程序一跑就是十几分钟,最后拿到一堆篇报错。这种情况多半是没控制“SALES_ORDER行项目拆分的数量”和“批次拆分”。比如一张订单有100行,你想创建成一张交货单,结果系统因为“不同装运点”或“不同工厂”自动拆成3张。这个时候不只关注函数入参,还要检查后台“交货拆分规则”。如果业务允许,通常把“仅按工厂”拆分关掉,让所有行都落在一张交货单上。
3. BAPI_OUTB_DELIVERY_CREATE_STO:STO场景的特殊玩家
3.1 STO与普通销售订单的本质区别
先别急着往下看代码,得先说清楚STO是什么。STO(Stock Transport Order,库存转储订单),在SAP里本质是一种特殊采购订单,但又能像销售订单一样触发交货流程。常见于公司间调拨、工厂间调拨、或者“集团内销售”。如果你们项目有总部工厂往分公司仓库补货这种场景,用的就是这玩意儿。
既然本质是采购单,就不能直接用BAPI_OUTB_DELIVERY_CREATE_SLS去创建交货,否则系统压根找不到“销售订单”的对应关系。BAPI_OUTB_DELIVERY_CREATE_STO就是专门为这场景准备的,它接收的是采购订单(EBELN)和行项目(EBELP),而不是VBELN/POSNR,这是和SLS版函数最大的区别。
3.2 关键参数与调用要点
BAPI_OUTB_DELIVERY_CREATE_STO的导入参数也是“抬头 + 行 + 更新开关”这个套路,但抬头和行项目的字段跟SLS版相比多了不少跟采购相关的控制。核心几组:
- DELIVERY_HEADER:同样用BAPISHPDLVUPD,SHIP_POINT、DELIV_DATE这些照填。
- ITEMS / ITEMS_X:结构类型BAPISDITM/BAPISDITMX,这里既包含收货方信息,也包含采购订单号EBELN、行项目EBELP。注意,行项目里必须填REF_DOC(参考单据号),让系统知道你是基于哪张采购订单去建交货。
- REL_STO_CREATE:部分版本里有这个参数,标识是否允许“释放STO后立即创建”。如果这个开关没传对,STO还在审批中,你去创建交货单会被打回。
代码骨架大致是这样:
DATA: lt_items TYPE TABLE OF bapisditm, ls_items TYPE bapisditm. ls_items-ref_doc = p_ebeln. " 采购订单号 ls_items-ref_item = p_ebelp. " 采购订单行 ls_items-ship_point = p_ship_point. APPEND ls_items TO lt_items. CALL FUNCTION 'BAPI_OUTB_DELIVERY_CREATE_STO' EXPORTING delivery_header = ls_delivery_header delivery_header_x = ls_delivery_header_x TABLES items = lt_items * items_x = lt_items_x return = lt_return.我实际用下来的感受是:STO版的函数比SLS版“娇贵”,它对后台配置的依赖更重。比如交货类型、项目类别归属、库存地点找取逻辑,任何一环断了都会在BAPI返回里报很底层的消息(甚至只有E级别消息,但不告诉你具体哪个配置缺失)。所以遇到STO创建失败,先别急着看函数代码,先用VL10B或VL10C把该订单手工转一遍,如果手工也转不出来,那就是后台配置的问题,跟函数本身没关系。
3.3 什么时候用STO版,什么时候用SLS版
有些顾问会在这块犯迷糊,比如项目里用户说“我有一张销售订单,同时又是集团内调拨”,就开始纠结该用哪个BAPI。我的建议就一条:看“交货单单据类型怎么产生”,一切以SO-Path为准。协议模式的集团内销售,后台用的是“销售订单 → 交货单”流程,选SLS;真正的STO流程,是“采购订单 → 交货单”,选STO版。混淆使用最常见的报错是“没有指定订单项目”或“参考单据类型不允许”。
这里放个对比表,一目了然:
| 对比维度 | BAPI_OUTB_DELIVERY_CREATE_SLS | BAPI_OUTB_DELIVERY_CREATE_STO |
|---|---|---|
| 前置单据 | 销售订单(VA01) | 库存转储订单/采购订单(ME21N) |
| 参考字段 | VBELN/POSNR | EBELN/EBELP |
| 典型场景 | 标准零售、批发、退货 | 工厂调拨、公司间补货、跨工厂采购直发 |
| 配置依赖 | 相对低,但要装运点 | 高,涉及STO流程配置 |
| 常见报错 | 交货冻结、装运点缺失 | 找不到项目类别、释放状态不对 |
选函数的本质不是“哪个名字看着像”,而是明白你手里要处理的业务单据到底是什么类型。搞清楚这一个点,SD里的交货单创建函数你基本就通了一半。
4. 创建之后要改要过账:WS_DELIVERY_UPDATE_2 与配套函数
4.1 WS_DELIVERY_UPDATE_2能干什么
很多项目里BAPI_OUTB_DELIVERY_CREATE_SLS创建出来的交货单,行项目数量是“订单计划量”,但到了真正拣货时,仓库发现有两箱破损,只能发95箱。这时候你不能重新删单再建,成本太高,通常做法是直接改交货单行项目数量。这就是WS_DELIVERY_UPDATE_2的出场时间。
这个函数是VL02N的底层更新函数。它跟BAPI的区别在于,它本身不做很复杂的业务校验,更像一个“数据搬运工”:你给它抬头数据LIKP、抬头更新开关LIKP_X,以及行项目数据LIPS、行项目更新开关LIPS_X,它就把这些字段按X结构的指示更新到交货单上。
需要注意,WS_DELIVERY_UPDATE_2这个名字听着像“老函数”,但它并没有被废弃,在S4里仍然能用。只是很多开发在调用时容易漏一个关键参数:IF_LIPS_UPD——它是控制“是否更新行项目”的开关。如果不传,经常出现抬头改了、行没改的诡异现象。所以调用前一定要在SE37里查一下这个函数当前的参数接口,不同版本参数名会有差异。
4.2 发货过账:BAPI_GOODSMVT_CREATE 的配合使用
交货单创建完、数量确认完,下一步通常是发货过账(PGI)。手工做是VL02N里点“发货过账”,程序化做法就是调BAPI_GOODSMVT_CREATE。它其实不是一个交货单专用函数,而是一个通用货物移动函数,但配合交货单时,要往里传三样东西:
- GOODSMVT_HEADER:抬头信息,PSTNG_DATE(过账日期)是必填,DOC_DATE是凭证日期。
- GOODSMVT_ITEM:行项目,特别要注意字段 material(物料)、plant(工厂)、move_type(移动类型)。交货单PGI对应的移动类型通常是601(销售出库)。
- GOODSMVT_CODE:移动代码。这里的GM_CODE要填“04”,表示发货/外向交货。
有人会问,既然交货单上已经有数量、批次、库存地点了,为什么BAPI_GOODSMVT_CREATE还得手工填一遍行项目?因为货物移动函数本身不知道“哪张交货单”,它只知道“有哪些物料要从哪个库位出多少”。所以在调用前,你需要先通过循环交货单行项目把信息填进去,再调用。这也是接口开发时最容易写反的地方:有人觉得调了创建交货单的函数就等于过账了,其实差着一个函数呢。
这一部分我补充一个实用做法:如果创建的出库单不需要单独修改,直接创建后立即过账,可以两段函数连调:
CALL FUNCTION 'BAPI_OUTB_DELIVERY_CREATE_SLS' EXPORTING delivery_header = ls_delivery_header delivery_header_x = ls_delivery_header_x TABLES sales_order = lt_sales_order return = lt_return. IF lv_delivery_no IS NOT INITIAL. PERFORM frm_fill_goodsmvt USING lv_delivery_no CHANGING lt_goodsmvt_item. CALL FUNCTION 'BAPI_GOODSMVT_CREATE' EXPORTING goodsmvt_header = ls_goodsmvt_header goodsmvt_code = ls_goodsmvt_code testrun = abap_false TABLES goodsmvt_item = lt_goodsmvt_item return = lt_return. ENDIF.注意两个函数间不能COMMIT太早。有些项目里程序员习惯创建完立刻COMMIT,这个习惯在单独创建交货单时没问题,但如果后面跟着过账,一定不要中间COMMIT,否则两张单的凭证流可能就断开了,排错特别麻烦。
4.3 函数、更新函数与BDC录屏,三者怎么选
和很多SD顾问聊的时候发现一个讨论点:既然有VL01N录屏(BDC)方案,也能创建交货单,那为什么还要用BAPI?
我的立场是,能用BAPI优先用BAPI,原因是BAPI返回结构清晰、错误处理可控、跳出SESSION事务后不会把屏幕上的错误混在一起,在接口场景下更干净。但有一个例外:当交货单创建过程中涉及大量客制化增强、自定义校验,而这些增强又写在屏幕PBO/PAI里,BAPI可能触发不到,这时候BDC录屏反而更稳。而WS_DELIVERY_UPDATE这种“中间层”更新函数,则更适合在BAPI基础之上做二次修改。
单从维护角度讲,BAPI + 更新函数是目前SAP官方推荐路径,也是我给别人做设计评审时最希望看到的方案。BDC可以作为Plan B,但尽量不要让它成为首选,毕竟录屏一旦碰上菜单调整,脚本经常要重录,维护成本真的高。
5. 实操实录:从订单到出库单的完整实现
5.1 前提配置检查清单
开始码代码之前,我会先过一遍配置检查清单。项目上很多“莫名其妙”的问题,最后都证明根本不是程序问题,而是事前配置不完整。这块东西如果靠BAPI报错去反推,往往要走很多弯路。
第一,检查装运点确定。SE11里看TVAK(销售单据类型)、TVTW(装运点)、TPOP(装运点确定组合),确保订单类型有对应的装运点;否则BAPI创建时直接“未找到装运点”。
第二,检查交货类型。后台SPRO路径:销售与分销 → 装运 → 交货 → 定义外向交货类型。确认销售订单类型对应的交货类型是否存在,例如标准OR对应的交货类型LF。
第三,检查项目类别。从VBAP的PSTYV看是否有对应的项目类别,并确认该项目类别的“.交货相关”字段是打开的。退货单创建出库单时这一步尤其容易挂。
第四,检查自动批次确定。如果你物料启用了批次管理,且交货时自动确定批次,那么后台“批次确定”的搜索过程必须配好,否则创建时系统找不到可用批次直接报错。
第五,检查客户主数据的装运条件。XD03客户主数据销售区域里“装运条件”字段如果为空,交运点确定就相当被动,也可能导致函数报错。
这五条查完,90%的“函数创建不了交货单”问题可以提前规避。别一上来就埋头Debug,先让SD顾问把这些配置点给你过一遍,开发效率能翻倍。
5.2 完整示例:批量读取订单并创建交货单
实际项目里“批量”的需求最多。下面我给你一个相对完整的演示:输入一个销售订单范围,系统循环读取订单行,批量创建交货单,创建失败的行汇总输出日志。
DATA: lt_vbeln_range TYPE RANGE OF vbeln, lv_vbeln TYPE vbeln, lv_delivery_no TYPE vbeln_vl, lt_sales_order TYPE TABLE OF bapidlvref, ls_sales_order TYPE bapidlvref. * 1. 查询待处理订单行 SELECT vbeln, posnr INTO TABLE @DATA(lt_vbap) FROM vbap WHERE vbeln IN @lt_vbeln_range AND lfgsa = '' " 无交货冻结 AND abgru = ''. " 无拒绝原因 * 2. 按订单合并行项目 SORT lt_vbap BY vbeln posnr. LOOP AT lt_vbap INTO DATA(ls_vbap). lv_vbeln = ls_vbap-vbeln. AT NEW vbeln. CLEAR ls_sales_order. * 这里可根据需要初始化抬头参数 ENDAT. ls_sales_order-vbeln = ls_vbap-vbeln. ls_sales_order-posnr = ls_vbap-posnr. APPEND ls_sales_order TO lt_sales_order. AT END OF vbeln. * 3. 对每一张订单执行创建 PERFORM create_outbound_delivery USING ls_sales_order-vbeln CHANGING lv_delivery_no lt_return. ENDAT. ENDLOOP.这里面真正的创建逻辑封装在FORMcreate_outbound_delivery里,里面就是第二节给的那段BAPI代码。封装的好处是日志、错误跟踪、重试图都要好做。
再补充一个项目经验的点:批量程序尽量带TESTRUN。把BAPI或其他函数里的TESTRUN参数利用好,先跑一遍检查,确认没有错误后再正式跑。这样能非常有效地避免批量跑一半、回头要删一大堆错误单的尴尬场面。
5.3 结果验证与日志设计
函数跑完了不等于活干完了。程序一定要把结果写进自建日志表,而不是仅仅输出到ALV就算了。我一般会在自建表里存:销售订单号、行项目、创建出的交货单号、状态、返回消息、时间戳、执行用户。
原因很简单,接口场景中下游WMS/OMS系统需要拿着交货单号做后续动作。如果SAP这边创建完成,但日志没记录,下游查询不到交货单,两套系统直接架起一座断桥。而一旦出现数据不一致,日志表就能帮你快速定位:到底订单创建失败了,还是创建成功但没告诉下游?
另外,针对过账环节,建议在日志里加一个“PGI状态字段”,用来记录交货单是否已经发货过账。很多项目都会用状态字段做自动化重试:比如WMS反馈说仓库没收到,系统可以通过REST接口重新推送这张交货单的过账信息。
6. 常见问题与排查技巧实录
6.1 典型报错速查表
| 报错/现象 | 原因 | 解决方案 |
|---|---|---|
| 订单有交货冻结,函数返回“no items due for delivery” | VBAP-LFGSA字段被冻结 | 清冻结(VL01N里解除或直接更新VBAP),再创建 |
| 未找到装运点 | 配置缺装运点确定 | SPRO配置装运点确定,检查客户主数据装运条件 |
| BAPI返回E但报文是空 | 无库存/批次查找失败 | 查物料库存、批次,配置自动批次确定搜索过程 |
| 创建出的交货单数量不对 | 行项目类别或拆分规则影响 | 检查后台存储过程“定义交货项分类”“定义批量拆分规则” |
| 抬头日期没更新进去 | X结构没传 | 检查DELIVERY_HEADER_X更新开关 |
| COMMIT后找不到交货单 | 调用顺序错或COMMIT提前 | 确保COMMIT在BAPI返回成功后再执行 |
| STO创建失败提示“单据类型不允许” | 用错了BAPI,混用SLS和STO | 确认前置单据类型,使用BAPI_OUTB_DELIVERY_CREATE_STO |
6.2 顾问级避坑经验
我再加几条只有实际项目里摸爬滚打才能攒出来的经验。
第一,能用工作组(Workgroup)就尽量别单步循环。比如需要给3000张订单创建交货,循环里调BAPI,每次调用都要占一个数据库更新周期,3000个循环跑下来很痛苦。你可以考虑分批提交,每100张COMMIT一次,日志也按批次记录,出错时回滚范围更精准。
第二,注意单位换算。BAPI的DELIVERY_HEADER里如果涉及到重量、体积,函数会根据物料主数据自动带出。但如果你从外部系统传入的是“箱数”,那得先根据包装关系换算成基本单位或者销售单位,否则交货单上的数量单位会跟库存单位不匹配,PGI时直接报“数量超过库存”。
第三,批次确定性增强要谨慎。有时用户要求交货单自动带出“最早过期批次”,但标准批次确定策略选不出来,需要写一个增强。这种增强通常在交货单创建BAPI的ExtensionIn里传搜索条件。注意这种逻辑不要硬改成“优先选某个批次”,最好做成可控的“备选策略”,否则呆滞库存全部被优先出掉,财务那边很快就来找你了。
第四,程序里凡是改过交货单,后面想回到VL02N界面修改,系统可能会提示“数据已被外部程序锁定”。这是因为你用WS_DELIVERY_UPDATE_2更新后,没有调用DEQUEUE去释放对象锁。如果遇到这种提示,用SM12看一下锁对象,或者代码里在更新完成后调DEQUEUE_EVVL_LIKP释放。这个坑非常隐蔽,很多人查到死都查不出来。
第五,版本兼容性。S4 HANA的BAPI接口比ECC阶段收敛了不少,比如RETURN类型很多从BAPIRETURN升级到了BAPIRET2。不要在代码里写死“BAPIRETURN”,否则传输到新环境直接激活失败。用前SE37核对是最稳的。
尾声
做SD模块的这几年,我越来越觉得“函数列表”这类内容光看不练是不行的。你背得出BAPI_OUTB_DELIVERY_CREATE_SLS每个参数,不如亲手在系统里调一次,按我说的把X结构漏掉、装运点传空,再有意识地去读一遍返回报文,记忆会深得多。系列写到第二篇,我尽量把每个函数在项目中真正用得上的细节都摊出来讲,但每套系统的配置、增强和用户习惯都不同,所以你落地时肯定还会遇到自己特有的问题。
我自己实际做项目时有个习惯,凡是SD交货单相关的新开发,都会先把手动VL01N操作流程完整走一遍,记录每一步涉及的TCODE、字段、报错;再对照BAPI参数,一个一个映射过去。这个方法帮我避掉过很多坑,也方便我回头跟用户解释“这个函数为什么这么设计”。这套方法同样推荐给准备深入研究函数列表的读者。下一篇我计划讲讲“增强怎么落到创建交货单的BAPI里”,比如批次确定增强、装运点覆盖、自定义字段传递,这些都是接口项目躲不开的硬骨头,到时我们再接着聊。