背景
此文适合对SAP PP模块生产订单业务有一定了解的PP顾问和ABAP开发人员阅读。
所在项目中遇到一个特殊需求,创建拆解工单时,拆除整机得到半成品和原材料。
这时候不是增加组件预留数量,而是减少预留数量(因为半成品和原材料增多了),业务方的要求是,这种特殊类型工单,组件数量默认是负数。说实话这种需求我见过,这种解决方案我是第一次听说(更别提见)。为什么不用物料凭证移动类型配置,同仓转储之类的成熟解决方案,我不知道,大概是财务科目配不明白吧。
OK,我的任务是实现需求,别的咱也不知道,咱也不敢问。
需求是通过接口创建拆解工单,组件物料是定制的,数量是负的。
有点绕的解决方案
我之前(差不多10年前)一直用一个标准BAPI处理PP生产订单的增删改,就是BAPI_ALM_ORDER_MAINTAIN。
实际上这是一个PM模块维修/服务工单使用的BAPI,SAP的PP,PM,PS模块使用了同一套底表来存储业务数据(问就是架构原因,我也不知道为什么),所以这个API也能够用来处理PP生产订单。为什么非得用PM的API来改PP的数据呢,因为PP模块没有相应功能的API,自带的BAPI_PRODORD_CREATE没有什么考虑定制组件之类的功能,SAP就是摆烂,OK?
SAP曾推出过一系列Note补丁,禁止客户开发人员使用这个BAPI改生产订单,都被我司的BASIS同事屏蔽了,所以我们一直这么用。
我在目前项目的系统一看,BAPI_ALM_ORDER_MAINTAIN这个BAPI不能用了。Note打了好几波,摘都摘不掉。
接口使用的账号不是dialog账号,不能支持BDC的CO01方式,只能另想办法。
最终考虑,使用COXT函数组的CO_XT_COMPONENT_ADD来添加定制组件和加工数量(负数)。
方案细节
- 先调用BAPI_PRODORD_CREATE创建生产订单,这一步会自动生成加工组件,不能定制。
这一步会根据表头的物料,工厂,订单类型,数量,计划起始/截止日期生成生产订单,同时会根据订单物料对应的生产BOM自动计算加工组件物料和数量,按常规方式不能改(非常规可以,我们下面聊)。
我们需要做的是,删除不需要的“自动计算”出来的组件,替换成需要的定制组件。
FORM create_ppo.
DATA:ls_orderdata TYPE bapi_pp_order_create,
ls_return TYPE bapiret2,
lv_order_number TYPE aufnr.
* 创建生产订单
CLEAR:ls_orderdata.
ls_orderdata-material = gs_h-matnr.
ls_orderdata-plant = gs_h-dwerk.
ls_orderdata-order_type = gs_h-auart.
ls_orderdata-quantity = gs_h-pgmng.
ls_orderdata-basic_start_date = gs_h-gstrp.
ls_orderdata-basic_end_date = gs_h-gltrp.
CALL FUNCTION 'BAPI_PRODORD_CREATE'
EXPORTING
orderdata = ls_orderdata
IMPORTING
return = ls_return
order_number = lv_order_number.
IF ls_return-type = 'E'.
CALL FUNCTION 'BAPI_TRANSACTION_ROLLBACK'.
MESSAGE ID ls_return-id
TYPE ls_return-type
NUMBER ls_return-number
WITH ls_return-message_v1
ls_return-message_v2
ls_return-message_v3
ls_return-message_v4.
ELSE.
CALL FUNCTION 'BAPI_TRANSACTION_COMMIT'
EXPORTING
wait = 'X'.
ENDIF.
DO 10 TIMES.
SELECT COUNT(*)
FROM aufk
WHERE aufnr = lv_order_number.
IF sy-subrc = 0.
EXIT.
ELSE.
WAIT UP TO 1 SECONDS.
ENDIF.
ENDDO.
* 由于生产订单创建时自动生成组件,需要先清空组件
PERFORM delete_components USING lv_order_number.
* 添加需要的组件
PERFORM add_components USING lv_order_number.
* 没有抛出错误消息,证明工单操作成功
MESSAGE S100(CO) WITH lv_order_number.
LEAVE TO SCREEN 0.
ENDFORM.
2. 删除已存在的订单组件
这里需要调用CO_XT_COMPONENTS_DELETE这函数,删除工单对应RESB预留表中所有已存在组件。
COXT函数组的一些函数使用时,需要执行CO_XT_ORDER_PREPARE_COMMIT预提交,正式提交后需要执行CO_XT_ORDER_INITIALIZE再次初始化,这一点和PS模块的一些API类似(毕竟是同一套底表)。
FORM delete_components USING pv_order_number TYPE aufnr.
*** Deleting Components from Production Order
DATA: lt_resbkeys TYPE coxt_t_resbdel,
lt_return TYPE STANDARD TABLE OF bapiret2,
ls_return TYPE bapiret2,
lv_error TYPE flag,
ls_bapireturn TYPE coxt_bapireturn.
* Fetch existing components of given Production Order
SELECT rsnum, rspos INTO TABLE @DATA(lt_resb)
FROM resb
WHERE aufnr = @pv_order_number. " Previously created order
IF sy-subrc EQ 0.
lt_resbkeys = CORRESPONDING #( lt_resb ).
ENDIF.
IF NOT lt_resbkeys[] IS INITIAL.
* BAPI to delete the components of Production Order
CALL FUNCTION 'CO_XT_COMPONENTS_DELETE'
EXPORTING
it_resbkeys_to_delete = lt_resbkeys
IMPORTING
e_error_occurred = lv_error
TABLES
ct_bapireturn = lt_return
EXCEPTIONS
delete_failed = 1
OTHERS = 2.
IF lv_error = space.
CALL FUNCTION 'CO_XT_ORDER_PREPARE_COMMIT'
IMPORTING
es_bapireturn = ls_bapireturn
e_error_occurred = lv_error.
IF ( ls_bapireturn-type = 'S' OR
ls_bapireturn-type = 'W' OR
ls_bapireturn-type = 'I' ) OR
ls_bapireturn IS INITIAL.
CALL FUNCTION 'BAPI_TRANSACTION_COMMIT'.
CALL FUNCTION 'CO_XT_ORDER_INITIALIZE'.
ELSE.
CLEAR: lv_error.
CALL FUNCTION 'BAPI_TRANSACTION_ROLLBACK'.
MESSAGE ID ls_bapireturn-id
TYPE ls_bapireturn-type
NUMBER ls_bapireturn-number
WITH ls_bapireturn-message_v1
ls_bapireturn-message_v2
ls_bapireturn-message_v3
ls_bapireturn-message_v4.
ENDIF.
ELSE.
CLEAR lv_error.
CALL FUNCTION 'BAPI_TRANSACTION_ROLLBACK'.
READ TABLE lt_return INTO ls_return WITH KEY type = 'E'.
IF sy-subrc = 0.
MESSAGE ID ls_return-id
TYPE ls_return-type
NUMBER ls_return-number
WITH ls_return-message_v1
ls_return-message_v2
ls_return-message_v3
ls_return-message_v4.
ENDIF.
ENDIF.
ENDIF.
ENDFORM.
3. 添加我们需要的定制组件
需要注意,CO_XT_COMPONENT_ADD这个函数每次只能处理一条数据。如果添加多条需要循环处理,都成功后统一预提交,再正式提交,清缓存。
这个函数不是标准BAPI,所以需要修改主程序内存中的预留表对应的变量(SAPLCOBC)RESB_BT[],给组件对应预留数据的行项目字段赋值,配合让它生效。这么做非常别扭,但没别的选择余地。
值得一提的是,某些旧版本系统在执行完添加组件后,不会生成组件的状态对象。你会看到组件状态是空的,不是CRTD已创建。这时候你需要根据预留号和预留行项目号,自己创建一个状态对象。新版本不需要了,对这一步有疑问的伙计,我们可以单独再聊。
FORM add_components USING pv_order_number TYPE aufnr.
DATA: ls_requ TYPE coxt_s_quantity,
ls_storage TYPE coxt_s_storage_location,
ls_storagex TYPE coxt_s_storage_locationx,
ls_return TYPE coxt_bapireturn,
lt_return TYPE coxt_t_bapireturn,
lv_msg TYPE string,
lv_tabix TYPE sy-tabix,
lv_postp TYPE postp,
lv_operation TYPE co_aplzl,
lv_sequence TYPE plnfolge,
lv_material TYPE matnr,
lv_positionno TYPE positionno,
lv_error TYPE flag.
TYPES: BEGIN OF ty_resb_bt.
INCLUDE TYPE resbb.
TYPES: indold LIKE sy-tabix,
no_req_upd LIKE sy-datar,
END OF ty_resb_bt.
TYPES: lt_resb_bt TYPE TABLE OF ty_resb_bt.
FIELD-SYMBOLS: <ft_resb_bt> TYPE lt_resb_bt,
<fs_resb_bt> TYPE ty_resb_bt.
SELECT SINGLE aufnr, aufpl
INTO @DATA(ls_afko)
FROM afko
WHERE aufnr = @pv_order_number.
IF sy-subrc EQ 0.
* Fetch operation to which it has to be assigned
SELECT SINGLE aufpl, aplzl, plnfl
INTO @DATA(ls_afvc)
FROM afvc
WHERE aufpl = @ls_afko-aufpl.
IF sy-subrc EQ 0.
lv_operation = ls_afvc-aplzl.
lv_sequence = ls_afvc-plnfl.
ENDIF.
ENDIF.
* gt_i为存储定制组件的内表,gs_i为对应的工作区
LOOP AT gt_i INTO gs_i.
lv_tabix = sy-tabix.
CLEAR: ls_requ,ls_storage,ls_storagex.
ls_requ-quantity = gs_i-menge.
ls_requ-uom = gs_i-meins.
ls_storage-werks = gs_h-dwerk.
ls_storage-lgort = gs_i-lgpro.
ls_storagex-werks = 'X'.
ls_storagex-lgort = 'X'.
lv_positionno = sy-tabix * 10.
lv_postp = 'L'.
lv_material = gs_i-idnrk.
* BAPI to add components to Production Order
CALL FUNCTION 'CO_XT_COMPONENT_ADD'
EXPORTING
is_order_key = pv_order_number
i_material = lv_material
is_requ_quan = ls_requ
i_operation = lv_operation
i_sequence = lv_sequence
is_storage_location = ls_storage
is_storage_locationx = ls_storagex
i_postp = lv_postp
i_posno = lv_positionno
IMPORTING
es_bapireturn = ls_return
e_error_occurred = lv_error.
IF lv_error IS NOT INITIAL.
EXIT.
ENDIF.
ENDLOOP.
IF lv_error = space.
CLEAR: lv_tabix,
ls_return.
* Modify POSNR via ASSIGN before DB update to correct the blank
* item number in Components due to incompatible types of I_POSNO
* (type CIF_R3RES-POSITIONNO) and RESB-POSNR
ASSIGN ('(SAPLCOBC)RESB_BT[]') TO <ft_resb_bt>.
LOOP AT <ft_resb_bt> ASSIGNING <fs_resb_bt>.
lv_tabix = sy-tabix * 10.
<fs_resb_bt>-posnr = lv_tabix.
CLEAR lv_tabix.
ENDLOOP.
* Commit transaction
CALL FUNCTION 'CO_XT_ORDER_PREPARE_COMMIT'
IMPORTING
es_bapireturn = ls_return
e_error_occurred = lv_error.
IF ( ls_return-type = 'S' OR
ls_return-type = 'W' OR
ls_return-type = 'I' ) OR
ls_return IS INITIAL.
* Commit data
CALL FUNCTION 'BAPI_TRANSACTION_COMMIT'.
CALL FUNCTION 'CO_XT_ORDER_INITIALIZE'.
ELSE.
CLEAR: lv_error.
* Data Rollback
CALL FUNCTION 'BAPI_TRANSACTION_ROLLBACK'.
IF ls_return-type = 'E'.
MESSAGE ID ls_return-id
TYPE ls_return-type
NUMBER ls_return-number
WITH ls_return-message_v1
ls_return-message_v2
ls_return-message_v3
ls_return-message_v4.
ENDIF.
ENDIF.
ELSE.
CLEAR: lv_error.
* Data Rollback
CALL FUNCTION 'BAPI_TRANSACTION_ROLLBACK'.
IF ls_return-type = 'E'.
MESSAGE ID ls_return-id
TYPE ls_return-type
NUMBER ls_return-number
WITH ls_return-message_v1
ls_return-message_v2
ls_return-message_v3
ls_return-message_v4.
ENDIF.
ENDIF.
ENDFORM.
其他的非常规方案
既然BAPI_PRODORD_CREATE这个官方主推的BAPI功能这么少,那么想改组件,工序之类的内容有什么办法吗?
答案是,有!但不是SAP官方推荐的方法。
使用SMOD事务码可以查到一个PP的生产订单保存前增强-PPCO0007,这是一个客户出口增强,其中只有一个组件函数EXIT_SAPLCOZV_001。里面只能看到表头数据CAUFVD。SAP本意是让你在里面做定制校验抛异常的,不是让你改东西。
如果非要改组件之类的行不行?用上文那个方法,字段符号直接匹配主程序内存中的更新数据库表变量(SAPLCOBC)RESB_BT[],以及其他工序,文本表之类。
这么做不但SAP不推荐,我也不推荐,可能出一些奇怪的问题。