ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

SAP ABAP开发:用BAPI_PO_CREATE1批量创建委外采购订单

SAP ABAP开发:用BAPI_PO_CREATE1批量创建委外采购订单 1. 项目概述1.1 核心需求解析做SAP运维或者开发的同行对“委外采购订单”这个名字绝对不陌生。它和普通采购订单最大的区别在于你不是直接买一个现成的成品而是把原材料发给供应商让供应商加工成半成品或者成品再送回你的仓库。这个过程中原材料的所有权还是你的供应商只收加工费。所以系统里既要记录发给供应商的材料明细又要记录最终收回的物料和加工费。我这次接到需求时用户在电话里说得挺直接“我们现在的委外订单都是手工在ME21N里面敲一天几十张业务量上来之后根本忙不过来能不能用程序批量生成”我问他生成逻辑是什么他说从Excel表格里读取物料号、供应商、数量、价格和交货日期程序自动把订单建好再根据订单号去跑一个后续的收货和发料。流程听起来不复杂但是真正动手之后发现坑都在细节里。这个场景下BAPIBusiness Application Programming Interface业务应用程序编程接口是绕不开的方案。SAP针对采购订单场景提供了标准的BAPI_PO_CREATE1老版本里也常见BAPI_PO_CREATE但前者在功能覆盖和参数结构上更完善。用BAPI的好处是所有校验逻辑都由SAP底层控制比如供应商主数据是否存在、物料是否允许委外采购、采购信息记录的价格是否有效你在程序里只需要把参数传对剩下的交给系统判断比直接往数据库表里插数据要安全得多也符合标准的ABAP开发规范。1.2 委外订单的业务特殊性很多人第一次接触委外订单会觉得它和普通订单没什么区别无非多填一个“物料提供给供应商”的动作。但真正操作过就会发现委外订单的结构比普通订单复杂不少主要体现在两个层面。第一个层面是订单头信息和行项目信息。订单头里有供应商、采购组织、采购组、公司代码、货币类型这些基础字段行项目里有物料号、数量、交货日期、工厂、库存地点、价格、税代码。这些和普通订单是一样的也是BAPI_PO_CREATE1最基础的一批参数。第二个层面就特殊了是BOM组件行项目。委外订单的每一行成品/半成品物料背后都挂着一个BOM系统需要根据这个BOM自动展开生成一份子件清单这就是发给供应商的原材料/零配件明细。在ME21N里这部分工作是在“物料组件”标签页里体现的点开之后能看到每个组件对应的物料、数量、工厂和库存地点。这些组件信息就是用BAPI创建委外订单时最容易被忽略、也最容易出错的部分。如果组件行项目没生成或者数量不对后面的业务动作全都会乱套。收货的时候系统会提示找不到组件MIGO发料时又没有足够的预留量整个供应链流程直接被卡住。所以做这个需求之前一定要先弄清楚一个核心问题系统到底是根据什么逻辑来展开BOM、生成组件行的我把这个逻辑放在后面单独讲因为它是整个方案能不能跑通的关键。2. BAPI选型与方案拆解2.1 为什么选BAPI_PO_CREATE1而不是其他方式SAP里创建采购订单的接口其实不止一个除了BAPI_PO_CREATE1还有BAPI_PO_CREATE以及更老一些的函数ME_CREATE_PO和直接调BAPI的变体BAPI_PO_CREATE2。实际项目里我基本不会去纠结直接用BAPI_PO_CREATE1理由有三个。第一BAPI_PO_CREATE1的入参结构最完整几乎覆盖了ME21N界面上所有可以手工填写的字段。从订单头到行项目再到计划行、交货地址、科目分配、文本、附件全都有对应的参数而BAPI_PO_CREATE在功能上相对精简有些扩展字段根本传不进去后续想做增强还得额外出去翻函数。第二它的扩展机制非常成熟。SAP在BAPI里提供了一个叫EXTENSIONIN的参数表专门用来传标准结构之外的自定义字段。比如用户要求在采购订单行上维护一个内部的项目负责人编号标准参数里没有这个字段我就可以通过EXTENSIONIN把这个字段塞进BAPI的更新逻辑里。虽然SAP官方建议优先使用BAPI专用增强结构但EXTENSIONIN在许多老项目里依然是必经之路因为它兼容性最好对代码改动最小。第三BAPI_PO_CREATE1在调用之后会返回完整的采购订单号这个订单号可以直接拿来作为后续BAPI_GOODSMVT_CREATE收货/发货过账的参数形成一条完整的业务闭环。后面的MIGO操作都能以这个订单号为主键串联起来数据一致性也有保障。2.2 创建前必查的配置与主数据哪怕你把BAPI的参数传得再完美如果之前的基础数据没准备好系统照样报错。我印象里最典型的一次程序跑起来之后SAP一直提示“物料的采购类型不允许”排查了半天发现物料主数据里“采购类型”字段设置成了“自有生产”根本没开委外采购。这些坑看着低级但是特别容易在项目上线初期集中爆发。所以在开发之前我建议把下面几类数据全部核查一遍可以做成一个检查清单供应商主数据供应商代码在对应采购组织下是否存在是否维护了采购视图物料主数据物料是否启用了“委外加工”的采购类型是否有有效的BOMBOM是否在工厂下是激活状态采购信息记录价格控制方式是什么是否维护了有效加工费有效期是否覆盖了订单日期税代码行项目里必须传税代码比如J1国内采购如果不传BAPI通常会因为税确定失败而直接报错工厂和库存地点组件行项目里的发料工厂和库存地点必须是系统中存在的而且物料在这个库存地点下要有账面库存BOM展开相关参数BOM展开的工厂一般是订单工厂和用途一般是1生产/采购要配置正确。这些检查看起来繁琐但能消灭掉一大批低级报错。我在开发程序的早期版本里没有做全量检查结果用户测试的时候一个上午报出几十个错误后来我把上面这六个检查项全部加到程序日志里每次创建前先自动核查不符合条件的订单直接不创建把具体原因写到日志里业务人员照着日志去补主数据效率提升了一大截。这也是我强烈建议每个做BAPI开发的人都养成的好习惯宁可程序里多写几行校验代码也不要让用户面对一堆看不懂的SAP报错信息。2.3 委外订单组件行的生成逻辑委外订单最有意思的地方在于组件行项目不是直接传进去就能保存的它背后的逻辑链路很长而且在不同版本的SAP里表现不太一样。初次接触这个功能的同事经常被“组件数量翻倍”“组件行多了几行”这类问题绕晕。先讲标准流程。BAPI_PO_CREATE1在接收到“这是委外订单”的信号之后会根据行项目的物料号去自动查找并展开BOM。展开之后的每个BOM组件会生成一个“物料组件”行这个“组件行”在SAP里属于采购订单行项目类别SUBSubcontracting对应的组件数量还要根据BOM数量和采购订单数量做换算。举个例子假设BOM里定义生产1件成品A需要用到2件原材料B和3件原材料C现在采购订单要采购10件成品A那么在组件行里B的数量就是20C的数量就是30。这个换算逻辑是系统自动完成的前提是你传入的BOM用量是基础数量1而且采购订单行项目里没有手动覆盖数量。如果业务上希望绕过BOM展开而是手工指定组件那也可以。你可以在调用BAPI之前把组件行项目的数据直接构造出来传给BAPI的ITEM表SAP会根据你传的数据当作已有的组件行来处理。但这样做有一个很大的风险一旦你传的组件数据不完整或者数量不对后面收货环节的预留和发料就会对不上账。所以我的方案是优先让系统按BOM自动展开只有在系统展开结果不满足需求的时候才走手工覆盖的逻辑。自动展开有两个天然优势第一是降低了程序里构造数据的复杂度第二是保证了组件数量和BOM一致性后续MRP跑出来的预留也能对齐。2.4 关键参数结构一览表BAPI_PO_CREATE1的参数结构里委外场景最常用的是下面几组参数名称用途说明委外场景下的关键点PO_HEADER订单头信息传入供应商、采购组织、采购组、公司代码等PO_ITEM行项目信息物料号、工厂、数量、税代码、价格PO_ITEMSUB行项目补充字段BAPI_PO_CREATE1专用用于存ITEMSUB字段如科目分配类别PO_SCHEDULE计划行信息交货日期、交货数量PO_COMPONENTS物料组件行组件物料号、组件数量、工厂/库存地点、项目类别POACCOUNT科目分配成本中心/内部订单/资产等常规订单一般不传委外可根据业务传RETURN返回消息存报错信息E类型会阻止BAPI提交EXTENSIONIN扩展字段存自定义字段需要配套写增强逻辑这里需要特别提醒一件事BAPI_PO_CREATE1的参数名和BAPI_PO_CREATE略有不同比如BAPI_PO_CREATE里的组件表叫PO_COMPONENTS老版本的BAPI_PO_CREATE里也有同名参数但是字段结构有细微差别。不要以为字段名称一样就能直接套用实际开发时一定要先检查函数模块的导入参数FULLSTRUCTURE看看你的版本支持哪些字段。这是我在一次项目迁移时亲身体会到的坑当时把老代码直接搬到新系统结果WB01字段死活传不进去后来一查才发现新版本的参数结构里改成了其他字段名。3. 核心实现基于BAPI_PO_CREATE1的完整解析3.1 最基本的调用框架直接贴代码最实在。下面是我在项目里常用的一个标准调用框架这个框架处理了订单头、行项目、计划行和组件行四个层面的数据。代码写得不复杂重点在于理解每个字段什么时候赋值、什么时候留空。DATA: ls_poheader TYPE bapimepoheader, ls_poheaderx TYPE bapimepoheaderx, lt_poitem TYPE TABLE OF bapimepoitem, ls_poitem TYPE bapimepoitem, lt_poitemx TYPE TABLE OF bapimepoitemx, ls_poitemx TYPE bapimepoitemx, lt_poschedule TYPE TABLE OF bapimeposchedule, ls_poschedule TYPE bapimeposchedule, lt_poschedulex TYPE TABLE OF bapimeposchedulex, ls_poschedulex TYPE bapimeposchedulex, lt_pocomponents TYPE TABLE OF bapimepocomponent, ls_pocomponents TYPE bapimepocomponent, lt_return TYPE TABLE OF bapiret2, ls_return TYPE bapiret2, lv_po_number TYPE bapinekko-belnr, lv_po_item_number TYPE bapimepoitem-po_item. 1. 订单头 ls_poheader-doc_type NB. 标准采购订单 ls_poheader-vendor lv_vendor. ls_poheader-purch_org lv_purch_org. ls_poheader-pur_group lv_pur_group. ls_poheader-comp_code lv_comp_code. ls_poheader-currency CNY. ls_poheader-langu sy-langu. ls_poheader-pmnttrms 0001. 委外订单标记 ls_poheader-pstyp F. 委外加工 ls_poheaderx-doc_type X. ls_poheaderx-vendor X. ls_poheaderx-purch_org X. ls_poheaderx-pur_group X. ls_poheaderx-comp_code X. ls_poheaderx-currency X. ls_poheaderx-langu X. ls_poheaderx-pmnttrms X. ls_poheaderx-pstyp X.这个地方的PSTYP字段很重要标准采购订单默认是NB但委外加工必须传F。如果这里漏了后面组件行业务根本没有机会被触发这是在很多项目里都会遇到的第一道坎。接下来是行项目和计划行的数据填充。行项目里的字段比较多这里只挑委外场景下最重要的几个讲完整的字段清单可以参照SAP帮助文档。 2. 行项目 ls_poitem-po_item 00010. ls_poitem-material lv_material. ls_poitem-plant lv_plant. ls_poitem-quantity lv_po_qty. ls_poitem-orderpr_un PC. ls_poitem-net_price lv_price. ls_poitem-tax_code J1. ls_poitem-po_unit lv_po_unit. ls_poitem-item_cat 0. 空/0表示标准ITEM委外组件是系统自动生成 ls_poitem-acctasscat U. U 未知常规库存物料如果是科目分配类需按业务调整 ls_poitem-mvt_st E. 移动类型E表示库存转移相关 ls_poitemx-po_item 00010. ls_poitemx-material X. ls_poitemx-plant X. ls_poitemx-quantity X. ls_poitemx-orderpr_un X. ls_poitemx-net_price X. ls_poitemx-tax_code X. ls_poitemx-po_unit X. ls_poitemx-item_cat X. ls_poitemx-acctasscat X. ls_poitemx-mvt_st X. 3. 计划行 ls_poschedule-po_item 00010. ls_poschedule-deliv_date lv_delivery_date. ls_poschedule-quantity lv_po_qty. ls_poschedulex-po_item 00010. ls_poschedulex-deliv_date X. ls_poschedulex-quantity X.行项目里的ITEM_CAT在委外场景下也要特别说明很多人在这里会把它传成T文本项目或者L库存项目结果组件展开直接失败或者创建出来的订单在界面和标准行为不一致。标准委外物料用ITEM_CAT0或者留空都是可以的但如果你在ME21N里看到的项目类别是“L”其实那是系统在保存后显示的值不是传入值。好记的方式是程序里传0保存后系统会转换成它认为正确的显示值。3.2 处理组件信息到了组件信息这一步就是委外订单和普通订单拉开差距的地方了。BAPI里对应的参数是PO_COMPONENTS这个参数的值来自BOM展开。我在程序里的处理逻辑是先用CS_BOM_EXPL_MAT_V2这个BOM展开函数把BOM数据读出来然后逐行写入PO_COMPONENTS。这里要注意几个字段的映射关系。 4. 组件行 —— 从BOM展开结果填充 DATA: lv_bom_item TYPE i VALUE 10. LOOP AT gt_bom_data INTO gs_bom_data. CLEAR ls_pocomponents. ls_pocomponents-po_item 00010. ls_pocomponents-sched_line 0001. 计划行编号建议按实际计划行填 ls_pocomponents-item_no gs_bom_data-posnr. 组件在BOM中的行号 ls_pocomponents-material gs_bom_data-idnrk. 组件物料号 ls_pocomponents-quantity gs_bom_data-menge. 组件数量需乘以比例 ls_pocomponents-plant lv_plant. ls_pocomponents-req_date lv_delivery_date. ls_pocomponents-requirement X. APPEND ls_pocomponents TO lt_pocomponents. ENDLOOP.这一段里有个细节特别容易出错ITEM_NO这个字段。在BAPI_PO_CREATE1的组件表里ITEM_NO表示的是“BOM组件在订单里的组件项目编号”不是订单的行项目编号。如果你不传SAP会按顺序自动生成但如果你传入的值和BOM里的行号不一致后面查询的时候会有点乱。我建议直接把BOM展开结果里的POSNR字段映射过来保证它在订单里唯一即可。另外组件行里还经常需要传ITEM_CAT字段。在BOM展开出来的数据里组件在委外订单里显示的项目类别通常是L但传入BAPI时你也需要显式指定否则系统有时会根据物料类型自动推导有时会直接报“项目类别缺失”的错误。我的经验是组件行的ITEM_CAT统一传L这个值和ME21N界面上看到的“物料组件”项目类别是对应的。关于数量换算如果BOM的基础数量是1那么组件数量直接用BOM组件数量乘以订单数量就能得到。但如果BOM的基础数量不是1比如BOM定义生产10件成品A需要5件材料B那组件数量的计算方式是5除以10再乘以订单数量这个逻辑不能拍脑袋写死一定要从BOM展开结果里把基础数量取出来做除法。我在项目里见过几次因为这个数量换算写错导致委外发料数量偏差的案例最后全是生产盘点的时候才暴露出来处理起来特别麻烦。3.3 调用BAPI、检查返回并COMMIT数据都填充完就该调用BAPI并处理返回结果了。这里也是很多初级开发者容易忽略的地方。BAPI和直接写表不一样它需要你“手动”管理数据库事务也就是调用BAPI_TRANSACTION_COMMIT之后数据才真正提交到数据库。如果漏了COMMIT程序显示“成功”了但数据库里查不到记录这个坑我见过不下五次。CALL FUNCTION BAPI_PO_CREATE1 EXPORTING poheader ls_poheader poheaderx ls_poheaderx IMPORTING exppurchaseorder lv_po_number TABLES return lt_return poitem lt_poitem poitemx lt_poitemx poschedule lt_poschedule poschedulex lt_poschedulex pocomponents lt_pocomponents. 检查返回信息 READ TABLE lt_return WITH KEY type E TRANSPORTING NO FIELDS. IF sy-subrc 0. 创建失败输出错误日志 LOOP AT lt_return INTO ls_return WHERE type E. WRITE: / ls_return-message. ENDLOOP. ELSE. 创建成功提交事务 CALL FUNCTION BAPI_TRANSACTION_COMMIT EXPORTING wait X. WRITE: / 采购订单创建成功, lv_po_number. ENDIF.注意RETURN表里的消息类型比我想象的要多。除了E错误和S成功还有W警告和I信息很多时候BAPI没有被E类型卡住但W类型里可能藏着隐患比如“价格有变动请核实”“交货日期被系统自动修正到了其他地方”之类的提示。我一般会把W类型消息也记录下来输出到日志文件里让用户创建完之后人工抽查一遍防止订单在“成功”的状态下带着隐藏问题进入后续流程。另外BAPI_TRANSACTION_COMMIT的WAIT参数非常关键如果WAITX系统会等待数据库事务真正提交完成如果WAIT为空BAPI可能只是把数据发到一个逻辑队列后续程序立刻去根据订单号查询时有可能查不到。在做这种“创建完后立刻继续操作”的场景时WAITX是必须的。3.4 真实案例Excel批量导入生成委外订单光说框架不够我再放一个实际跑过的案例。用户给了一张Excel表大概三四百行每一行代表一条委外采购需求。表的字段有物料号、数量、交货日期、供应商、加工费单价。要求是批量创建委外PO创建成功后把PO号回写到Excel里。我的实现思路是这样先从Excel里把全部数据读入内表然后按照供应商分组。同一个供应商的多行需求我合并到同一个采购订单里不同行项目共用同一个订单头。这样的好处是减少了订单数量后续收货和结算也集中。当然如果业务强制要求一单一物料那就一个物料一个订单这个要根据用户的流程来定不是开发说了算。读取Excel用的是ALSM_EXCEL_TO_INTERNAL_TABLE这个函数或者也可以用OLE2.0的方式直接读Excel对象。前者简单适合处理列结构固定的表后者灵活适合需要读取格式的复杂场景。这个需求比较简单直接用ALSM就够。接下来的核心逻辑就是这个循环处理的过程。我贴上关键的伪代码实际代码里还有一些容错处理但主流程就是下面这个结构SORT gt_excel BY vendor. LOOP AT gt_excel INTO gs_excel. AT NEW vendor. 新供应商初始化订单头和行项目数据 PERFORM prepare_po_header. ENDAT. 填充行项目 PERFORM fill_item. AT END OF vendor. 供应商分组结束调用一次BAPI_PO_CREATE1 PERFORM create_po. ENDAT. ENDLOOP.在创建完每张订单之后我建议顺手把BAPI返回的采购订单号连同原Excel数据写进一张日志表可以用自定义的Z表方便以后出了问题做追溯。单纯依赖SAP应用日志总是不够直观用户A用户B在同一个时间点跑了程序出了问题要查是谁传的、原始数据是什么一张Z表比什么都方便。这是我在很多项目里慢慢养成的一个小习惯后面找问题的时候能省很多力气。4. 常见问题与排查技巧实录4.1 组件行项目没有自动生成这个问题在委外订单开发中出现的频率极高。BAPI执行成功订单行存在但是在ME23N里打开订单下面的“物料组件”标签页是空的或者组件行数量对不上。排查思路首先看PSTYP。在调试模式里看一下PO_HEADER-PSTYP最终传进去的值如果传到BAPI里面变成了空或者NB组件直接不会展开。其次是看BOM是否有有效版本。我在调试过程中经常发现物料明明有BOM但BOM的有效期只到上个月系统一检查日期就直接跳过组件当然不会生成。最后还要确认物料的主数据里“BOM/Material”视图是通的有没有分配BOM给对应工厂如果BOM只建在集团层而没有分配到工厂展开一样是空的。如果上面的检查都做了还是不行就去看组件表里传入的ITEM_NO和SCHED_LINE是否有问题。有一个我踩过的坑是计划行编号填错BAPI返回成功但组件行全挂在别的地方在ME23N里怎么都看不到后来我打开计划行页签才发现组件被挂到了第二个计划行下面。4.2 组件数量翻倍或变成0有次测试的时候用户反馈一个有趣的现象组件行数量是订单数量的好几倍。我当时第一反应是“BOM基础数量不是1”但查完之后发现基础数量确实是1问题出在传入PO_COMPONENTS的QUANTITY字段时我把BOM展开出来的原始用量直接填进去了忘记乘以订单数量。举个小例子BOM显示生产1件A需要1件B订单数量是10件A。按理说组件B的数量应该是10但我直接填了1系统一看组件数量跟订单数量不匹配就自动乘了一个10倍最后显示成10。反向的错误也遇到过如果我在程序里手动乘了10但同时BOM的基础数量是10系统又会自动帮你乘一次最后变成100件这就是典型的“双重换算”问题。解决这个问题的最好办法是不要去猜系统会不会帮你换算先确认你使用的BOM展开函数的输出结果里有没有自动做了“数量换算”。我常用的CS_BOM_EXPL_MAT_V2有个字段是BOM用量单位是基础数量对应的用量我在填充PO_COMPONENTS时就直接拿它乘以订单数量系统一般不会再乘第二遍。为了稳妥我在代码里加了一个调试模式打印出“BOM数量、基础数量、订单数量、组件数量”四列一眼就能看出换算有没有问题。4.3 BAPI返回E类型消息但订单还是生成了这个比较阴险我也是被坑过一次之后才重视起来。BAPI_PO_CREATE1的RETURN消息有时候E类型消息只影响部分数据不会影响整张订单。比如它返回了“价格发生变化错误”但订单头还是建出来了只是行项目价格没更新或者被系统用默认值覆盖了。处理这种问题我现在的策略是BAPI返回里有任何E类型消息一律按“整张创建失败”处理。程序里如果检测到E消息就直接执行一次BAPI_TRANSACTION_ROLLBACK把整个数据回滚。否则订单半成品留在表里用户也不知道后续业务环节莫名其妙就会出问题。这里还要提醒一下RETURN表里可能有多个E消息不能只检查第一条。我处理的方法是把E/W消息全部循环读出来拼接到一个字符串里写进日志表同时在屏幕上弹出一个简短的提示让用户点进去看完整内容。这样的用户体验比一个生硬的红叉要好很多。4.4 常见问题速查表现象可能原因处理办法BAPI报“采购订单项目类别无效”物料未维护采购视图或PSTYP错误检查MM01里的采购视图、检查PSTYP是否为F组件行空白BOM展开失败或PSTYP不是F检查BOM有效性、工厂分配、PSTYP价格报错“定额价格未找到”没有维护有效采购信息记录检查ME13里的信息记录及有效期税代码错误税代码无效或无税确定配置检查FTXP/FV11税配置换成J1等有效税码收货时报“没有发料预留”组件行数量为0或没有维护库存地点检查组件行的工厂、库存地点和数量订单生成但查不到漏掉BAPI_TRANSACTION_COMMIT补上COMMIT且带WAITX组件数量异常基础数量换算重复或字段映射错对照BOM展开结果检查数量计算逻辑这张表我基本每次做委外订单开发都会翻出来对照排查效率比从零开始看日志要快得多。5. 写在最后的实操体会回到文章开头那句话——BAPI创建委外采购订单表面上看只是一个函数调用背后牵扯到的基础数据、BOM逻辑、价格校验和事务控制一环接一环。我做过不少次类似的需求最大的感受是开发本身不复杂复杂的是把业务规则吃透再配合SAP的标准接口做好边界处理。如果让我给刚接手这类任务的同事一个建议我会说先花半小时在ME21N里面手工建一张委外订单把每个标签页的字段都点开看一遍心里有数了再去看BAPI参数。很多刚开始做SAP开发的同事容易犯一个毛病就是拿到接口文档就开始写代码最后花在调试上的时间比写代码的时间还多。反而那些愿意先在系统里手动操作一遍、把业务动作和数据表对应上的人开发起来特别快写出来的程序也几乎不怎么需要返工。最后再分享一个小技巧。创建委外订单时在调用BAPI之前先用事务码SE37直接测试BAPI_PO_CREATE1把你要传的参数在测试环境里人工填一遍看返回什么结果。这样做比在程序里反复打断点调试要快得多因为你能直接看到SAP在每一组参数上给出的消息。等到SE37里能成功创建出订单了再把参数搬进ABAP程序你会发现问题少了一半还多。这是我从老同事那里学来、然后自己验证过无数次的方法今天一并分享给有缘看到这篇博客的你。
返回列表