ARTICLE DETAIL

资讯详情

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

SAP MM BAPI_RESERVATION_CREATE1外部给号取值与避坑

SAP MM BAPI_RESERVATION_CREATE1外部给号取值与避坑 在SAP MM的接口开发里BAPI_RESERVATION_CREATE1是创建预留时绕不开的标准功能。最近我在弄一个APS系统与SAP的物料预留接口外部系统坚持要由自己生成预留编号也就是所谓的“外部给号”结果就牵出一连串问题这个BAPI到底从哪里取值外部传入的编号系统认不认哪些字段必须一起传踩了几个坑之后我决定把BAPI_RESERVATION_CREATE1外部给号的取值逻辑彻底整理一遍。这篇文章适合正在做MM模块接口、经常面对MES/APS/WMS集成的顾问和ABAP开发也适合刚接触预留业务的初学者——我会把内部给号和外部给号的差异、参数结构、代码实现、以及我实际遇到的坑全部讲透。1. 外部给号到底解决什么问题什么时候必须用1.1 内部给号与外部给号的分界点BAPI_RESERVATION_CREATE1和其他很多主数据类BAPI不太一样它没有类似EXTERNAL_NUMBER这样的开关参数判断内部给号还是外部给号的唯一依据就是你是否在动态内表里填了RESERV_NUMBER这个字段。如果RESERV_NUMBER为空系统走内部编号分配逻辑从标准编号范围对象通常和预留编号范围相关自动取一个预留号然后通过函数的RESERVATION导出参数返回给你。如果RESERV_NUMBER有值系统不会再调用编号范围对象直接用你传入的这个值作为预留抬头编号RSBNR写入预留抬头表RESBK。这个分界点很多新手第一次接触时会困惑因为你去SE37看这个BAPI的参数找不到任何“外部标志位”于是就开始翻文档、问同事“外部给号怎么开”。其实没有开关你传值就是外部给号不传就是内部给号就这么简单。但这个“简单”背后有一个容易踩坑的设计BAPI不对RESERV_NUMBER做唯一的强校验。也就是说它不像内部编号范围那样保证号码一定不重复如果你外部传入的编号已经在RESBK表里存在函数模块本身可能不报错等到COMMIT写入数据库时才可能因为主键冲突出问题。这一点我后面会单独讲。1.2 外部给号的高发场景与选型理由我接触下来的大多数项目用外部给号无非是这几种情况。第一个场景是上下游系统需要共用同一把业务编号。比如MES下发工单到SAPMES已经用自己的工单号在车间排产希望SAP里的预留号就是那个工单号这样现场报工、线边拉动、MES查预留都方便。如果SAP内部自动给号两边就会有一个“预留号”和“工单号”的映射关系要维护每次都要来回转换不出问题还好一出问题对账能对到怀疑人生。第二个场景是数据迁移或历史数据切换。项目上线时要把旧系统里的预留记录迁到SAP为了保持历史追溯一致业务部门强烈要求把原系统的预留编号原样迁入。这种情况你不可能让SAP重新内部给号要么用LSMW/BDC要么用BAPI外部给号BAPI是最受控的方式。第三个场景是第三方系统只认自己生成的编号。例如接口平台或EDI对接时外部传过来的报文里已经包含预留号SAP接到报文后原样创建后续状态回传、删除、修改都靠这个编号关联。使用外部给号可以省掉一层“外部编号转内部编号”的映射维护也减少了接口幂等处理的复杂度。你说内部给号行不行当然也可以无非是多维护一张映射表。但从实际开发和运维角度看外部给号在跨系统集成场景中确实能省不少事。它解决的问题本质上是编号一致性让业务对象在多个系统之间的身份标识保持一致。1.3 预留编号的三种典型取值来源既然要做外部给号那么预留编号从哪来很多开发在这一步就开始拍脑袋以为“外部给号”就是把外部字段随便填进RESERV_NUMBER就完事。实际上预留编号的取值逻辑有三种常见来源你要根据项目实际情况选。**第一种外部系统直接给出编号SAP原样接收。**这类场景最常见例如APS直接把工单号传过来SAP直接用。但要注意SAP预留编号RSBNR是CHAR10也就是最长10位。外部工单号如果超过10位你就得在接口设计阶段决定是截断、映射压缩、还是报错让上游改。我遇到过一家企业工单号带年份加流水号一共12位最后是通过自定义映射表把12位缩成8位两边维护对应关系。**第二种从接口中间表读取编号。**很多SAP接口不走直连而是通过第三方接口平台或自建接口表。外部系统先把数据写到ZMM_RESERV_IN这样的表里SAP后台轮询或者人工触发此时预留编号直接取自ZRESERVE_IN-RESERVE_NO。这种方式的取值逻辑最简单但要特别注意中间表字段类型和长度别把数据库里CHAR12的值塞进CHAR10的BAPI字段否则一样会截断。**第三种外部没有给编号但业务又要求外部叫法。**听起来有点绕但实际很多业务说“我们要自己传编号”结果接口报文里根本没有编号字段编号需要SAP侧生成后再回传。这时我会用自定义号码范围对象Number Range Object在调用BAPI之前用NUMBER_GET_NEXT取一个号再用这个号作为RESERV_NUMBER传给BAPI。本质上是SAP内部给号但号码规则由业务自定义且外部系统可以提前拿到号相当于“半外部给号”。这也是一种很实用的取值逻辑。2. BAPI_RESERVATION_CREATE1参数结构和外部给号核心字段2.1 动态内部表一行一个预留项目的结构BAPI_RESERVATION_CREATE1最让人头疼的是它的输入参数不是普通内表而是一个动态内部表导入参数一般叫T_RESERVATION或者T_REQUIREMENT具体名称在ECC和S/4 HANA的某些版本里可能不一样但行的结构基本都是BAPI_RESERVATION_CORE_TYPE或者说BAPI_RESERVATION_CORE_TYPE1。这个“动态”意味着什么调用前你要么在SE37界面里用“复制”功能把字段清单贴到程序中要么在ABAP代码里动态创建数据结构。大多数项目开发时都会直接用SE37展示的结构字段来定义静态内表比如DATA: lt_reservation TYPE TABLE OF bapi_reservation_core_type, ls_reservation LIKE LINE OF lt_reservation.如果你不是特别确定当前版本参数名打开SE37输入BAPI_RESERVATION_CREATE1看导入参数列表以系统显示为准。我在一个S/4项目里就遇到过旧代码写的是T_RESERVATION新版本里参数已经变成T_REQUIREMENT代码直接激活报错。动态表里每一行对应一个预留项目行可以一次传入多行。如果你关心的是一个预留下面有多个行项目那就多传几行这些行共用同一个RESERV_NUMBER。如果传了不同的RESERV_NUMBERBAPI会一次创建多个独立的预留。这个“按编号分组建预留”的逻辑是理解整个BAPI行为的关键很多问题都出在这。2.2 外部给号必须控制的字段取值逻辑外部给号场景下RESERV_NUMBER是核心但光传编号远远不够。我按照实际项目经验把最常需要赋值的字段整理成一张表方便你对照检查字段名说明取值逻辑RESERV_NUMBER预留编号外部传入决定内外给号方式RESERV_DATE预留创建日期一般取系统日期SY-DATUM也可按外部需求日期RESERV_TYPE预留类型通常和移动类型联动很多项目留空由系统根据移动类型自动带出MOV_TYPE移动类型最常用的是48/49通用预留也可以根据业务用其他移动类型PLANT工厂外部或主数据映射必填STGE_LOC库存地点如果物料做批次管理或库位管理这里很关键MATERIAL物料编号注意内部格式18位物料号可能前面带前导零REQ_QTY需求数量CHAR15字符型需要注意数值转换REQ_DATE需求日期预留需求履行日期影响MRP运行这里我特别想强调MATERIAL和REQ_QTY这两个字段的取值细节。物料号在BAPI里是CHAR18但SAP物料主数据很多是内部格式比如实际物料号是1234567890内部存储可能是00000000001234567890。如果你从外部接口拿到的是不带前导零的字符串直接塞进去也能创建成功但之后查询、显示都可能不一致。稳妥做法是调用物料转换函数或者在读数据时就带上内部格式转换避免前导零问题。REQ_QTY字段是CHAR15不是数值。很多新手直接传10没问题但如果接口表里存的是10.000或者小数点是逗号系统就会解析异常。这个字段和BAPI的整体风格一致——所有值都是字符串外部给号模式下你要自己保证格式和精度。关于RESERV_TYPE我并不建议你照搬某个固定值。不同的移动类型在系统配置里会有对应的预留类型你可以在后台用事务码OMJJ查看移动类型配置或者请教你们的MM顾问。大多数通用预留场景用移动类型48然后RESERV_TYPE留空让系统默认是能正常创建的。但如果你需要做特殊库存预留、销售订单相关预留RESERV_TYPE配合移动类型必须显式赋值否则你可能一路报错。2.3 扩展参数批次、特殊库存、WBS的取值逻辑BAPI_RESERVATION_CREATE1还有一个经常被忽略但非常实用的参数IN_EXTENSION_IN它是BAPI_EXTENSIONIN标准结构每行由STRUCTURE和VALUEPART1~10组成用来传核心动态表里放不下或者不方便放的字段。外部给号场景下比较常见的扩展字段有批次号BATCH、特殊库存标识SPECIAL_STOCK、WBS元素WBS_ELEMENT、销售订单行SALES_ORDER等。什么意思比如你要给一个“代工库存”做预留移动类型工厂库位这些核心字段都填完了但特殊库存标识“O”供应商代管这类字段不进核心结构你就要靠扩展参数传进去。在实际代码里常见的传法是把BAPI_RESERVATION_EXTENSION这个结构的数据放到VALUEPART里比如DATA: lt_extension TYPE TABLE OF bapi_extensionin, ls_extension LIKE LINE OF lt_extension. ls_extension-structure BAPI_RESERVATION_EXTENSION. MOVE-CORRESPONDING ls_reservation_ext TO ls_extension-valuepart1. APPEND ls_extension TO lt_extension.这里ls_reservation_ext就是你按BAPI_RESERVATION_EXTENSION结构定义的一个工作区把批次、特殊库存等字段赋值进去再通过MOVE-CORRESPONDING搬到VALUEPART1。需要注意的是VALUEPART1是CHAR240比结构长所以这种搬法一般不会截断但一定要保证STRUCTURE这个参数名和结构保持一致。我见过很多项目栽在扩展参数上最典型的是“外部编号传进去了预留也建出来了但批次没带过去”后来一查就是扩展参数没传或者STRUCTURE字段名不对系统没报错只是静默忽略了。所以外部给号不只是给一个编号批次、特殊库存这类联动字段的取值逻辑也要一并设计清楚。3. 实操预留创建外部给号的完整实现3.1 动手前先确认这四件事不要一上来就写代码。我在项目里吃过亏外部接口都开发完了联调时才发现预留类型配得不对只能回头改程序。所以动手前先确认以下四类信息。第一移动类型和预留类型。确认你们业务规划用哪个移动类型建预留这个移动类型对应的预留类型是否在后台配置完整。如果拿不准去查MD04或者手动事务MB21里建一张预留测试一下确保手动能成功再用BAPI。第二工厂与库存地点是否有效。特别是跨工厂调拨、代工库存这种场景工厂和库存地点的组合是否允许预留创建库存地点是否已经维护到物料主数据的库位视图。第三外部编号规则和长度。明确外部系统传过来的编号是几位、什么格式、是否需要转换。这一步最好在接口设计文档里写清楚否则开发中途扯皮成本很高。第四扩展字段清单。除了编号还需要外部传哪些字段进来比如批次号、WBS元素、特殊库存标识、销售订单号。这些字段是否需要映射、默认值是什么也要在一开始确定。确认这四件事之后再进入编码你的开发过程会顺畅很多。3.2 关键代码实现与步骤拆解下面我用一个典型的、从中间表读取数据并调用BAPI创建预留的例子完整展示外部给号的实现步骤。这个场景是外部系统先把预留数据传输到中间表ZMM_RESERVE_INSAP端程序轮询处理预留编号直接从中间表取。DATA: lt_reservation TYPE TABLE OF bapi_reservation_core_type, ls_reservation LIKE LINE OF lt_reservation, lt_return TYPE TABLE OF bapiret2, ls_return LIKE LINE OF lt_return, lv_reservation TYPE bapi_reservation_create1-reservation, lv_error TYPE c. 从接口中间表读数据可能存在多条预留行 SELECT * FROM zmm_reserve_in INTO TABLE DATA(lt_in) WHERE status NEW. IF lt_in IS INITIAL. RETURN. ENDIF. LOOP AT lt_in INTO DATA(ls_in). CLEAR ls_reservation. 1. 外部给号核心字段预留编号直接取自接口表 ls_reservation-reserv_number ls_in-reserve_no. 2. 预留创建日期和移动类型 ls_reservation-reserv_date sy-datum. ls_reservation-mov_type ls_in-move_type. 3. 工厂、库位、物料号 ls_reservation-plant ls_in-plant. ls_reservation-stge_loc ls_in-stge_loc. ls_reservation-material ls_in-material. 4. 需求数量和需求日期注意字符型转换 ls_reservation-req_qty |{ ls_in-require_qty }|. ls_reservation-req_date ls_in-require_date. APPEND ls_reservation TO lt_reservation. ENDLOOP. 调用BAPI注意SE37确认参数名 CALL FUNCTION BAPI_RESERVATION_CREATE1 EXPORTING t_reservation lt_reservation IMPORTING reservation lv_reservation TABLES return lt_return. 检查返回消息只要有E类型消息就算失败 LOOP AT lt_return INTO ls_return WHERE type E OR type A. lv_error X. 记录错误日志 ENDLOOP. IF lv_error IS INITIAL. CALL FUNCTION BAPI_TRANSACTION_COMMIT EXPORTING wait X. ELSE. CALL FUNCTION BAPI_TRANSACTION_ROLLBACK. ENDIF.这段代码有几个地方我特别说明一下为什么这么写。RESERV_NUMBER从接口表取到后直接赋给动态内表系统会把它当成外部给号不再走内部编号范围。这里的字段名RESERV_NUMBER必须和结构里的完全一致大小写其实无所谓ABAP不分大小写但拼写错了激活时就能发现。需求数量的赋值方式建议用字符串模板|{ ls_in-require_qty }|这样无论中间表是QUAN类型还是CHAR类型都能转成字符型。如果中间表字段带小数点你还要考虑精度和位数是否符合预留数量的字段定义。我建议数量相关字段在中间表设计时就定义成足够大的CHAR而不是DEC接口开发会省去很多转换痛苦。BAPI_TRANSACTION_COMMIT和BAPI_TRANSACTION_ROLLBACK是配套动作BAPI自己不会提交光调用BAPI成功但没提交等于白做。这是新手写BAPI最常见的通病之一。3.3 提交、回滚与结果校验外部给号模式下即使BAPI返回成功你也别急着返回“成功”给外部系统。我把自己的检查习惯分享给你用CALL FUNCTION BAPI_TRANSACTION_COMMIT之前一定要循环检查RETURN表。光看TYPE字段还不够有的项目会返回W警告但实际已经创建成功有的会返回E阻断错误。我的建议是只把E和A当作失败W可以做日志提示但不回滚。提交后用事务码MB21或SE16N查RESBK表确认预留抬头真的存在。正式接口程序里可以在调用后重新读取RESBK用传入的RESERV_NUMBER去SELECT SINGLE能查到说明创建成功。如果需要给外部系统回传结果回传的预留编号建议用BAPI导出的RESERVATION参数。外部给号时这个参数一般就是你传入的编号但系统可能做了一些规范化处理比如去掉前导空格、转换大小写以它为准更稳妥。我实际项目里会在调用BAPI前把RESERV_NUMBER先CONDENSE一下把前导和尾随空格清掉。因为从接口表读出来的字符型字段经常带着制表符和空格传进去之后系统可能根本不认为这是外部编号转而去内部编号等你对账时才发现两边的号对不上。4. 常见问题与排查技巧实录4.1 三个最容易被忽略的取值错误我在这里列几个反复出现的取值错误都是我或者同事在实际项目中踩过的坑。第一个错误编号字段带了空格被系统当成内部给号。外部传过来的编号看起来有值但字符串末尾有换行符或空格跟空值没区别。BAPI判断RESERV_NUMBER是否为空看的是字段值是否全空格。解决方法是赋值前统一CONDENSE或SHIFT RIGHT去除空格。第二个错误多个预留行只给第一行填了编号。这个我前面提到过同一预留的每个行项目都要带同一个RESERV_NUMBER。很多开发在循环填充动态表时第一行人工赋值了编号后面行漏了结果系统把每个行当成独立预留处理一次调用创建出好几个编号。调这类问题最快的方法是在CALL FUNCTION处打断点看LT_RESERVATION内表里到底几行、每行的RESERV_NUMBER是什么。第三个错误扩展参数字段传错位置。批次传不进预留最终发现是IN_EXTENSION_IN里STRUCTURE字段写成了结构名本身而不是结构标识例如写成了BAPI_RESERVATION_EXTENSION_TYPE系统找不到匹配结构就忽略掉了。严格来说这个参数里要传的是外部扩展结构标识通常和机构的字段结构一致但名称不完全一样最好在SE37里F1看帮助或直接参考已有代码。4.2 外部编号唯一性怎么保外部给号下BAPI本身不检查编号是否重复所以唯一性责任在调用方。这算是我最想强调的一个点。你如果用内部给号编号范围对象帮你挡掉了重复风险一旦切到外部给号没有这层保护就要靠你自己。我自己在正式接口里的做法是双重保险第一层调用前查表校验。在组装完LT_RESERVATION之后把所有RESERV_NUMBER集合起来直接SELECT RSBNR FROM RESBK WHERE RSBNR IN ...如果已经存在要么报错退出要么走一条自定义的冲突处理逻辑。第二层用自定义编号范围对象兜底。外部系统传过来的编号本身就是从我们提供的NUMBER_GET_NEXT接口拿到的所以正常情况下不会冲突。万一外部系统绕过接口自己造号冲突SAP侧还可以用SNRO重新维护一个备用编号段让程序在冲突时自动改用备用号保证预留能创建成功。这里我特别提醒不建议在BAPI报错后再尝试对RESBK做MODIFY因为预留不是只有抬头表还有项目表、需求表、WM/IM相关的联动表手工硬写很容易把数据弄成一堆脏数据。最好的方式就是在调用前就堵住重复编号。4.3 几个实战排查场景排查外部给号问题我通常按下面几步走。第一步确认编号有没有正确进入BAPI。在CALL FUNCTION处打断点查看LT_RESERVATION所有行重点看RESERV_NUMBER是否都等于外部编号。这一步能筛掉80%的问题。第二步确认系统版本参数名。如果你代码在激活时提示参数T_RESERVATION不存在打开SE37看一眼当前版本的导入参数名称。ECC和S/4以及不同Support Package之间这个BAPI的参数名确实有变化但核心逻辑没有变。第三步用MB21手动创建一张相同移动类型、相同工厂/库位/物料的预留如果手动能成功BAPI不行那就是动态表或者扩展参数的问题如果手動也不行那是后台配置的问题别在BAPI上折腾。第四步查RESBK和RESB表。RESBK是预留抬头表RSBNR是预留编号RESB是预留项目表主键RSBNR加行号RSNUM。外部给号创建成功后RESBK-RSBNR应该等于你传的编号。如果这个字段是系统内部生成的编号说明你的外部编号在系统侧被判为“空值”了回去检查字符串里有没有隐藏空格。第五步如果创建后预留看不到但表里又有记录检查RESERV_DATE。预留日期在未来或过去在MB21/MD04查询时显示范围设置太窄也会给人“没建成功”的错觉。4.4 一些只有做过才会注意到的细节最后补充几个“做了几个项目才意识到”的细节。外部给号时如果预留编号用了非纯数字格式比如带有字母R-10001那么你在后续查询时MB21输入框可能默认只接收数字直接输字母会报错叫你用NMB21或去RESBK的维护界面。这个不算BAPI问题但接口联调时经常被业务误报为“系统故障”。关于OUT_EXTENSION_IN这个参数是BAPI执行后返回给外部系统使用的扩展输出参数一些项目用它做预留创建的“回执”把SAP内部处理信息带给外部。如果你需要外部系统在创建后立刻收到预留状态和细节可以把这些字段放到OUT_EXTENSION_IN一起返回比外部系统再查一遍接口表要方便。还有一点是关于接口幂等的。外部给号虽然避免了内部给号的映射问题但如果外部系统因为超时重发同一个预留编号会被重复提交两次。第一次成功第二次会因为编号已存在而失败。只要你的接口做了结果回传就能从返回结果里区分“新建成功”和“已存在”这时候建议把“已存在”当成成功处理直接返回成功避免上游一遍遍重试把日志刷爆。关于这个BAPI我实际做下来最深的体会就是外部给号表面上是个“传编号”的小事实际牵扯到编号规则、唯一性保障、字段映射、回滚提交、接口幂等一整套设计。很多问题不在BAPI本身而在调用方的取值逻辑。最后再分享一个小技巧开发这类接口时把RESERV_NUMBER的统一清洗动作写成一个子程序不管外部传什么乱七八糟的格式先转成CHAR10、去掉空格、必要时统一补成大写后面排查问题会省很多时间。
返回列表