需求场景:
- 根据物流托运单生成服务采购订单(按百分比分配科目)并完成自动收货。
- 根据服务采购订单完成冲销收货和删除订单。
实现步骤
1.数据准备
- 根据托运单项目获取成本中心和会计科目
- 以成本中心+会计科目计算托运单项目数量占比
- 部分关键代码
"获取成本中心和会计科目
LOOP AT lt_zmmtydand.
READ TABLE lt_zmmt081 WITH KEY bukrs = ls_zmmtydanh-bukrs prctr = lt_zmmtydand-prctr.
IF sy-subrc = 0.
lt_zmmtydand-kostl = lt_zmmt081-kostl.
ENDIF.
READ TABLE lt_mbew WITH KEY bwkey = ls_zmmtydanh-bukrs matnr = lt_zmmtydand-matnr.
IF sy-subrc = 0.
IF lt_mbew-bklas = '3000' OR lt_mbew-bklas = '3001' OR lt_mbew-bklas = '3002' OR lt_mbew-bklas = '3003' OR lt_mbew-bklas = '3004'
OR lt_mbew-bklas = '3040' OR lt_mbew-bklas = '3050' OR lt_mbew-bklas = '3060' OR lt_mbew-bklas = '3070' OR lt_mbew-bklas = '3080'
OR lt_mbew-bklas = '3300' OR lt_mbew-bklas = '3400' OR lt_mbew-bklas = '7900'.
lt_zmmtydand-saknr = '1412020010'."材料类
ELSEIF lt_mbew-bklas = '7920' OR lt_mbew-bklas = '7921' OR lt_mbew-bklas = '7930'.
lt_zmmtydand-saknr = '1412020020'."成品类
ENDIF.
ENDIF.
MODIFY lt_zmmtydand.
ENDLOOP.
IF ls_zmmtydanh-ztytype <> 'C'.
"按成本中心+会计科目维度计算数量
LOOP AT lt_zmmtydand.
CLEAR:ls_ratio.
ls_ratio-kostl = lt_zmmtydand-kostl.
ls_ratio-saknr = lt_zmmtydand-saknr.
ls_ratio-menge = lt_zmmtydand-menge.
COLLECT ls_ratio INTO lt_ratio.
ENDLOOP.
CLEAR:lv_menge_sum.
LOOP AT lt_ratio INTO ls_ratio.
ADD ls_ratio-menge TO lv_menge_sum.
CLEAR:ls_ratio.
ENDLOOP.
CLEAR:lv_menge_sum01.
CLEAR:lv_lines.
lv_lines = lines( lt_ratio ).
LOOP AT lt_ratio INTO ls_ratio.
IF sy-tabix <> lv_lines.
ls_ratio-menge = ls_ratio-menge / lv_menge_sum.
ADD ls_ratio-menge TO lv_menge_sum01.
ELSE.
ls_ratio-menge = ( lv_menge_sum - lv_menge_sum01 ) / lv_menge_sum.
ENDIF.
MODIFY lt_ratio FROM ls_ratio.
CLEAR:ls_ratio.
ENDLOOP.
ELSE.
LOOP AT lt_zmmt080.
CLEAR:ls_ratio.
ls_ratio-kostl = lt_zmmt080-kostl.
ls_ratio-saknr = lt_zmmt080-hkont.
ls_ratio-menge = lt_zmmt080-zyfft / 100.
ENDLOOP.
ENDIF.
2.生成服务采购订单
LOOP AT lt_it_data.
"生成服务采购订单
CLEAR ls_poheader.
CLEAR ls_poheaderx.
CLEAR lt_return.
CLEAR lt_poitem.
CLEAR lt_poitemx.
CLEAR lt_poschedule.
CLEAR lt_poschedulex.
CLEAR lt_poaccount.
CLEAR lt_poaccountx.
"数据准备-抬头
READ TABLE lt_zmmtydand WITH KEY ztydanno = lt_it_data-ztydanno.
IF sy-subrc = 0.
CLEAR:lv_vgbel.
READ TABLE lt_lips WITH KEY vbeln = lt_zmmtydand-vbeln BINARY SEARCH.
IF sy-subrc = 0 .
CLEAR:lv_vkorg,lv_bukrs,lv_werks.
READ TABLE lt_vbak WITH KEY vbeln = lt_lips-vgbel BINARY SEARCH.
IF sy-subrc = 0.
lv_vkorg = lt_vbak-bukrs_vf.
lv_bukrs = lt_vbak-bukrs_vf.
ENDIF.
READ TABLE lt_vbap WITH KEY vbeln = lt_lips-vgbel posnr = lt_lips-vgpos BINARY SEARCH.
IF sy-subrc = 0.
lv_werks = lt_vbap-werks.
ENDIF.
ENDIF.
ENDIF.
ls_poheader-doc_date = sy-datum. "采购凭证日期
ls_poheader-doc_type = 'Z06'."'Z02'. "采购凭证类型
ls_poheader-vendor = lt_it_data-lifnr.
ls_poheader-langu = sy-langu.
ls_poheader-pur_group = '903'. "采购组.
IF lt_it_data-ztytype <> 'C'.
ls_poheader-purch_org = lv_vkorg. "采购组织
ls_poheader-comp_code = lv_bukrs. "公司代码
ELSE.
ls_poheader-purch_org = lt_it_data-bukrs. "采购组织
ls_poheader-comp_code = lt_it_data-bukrs. "公司代码
ENDIF.
ls_poheader-currency = 'CNY'. "货币码
ls_poheader-item_intvl = space. "指定行项目编号
ls_poheaderx-doc_date = 'X'.
ls_poheaderx-doc_type = 'X'.
ls_poheaderx-langu = 'X'.
ls_poheaderx-pur_group = 'X'.
ls_poheaderx-purch_org = 'X'.
ls_poheaderx-comp_code = 'X'.
ls_poheaderx-currency = 'X'.
ls_poheaderx-vendor = 'X'."供应商账户号.
ls_poheaderx-item_intvl = 'X'.
"数据准备-项目
CLEAR:lv_ebelp.
LOOP AT lt_zmmtydanf WHERE ztydanno = lt_it_data-ztydanno.
CLEAR ls_poitem.
lv_ebelp = lv_ebelp + 10.
ls_poitem-po_item = lv_ebelp. "采购凭证项目编号
ls_poitem-quantity = lt_zmmtydanf-zfresl."数量
ls_poitem-short_text = lt_zmmtydanf-zfrename."短文本.
ls_poitem-plant = lv_bukrs."lv_werks. "工厂
ls_poitem-matl_group = 'Z029'. "物料组
ls_poitem-po_unit = 'ST'."'PC'. "订单单位
ls_poitem-net_price = lt_zmmtydanf-zfredj. "净价
CASE lt_zmmtydanf-zfretax.
WHEN '0'.
lt_zmmtydanf-zfretax = 'J0'.
WHEN '1'.
lt_zmmtydanf-zfretax = 'JA'.
WHEN '3'.
lt_zmmtydanf-zfretax = 'J6'.
WHEN '6'.
lt_zmmtydanf-zfretax = 'J4'.
WHEN '9'.
lt_zmmtydanf-zfretax = 'J9'.
WHEN '13'.
lt_zmmtydanf-zfretax = 'J2'.
WHEN OTHERS.
ENDCASE.
ls_poitem-tax_code = lt_zmmtydanf-zfretax."税码
ls_poitem-acctasscat = 'K'. "科目分配类别
IF lines( lt_ratio ) > 1.
ls_poitem-distrib = '2'.
ls_poitem-part_inv = '2'.
ENDIF.
APPEND ls_poitem TO lt_poitem .
CLEAR ls_poitemx.
ls_poitemx-po_item = lv_ebelp. "采购凭证项目编号.
ls_poitemx-quantity = 'X'."数量
ls_poitemx-short_text = 'X'."短文本.
ls_poitemx-plant = 'X'."工厂
ls_poitemx-matl_group = 'X'."物料组
ls_poitemx-po_unit = 'X'."订单单位'EA'.
ls_poitemx-tax_code = 'X'."税码
ls_poitemx-net_price = 'X'."净价
ls_poitemx-acctasscat = 'X'."科目分配类别
IF lines( lt_ratio ) > 1.
ls_poitemx-distrib = 'X'.
ls_poitemx-part_inv = 'X'.
ENDIF.
APPEND ls_poitemx TO lt_poitemx .
IF lines( lt_ratio ) = 1.
LOOP AT lt_ratio INTO ls_ratio.
CLEAR ls_poaccount.
ls_poaccount-po_item = lv_ebelp.
ls_poaccount-costcenter = ls_ratio-kostl."成本中心
ls_poaccount-co_area = 'CAPC'."控制范围
ls_poaccount-gl_account = ls_ratio-saknr."总账科目
APPEND ls_poaccount TO lt_poaccount.
CLEAR:ls_poaccountx.
ls_poaccountx-po_item = lv_ebelp. "采购订单行项目
ls_poaccountx-costcenter = 'X'.
ls_poaccountx-co_area = 'X'.
ls_poaccountx-gl_account = 'X'.
APPEND ls_poaccountx TO lt_poaccountx.
CLEAR:ls_ratio.
ENDLOOP.
ELSEIF lines( lt_ratio ) > 1.
CLEAR:lv_serial.
lv_serial = 0.
LOOP AT lt_ratio INTO ls_ratio.
ADD 1 TO lv_serial.
CLEAR:lv_distr.
lv_distr = ls_ratio-menge * 100.
CLEAR ls_poaccount.
ls_poaccount-po_item = lv_ebelp.
ls_poaccount-serial_no = lv_serial.
ls_poaccount-quantity = ls_ratio-menge * lt_zmmtydanf-zfresl. "采购订单数量
ls_poaccount-distr_perc = lv_distr.
ls_poaccount-costcenter = ls_ratio-kostl."成本中心
ls_poaccount-co_area = 'CAPC'."控制范围
ls_poaccount-gl_account = ls_ratio-saknr."总账科目
APPEND ls_poaccount TO lt_poaccount.
CLEAR:ls_poaccountx.
ls_poaccountx-po_item = lv_ebelp. "采购订单行项目
ls_poaccountx-serial_no = lv_serial.
ls_poaccountx-quantity = 'X'. "申请数量
ls_poaccountx-distr_perc = 'X'.
ls_poaccountx-costcenter = 'X'.
ls_poaccountx-co_area = 'X'.
ls_poaccountx-gl_account = 'X'.
APPEND ls_poaccountx TO lt_poaccountx.
CLEAR:ls_ratio.
ENDLOOP.
ENDIF.
ENDLOOP.
CALL FUNCTION 'BAPI_PO_CREATE1'
EXPORTING
poheader = ls_poheader
poheaderx = ls_poheaderx
IMPORTING
exppurchaseorder = lv_exppurchaseorder
expheader = ls_expheader
exppoexpimpheader = ls_exppoexpimpheader
TABLES
return = lt_return
poitem = lt_poitem
poitemx = lt_poitemx
poschedule = lt_poschedule
poschedulex = lt_poschedulex
poaccount = lt_poaccount
poaccountx = lt_poaccountx.
LOOP AT lt_return INTO ls_return WHERE type CA 'AEX'.
lv_message = lv_message && ls_return-message.
ENDLOOP.
IF lv_message IS NOT INITIAL .
CALL FUNCTION 'BAPI_TRANSACTION_ROLLBACK'.
MESSAGE lv_message TYPE 'E'.
ELSE.
CALL FUNCTION 'BAPI_TRANSACTION_COMMIT'
EXPORTING
wait = 'X'.
REFRESH:lt_zmmtydanh.
SELECT * FROM zmmtydanh INTO CORRESPONDING FIELDS OF TABLE lt_zmmtydanh
FOR ALL ENTRIES IN g_it_data
WHERE ztydanno = g_it_data-ztydanno.
LOOP AT g_it_data WHERE box IS NOT INITIAL.
g_it_data-ebeln = lv_exppurchaseorder.
MODIFY g_it_data.
READ TABLE lt_zmmtydanh WITH KEY ztydanno = g_it_data-ztydanno.
IF sy-subrc = 0.
CLEAR:lv_index.
lv_index = sy-tabix.
lt_zmmtydanh-ebeln = lv_exppurchaseorder.
MODIFY lt_zmmtydanh INDEX lv_index.
ENDIF.
ENDLOOP.
MODIFY zmmtydanh FROM TABLE lt_zmmtydanh.
COMMIT WORK.
"参考订单收货
PERFORM frm_create_goodsmvt USING lv_exppurchaseorder.
ENDIF.
ENDLOOP.
3.MIGO收货
FORM frm_create_goodsmvt USING p_exppurchaseorder.
"bapi 参数
DATA:lw_goodsmvt_header TYPE bapi2017_gm_head_01,
lv_goodsmvt_code TYPE bapi2017_gm_code,
lt_goodsmvt_item TYPE TABLE OF bapi2017_gm_item_create,
lw_goodsmvt_item TYPE bapi2017_gm_item_create,
lt_return TYPE TABLE OF bapiret2,
lw_return TYPE bapiret2,
lv_materialdocument TYPE bapi2017_gm_head_ret-mat_doc,
lv_matdocumentyear TYPE bapi2017_gm_head_ret-doc_year.
DATA: gt_return TYPE TABLE OF bapiret2,
wa_return TYPE bapiret2.
DATA: lv_error TYPE c.
DATA:ls_ekko LIKE ekko.
DATA:lt_ekpo LIKE TABLE OF ekpo.
SELECT SINGLE * FROM ekko INTO ls_ekko WHERE ebeln = p_exppurchaseorder.
SELECT * FROM ekpo INTO CORRESPONDING FIELDS OF TABLE lt_ekpo WHERE ebeln = p_exppurchaseorder.
lv_goodsmvt_code = '01'.
"准备抬头数据
lw_goodsmvt_header-pstng_date = sy-datum.
lw_goodsmvt_header-doc_date = sy-datum. "凭证中的凭证日期
lw_goodsmvt_header-pr_uname = sy-uname. "用户名
lw_goodsmvt_header-ref_doc_no = ls_ekko-ebeln."PO
lw_goodsmvt_header-header_txt = ls_ekko-ebeln.
LOOP AT lt_ekpo INTO DATA(ls_ekpo).
"行项目数据
lw_goodsmvt_item-vendor = ls_ekko-lifnr.
lw_goodsmvt_item-po_number = ls_ekpo-ebeln. "采购凭证号
lw_goodsmvt_item-item_text = ls_ekpo-txz01. "项目文本
lw_goodsmvt_item-po_item = ls_ekpo-ebelp. "采购凭证的项目编号
lw_goodsmvt_item-plant = ls_ekpo-werks. "工厂
lw_goodsmvt_item-entry_qnt = ls_ekpo-menge. "以录入项单位表示的数量
lw_goodsmvt_item-entry_uom = ls_ekpo-meins.
lw_goodsmvt_item-mvt_ind = 'B'. "移动标识 ’B‘为采购收货 ’F ' 生产收货
lw_goodsmvt_item-move_type = '101'. "移动类型
APPEND lw_goodsmvt_item TO lt_goodsmvt_item .
CLEAR:ls_ekpo.
ENDLOOP.
CALL FUNCTION 'BAPI_GOODSMVT_CREATE'
EXPORTING
goodsmvt_header = lw_goodsmvt_header
goodsmvt_code = lv_goodsmvt_code "MB01 按采购订单的货物移动
IMPORTING
materialdocument = lv_materialdocument
matdocumentyear = lv_matdocumentyear
TABLES
goodsmvt_item = lt_goodsmvt_item
return = lt_return.
LOOP AT gt_return INTO wa_return WHERE type CA 'AEX'.
IF sy-subrc = 0.
lv_error = 'E'.
EXIT.
ENDIF.
ENDLOOP.
IF lv_error EQ 'E'.
CALL FUNCTION 'BAPI_TRANSACTION_ROLLBACK'.
MESSAGE '成功创建服务采购订单:' && p_exppurchaseorder && '物料凭证过账失败' TYPE 'I'.
ELSE.
CALL FUNCTION 'BAPI_TRANSACTION_COMMIT'
EXPORTING
wait = 'X'.
MESSAGE '成功创建服务采购订单:' && p_exppurchaseorder && '物料凭证:' && lv_materialdocument && '已过账' TYPE 'I'.
ENDIF.
ENDFORM. " FRM_CREATE_GOODSMVT
4.MIGO冲销和服务采购订单删除
做的过程中遇到了一些问题:
-
如何判断是否已收货,最终的解决方案是根据凭证年度、编号和项目获取采购凭证历史表EKBE中的借方(shkzg = ‘S’)的物料凭证,根据参考物料凭证年度、编号、项目在MSEG中查找是否存在贷方(shkzg = ‘H’)的记录,如果存在,则认为已收货冲销,不存在,则认为存在未冲销的收货凭证。
-
查询MSEG时速度较慢,我这里采用了三个优化。
1、只取需要的字段 SELECT lfbja lfbnr lfpos FROM mseg
2、使用%_HINTS SYBASE '&max_blocking_factor 100& &max_in_blocking_factor 1000&'优化FOR ALL ENTRIES IN
3、对表排序,使用二分法查找 SORT lt_mseg BY lfbja lfbnr
lfpos.
READ TABLE lt_it_data INDEX 1.
IF sy-subrc = 0.
"校验是否收货
REFRESH:lt_ekbe,lt_mseg.
SELECT * FROM ekbe INTO CORRESPONDING FIELDS OF TABLE lt_ekbe
WHERE ebeln = lt_it_data-ebeln AND shkzg = 'S'.
IF lt_ekbe[] IS NOT INITIAL.
SELECT lfbja lfbnr lfpos FROM mseg
INTO CORRESPONDING FIELDS OF TABLE lt_mseg
FOR ALL ENTRIES IN lt_ekbe
WHERE lfbja = lt_ekbe-gjahr AND lfbnr = lt_ekbe-belnr AND lfpos = lt_ekbe-buzei AND shkzg = 'H'
%_HINTS SYBASE '&max_blocking_factor 100& &max_in_blocking_factor 1000&'.
SORT lt_mseg BY lfbja lfbnr lfpos.
LOOP AT lt_ekbe INTO DATA(ls_ekbe).
READ TABLE lt_mseg TRANSPORTING NO FIELDS WITH KEY lfbja = ls_ekbe-gjahr lfbnr = ls_ekbe-belnr lfpos = ls_ekbe-buzei BINARY SEARCH.
IF sy-subrc = 0.
DELETE lt_ekbe[].
ENDIF.
ENDLOOP.
ENDIF.
IF lt_ekbe[] IS NOT INITIAL.
"冲销物料凭证
CLEAR:ls_goodsmvt_headret.
REFRESH:lt_goodsmvt_matdocitem,lt_return.
CLEAR:lt_goodsmvt_matdocitem,lt_return.
LOOP AT lt_ekbe INTO ls_ekbe.
CALL FUNCTION 'BAPI_GOODSMVT_CANCEL'
EXPORTING
materialdocument = ls_ekbe-belnr
matdocumentyear = ls_ekbe-gjahr
goodsmvt_pstng_date = sy-datum
goodsmvt_pr_uname = sy-uname
IMPORTING
goodsmvt_headret = ls_goodsmvt_headret
TABLES
return = lt_return
goodsmvt_matdocitem = lt_goodsmvt_matdocitem.
LOOP AT lt_return INTO DATA(ls_return) WHERE type CA 'AEX'.
lv_message = lv_message && ls_return-message.
ENDLOOP.
IF lv_message IS NOT INITIAL .
CALL FUNCTION 'BAPI_TRANSACTION_ROLLBACK'.
MESSAGE lv_message TYPE 'E'.
ELSE.
CALL FUNCTION 'BAPI_TRANSACTION_COMMIT'
EXPORTING
wait = 'X'.
ENDIF.
CLEAR:ls_ekbe.
ENDLOOP.
ENDIF.
"保存记录到服务采购订单冲销记录表
CLEAR:ls_zmmt084.
ls_zmmt084-ztydanno = lt_it_data-ztydanno.
ls_zmmt084-ebeln = lt_it_data-ebeln.
ls_zmmt084-zcxby = sy-uname.
ls_zmmt084-zcxdat = sy-datum.
ls_zmmt084-zcxtim = sy-uzeit.
MODIFY zmmt084 FROM ls_zmmt084.
COMMIT WORK.
"删除采购订单
REFRESH:lt_ekpo.
SELECT * FROM ekpo INTO CORRESPONDING FIELDS OF TABLE lt_ekpo
WHERE ebeln = lt_it_data-ebeln.
CLEAR:ls_poheader,ls_poheaderx.
ls_poheader-po_number = lt_it_data-ebeln.
ls_poheader-creat_date = sy-datum.
ls_poheader-doc_date = sy-datum.
ls_poheader-created_by = sy-uname.
ls_poheaderx-po_number = 'X'.
ls_poheaderx-creat_date = 'X'.
ls_poheaderx-doc_date = 'X'.
ls_poheaderx-created_by = 'X'.
CLEAR:lt_poitem[],lt_poitemx[].
LOOP AT lt_ekpo INTO DATA(ls_ekpo).
CLEAR:ls_poitem.
ls_poitem-po_item = ls_ekpo-ebelp.
ls_poitem-delete_ind = 'X'.
APPEND ls_poitem TO lt_poitem[].
CLEAR:ls_poitemx.
ls_poitemx-po_item = ls_ekpo-ebelp.
ls_poitemx-po_itemx = 'X'.
ls_poitemx-delete_ind = 'X'.
APPEND ls_poitemx TO lt_poitemx[].
CLEAR:ls_ekpo.
ENDLOOP.
REFRESH:lt_return.
CALL FUNCTION 'BAPI_PO_CHANGE'
EXPORTING
purchaseorder = lt_it_data-ebeln
poheader = ls_poheader
poheaderx = ls_poheaderx
TABLES
return = lt_return
poitem = lt_poitem
poitemx = lt_poitemx.
LOOP AT lt_return INTO ls_return WHERE type CA 'AEX'.
lv_message = lv_message && ls_return-message.
ENDLOOP.
IF lv_message IS NOT INITIAL .
CALL FUNCTION 'BAPI_TRANSACTION_ROLLBACK'.
MESSAGE lv_message TYPE 'E'.
ELSE.
CALL FUNCTION 'BAPI_TRANSACTION_COMMIT'
EXPORTING
wait = 'X'.
"解锁托运单抬头审批/服务采购订单/费用明细审批
REFRESH:lt_zmmtydanf.
SELECT SINGLE * FROM zmmtydanh INTO ls_zmmtydanh WHERE ztydanno = lt_it_data-ztydanno.
SELECT * FROM zmmtydanf INTO CORRESPONDING FIELDS OF TABLE lt_zmmtydanf WHERE ztydanno = lt_it_data-ztydanno.
IF ls_zmmtydanh IS NOT INITIAL.
ls_zmmtydanh-approve = space.
ls_zmmtydanh-zsprq = space.
ls_zmmtydanh-ebeln = space.
ENDIF.
IF lt_zmmtydanf[] IS NOT INITIAL.
LOOP AT lt_zmmtydanf INTO DATA(ls_zmmtydanf).
ls_zmmtydanf-zstatus = '1'.
MODIFY lt_zmmtydanf FROM ls_zmmtydanf.
CLEAR:ls_zmmtydanf.
ENDLOOP.
ENDIF.
MODIFY zmmtydanh FROM ls_zmmtydanh.
MODIFY zmmtydanf FROM TABLE lt_zmmtydanf.
COMMIT WORK.
LOOP AT g_it_data ASSIGNING FIELD-SYMBOL(<fs_it_data>) WHERE ztydanno = lt_it_data-ztydanno.
<fs_it_data>-approve = space.
<fs_it_data>-zsprq = space.
<fs_it_data>-ebeln = space.
ENDLOOP.
MESSAGE '服务采购订单' && lt_it_data-ebeln && '已删除' TYPE 'S'.
ENDIF.
ENDIF.
20230310 16:08更新
用户测试冲销功能的时候,发现正常冲销成功了但是提示
经过检查发现收货冲销是按照物料凭证冲销的,会对该物料凭证下的所有项目一起冲销,我未对物料凭证去重,所以有这个问题,调整后的逻辑如下:
SORT lt_ekbe BY gjahr belnr.
DELETE ADJACENT DUPLICATES FROM lt_ekbe COMPARING gjahr belnr.
LOOP AT lt_ekbe INTO ls_ekbe.
CALL FUNCTION 'BAPI_GOODSMVT_CANCEL'
EXPORTING
materialdocument = ls_ekbe-belnr
matdocumentyear = ls_ekbe-gjahr
goodsmvt_pstng_date = sy-datum
goodsmvt_pr_uname = sy-uname
IMPORTING
goodsmvt_headret = ls_goodsmvt_headret
TABLES
return = lt_return
goodsmvt_matdocitem = lt_goodsmvt_matdocitem.