Triển khai mở rộng giao dịch MIGO bằng BAdI MB_MIGO_BADI để khởi tạo tài sản cố định

Tổng quan kiến trúc và thách thức kỹ thuật

Trong quá trình mở rộng giao dịch MIGO, một yêu cầu phổ biến là thêm một tab tùy chỉnh (subscreen) để thu thập dữ liệu, từ đó kết hợp với thông tin dòng mục (line item) nhằm tự động tạo thẻ tài sản cố định (Fixed Asset Master). Sau khi tạo thành công, hệ thống cần ghi ngược (write-back) số tài sản vào các trường tương ứng trên giao diện MIGO.

Thách thức lớn nhất trong kịch bản này nằm ở việc xung đột Logical Unit of Work (LUW). Hàm BAPI tiêu chuẩn BAPI_FIXEDASSET_CREATE1 yêu cầu thực thi COMMIT WORK để lưu dữ liệu. Tuy nhiên, việc gọi COMMIT trực tiếp bên trong các phương thức của BAdI MIGO (như LINE_MODIFY) sẽ phá vỡ luồng xử lý tiêu chuẩn của MIGO, dẫn đến lỗi dump hoặc mất dữ liệu chứng từ.

Để giải quyết vấn đề này, kiến trúc hệ thống cần được thiết kế lại bằng cách sử dụng kỹ thuật gọi hàm bất đồng bộ (Asynchronous RFC với STARTING NEW TASK). Phương pháp này giúp tách biệt LUW của việc tạo tài sản ra khỏi LUW chính của MIGO, đảm bảo an toàn dữ liệu và duy trì tính ổn định của hệ thống.

Chuẩn bị cấu trúc dữ liệu

Trước khi triển khai logic, cần định nghĩa các cấu trúc và bảng tùy chỉnh trong Data Dictionary (SE11):

  • ZST_MIGO_ASSET_INPUT: Cấu trúc chứa các tham số đầu vào để gọi BAPI tạo tài sản (Mã công ty, phân loại tài sản, trung tâm chi phí, v.v.).
  • ZST_MIGO_EXT: Cấu trúc ánh xạ dữ liệu hiển thị trên subscreen.
  • ZTB_MIGO_EXT: Bảng trong suốt (Transparent table) dùng để lưu trữ lịch sử dữ liệu mở rộng theo số chứng từ vật tư (MBLNR).

Xây dựng Function Group cho Subscreen

Tạo một Function Group mới (ví dụ: ZFG_MIGO_EXT) để quản lý toàn bộ logic trao đổi dữ liệu giữa BAdI và Subscreen 9100. Nhóm này sẽ bao gồm các Function Module (FM) sau:

1. FM khởi tạo thẻ tài sản (Remote-Enabled)

Function Module này phải được kích hoạt tính năng Remote-Enabled để hỗ trợ gọi bất đồng bộ. Bên trong FM, ta gọi BAPI và thực hiện commit độc lập.

FUNCTION Z_FM_CREATE_FIXED_ASSET.
*"----------------------------------------------------------------------
*"  IMPORTING
*"     VALUE(IV_INPUT_DATA) TYPE  ZST_MIGO_ASSET_INPUT
*"  EXPORTING
*"     VALUE(EV_MAIN_ASSET) TYPE  BF_ANLN1
*"     VALUE(EV_SUB_ASSET) TYPE  BF_ANLN2
*"     VALUE(EV_ERROR_MSG) TYPE  BAPI_MSG
*"----------------------------------------------------------------------
  DATA: ls_asset_key       TYPE bapi1022_key,
        ls_general_data    TYPE bapi1022_feglg001,
        ls_general_datax   TYPE bapi1022_feglg001x,
        ls_time_dep_data   TYPE bapi1022_feglg003,
        ls_time_dep_datax  TYPE bapi1022_feglg003x,
        ls_allocations     TYPE bapi1022_feglg004,
        ls_allocationsx    TYPE bapi1022_feglg004x,
        ls_return          TYPE bapiret2,
        lv_new_asset       TYPE bapi1022_1-assetmaino,
        lv_new_sub         TYPE bapi1022_1-assetsubno,
        lt_dep_areas       TYPE STANDARD TABLE OF bapi1022_dep_areas,
        lt_dep_areasx      TYPE STANDARD TABLE OF bapi1022_dep_areasx.

  " Ánh xạ dữ liệu đầu vào vào cấu trúc BAPI
  ls_asset_key-companycode = iv_input_data-bukrs.
  
  ls_general_data-assetclass    = iv_input_data-anlkl.
  ls_general_datax-assetclass   = abap_true.
  ls_general_data-descript      = iv_input_data-txt50.
  ls_general_datax-descript     = abap_true.
  ls_general_data-base_uom      = iv_input_data-meins.
  ls_general_datax-base_uom     = abap_true.

  ls_time_dep_data-costcenter   = iv_input_data-kostl.
  ls_time_dep_datax-costcenter  = abap_true.

  ls_allocations-assetsupno     = iv_input_data-anlue.
  ls_allocationsx-assetsupno    = abap_true.

  " Cấu hình vùng khấu hao
  APPEND VALUE #( area = '01' ulife_yrs = iv_input_data-ndjar ulife_prds = iv_input_data-ndper ) TO lt_dep_areas.
  APPEND VALUE #( area = '01' ulife_yrs = abap_true ulife_prds = abap_true ) TO lt_dep_areasx.

  " Gọi BAPI tạo tài sản
  CALL FUNCTION 'BAPI_FIXEDASSET_CREATE1'
    EXPORTING
      key                = ls_asset_key
      generaldata        = ls_general_data
      generaldatax       = ls_general_datax
      timedependentdata  = ls_time_dep_data
      timedependentdatax = ls_time_dep_datax
      allocations        = ls_allocations
      allocationsx       = ls_allocationsx
    IMPORTING
      asset              = lv_new_asset
      subnumber          = lv_new_sub
      return             = ls_return
    TABLES
      depreciationareas  = lt_dep_areas
      depreciationareasx = lt_dep_areasx.

  IF lv_new_asset IS NOT INITIAL.
    CALL FUNCTION 'BAPI_TRANSACTION_COMMIT' EXPORTING wait = abap_true.
    ev_main_asset = lv_new_asset.
    ev_sub_asset  = lv_new_sub.
  ELSE.
    CALL FUNCTION 'BAPI_TRANSACTION_ROLLBACK'.
    ev_error_msg  = ls_return-message.
  ENDIF.
ENDFUNCTION.

2. FM điều phối gọi bất đồng bộ

Sử dụng STARTING NEW TASK để tách LUW. Cần tạo một chuỗi ngẫu nhiên làm định danh task để tránh xung đột khi xử lý nhiều dòng cùng lúc.

FUNCTION Z_FM_TRIGGER_ASYNC_CREATION.
*"----------------------------------------------------------------------
*"  IMPORTING
*"     VALUE(IS_INPUT) TYPE  ZST_MIGO_ASSET_INPUT
*"  EXPORTING
*"     VALUE(EV_ANLN1) TYPE  BF_ANLN1
*"     VALUE(EV_ANLN2) TYPE  BF_ANLN2
*"     VALUE(EV_MSG)   TYPE  BAPI_MSG
*"----------------------------------------------------------------------
  DATA: lv_task_id TYPE string.

  CALL FUNCTION 'GENERAL_GET_RANDOM_STRING'
    EXPORTING number_chars  = 10
    IMPORTING random_string = lv_task_id.

  " Khởi tạo biến toàn cục trước khi gọi async
  CLEAR: gv_async_done, gv_res_anln1, gv_res_anln2, gv_res_msg.

  CALL FUNCTION 'Z_FM_CREATE_FIXED_ASSET' STARTING NEW TASK lv_task_id
    PERFORMING handle_async_response ON END OF TASK
    EXPORTING iv_input_data = is_input.

  " Chờ cho đến khi tiến trình nền hoàn tất
  WAIT UNTIL gv_async_done = abap_true.

  ev_anln1 = gv_res_anln1.
  ev_anln2 = gv_res_anln2.
  ev_msg   = gv_res_msg.
ENDFUNCTION.

3. Các FM trao đổi dữ liệu Subscreen

Nhóm các FM dùng để đồng bộ hóa dữ liệu giữa lớp BAdI và màn hình 9100.

" Lấy dữ liệu từ màn hình
FUNCTION Z_FM_GET_SCREEN_DATA.
  EXPORTING reference(e_output) TYPE zst_migo_ext.
  e_output = g_screen_data.
ENDFUNCTION.

" Đổ dữ liệu ra màn hình
FUNCTION Z_FM_SET_SCREEN_DATA.
  IMPORTING reference(i_input) TYPE zst_migo_ext.
  g_screen_data = i_input.
ENDFUNCTION.

" Lưu hành động (Action) hiện tại của MIGO
FUNCTION Z_FM_SET_ACTION_MODE.
  IMPORTING reference(i_action) TYPE goaction
            reference(i_refdoc) TYPE refdoc.
  gv_current_action = i_action.
  gv_current_refdoc = i_refdoc.
ENDFUNCTION.

" Lưu dữ liệu vào bảng tùy chỉnh
FUNCTION Z_FM_PERSIST_CUSTOM_DATA.
  TABLES t_data STRUCTURE zst_migo_ext.
  IF t_data[] IS NOT INITIAL.
    MODIFY ztb_migo_ext FROM TABLE t_data.
  ENDIF.
ENDFUNCTION.

4. Logic xử lý Subscreen 9100 (PBO & PAI)

Màn hình 9100 chứa các logic kiểm soát trạng thái nhập liệu dựa trên loại chuyển dịch (Movement Type) và cung cấp F4 Help tùy chỉnh.

MODULE status_9100 OUTPUT.
  " Đọc loại chuyển dịch từ bộ nhớ MIGO
  ASSIGN ('(SAPLMIGO)GODEFAULT_TV-BWART') TO FIELD-SYMBOL(<fs_bwart>).
  
  LOOP AT SCREEN.
    IF screen-group1 = 'MOD'.
      " Chỉ cho phép nhập liệu nếu là movement type 241 và kho chỉ định
      IF <fs_bwart> = '241' AND g_screen_data-lgort IN ('0001','0002').
        screen-input = 1.
      ELSE.
        screen-input = 0.
      ENDIF.
      MODIFY SCREEN.
    ENDIF.
  ENDLOOP.
ENDMODULE.

MODULE f4_cost_center INPUT.
  DATA: lt_kostl TYPE STANDARD TABLE OF csks.
  
  SELECT kostl, verak FROM csks INTO TABLE @lt_kostl WHERE kokrs = '1000'.

  CALL FUNCTION 'F4IF_INT_TABLE_VALUE_REQUEST'
    EXPORTING retfield    = 'KOSTL'
              dynpprog    = sy-repid
              dynpnr      = sy-dynnr
              dynprofield = 'G_SCREEN_DATA-KOSTL'
              value_org   = 'S'
    TABLES    value_tab   = lt_kostl.
ENDMODULE.

Xử lý Callback cho tác vụ bất đồng bộ trong Include LZFG_MIGO_EXTU01:

FORM handle_async_response USING p_task.
  RECEIVE RESULTS FROM FUNCTION 'Z_FM_CREATE_FIXED_ASSET'
    IMPORTING
      ev_main_asset = gv_res_anln1
      ev_sub_asset  = gv_res_anln2
      ev_error_msg  = gv_res_msg
    EXCEPTIONS
      system_failure        = 1
      communication_failure = 2.
      
  gv_async_done = abap_true.
ENDFORM.

Triển khai BAdI MB_MIGO_BADI

Tạo implementation mới cho BAdI MB_MIGO_BADI (ví dụ: ZMB_MIGO_ASSET_EXT). Dưới đây là logic triển khai cho các phương thức cốt lõi trong lớp ZCL_IM_MB_MIGO_ASSET_EXT.

Kích hoạt Subscreen (PBO_DETAIL & PAI_DETAIL)

METHOD if_ex_mb_migo_badi~pbo_detail.
  CHECK gf_class_id = i_class_id AND i_line_id IS NOT INITIAL.

  " Hiển thị tab tùy chỉnh
  e_cprog   = 'SAPLZFG_MIGO_EXT'.
  e_dynnr   = '9100'.
  e_heading = 'Thông tin khởi tạo tài sản'.

  " Đồng bộ dữ liệu dòng hiện tại ra màn hình
  READ TABLE it_migo_items INTO DATA(ls_item) WITH KEY global_counter = i_line_id.
  IF sy-subrc = 0.
    CALL FUNCTION 'Z_FM_SET_SCREEN_DATA' EXPORTING i_input = ls_item.
  ENDIF.
ENDMETHOD.

METHOD if_ex_mb_migo_badi~pai_detail.
  CHECK i_line_id <> 0.
  
  CALL FUNCTION 'Z_FM_GET_SCREEN_DATA' IMPORTING e_output = DATA(ls_screen_data).
  READ TABLE it_migo_items INTO DATA(ls_old_data) WITH KEY global_counter = i_line_id.
  
  " Nếu dữ liệu thay đổi, bắt buộc MIGO gọi lại LINE_MODIFY
  IF ls_old_data <> ls_screen_data.
    e_force_change = abap_true.
  ENDIF.
ENDMETHOD.

Xử lý tạo tài sản và cập nhật dữ liệu (LINE_MODIFY)

Đây là phương thức quan trọng nhất, nơi diễn ra việc thu thập dữ liệu, gọi hàm bất đồng bộ và gán kết quả vào cấu trúc CS_GOITEM.

METHOD if_ex_mb_migo_badi~line_modify.
  CHECK cs_goitem-bwart = '241' AND i_line_id <> 0.

  " Đánh dấu trigger tạo tài sản thông qua SAP Memory (được set ở PAI khi user nhấn nút tùy chỉnh)
  IMPORT p1 = DATA(lv_trigger_flag) FROM MEMORY ID 'Z_MIGO_CREATE_ASSET'.
  
  CALL FUNCTION 'Z_FM_GET_SCREEN_DATA' IMPORTING e_output = DATA(ls_screen).

  " Logic tạo tài sản
  IF cs_goitem-anln1 IS INITIAL AND lv_trigger_flag = 'X' AND cs_goitem-bwart = '241'.
    IF ls_screen-kostl IS NOT INITIAL AND ls_screen-anlkl IS NOT INITIAL.
      
      DATA(ls_asset_input) = VALUE zst_migo_asset_input(
        bukrs = cs_goitem-bukrs
        anlkl = ls_screen-anlkl
        txt50 = |{ cs_goitem-matnr } - { cs_goitem-maktx }|
        meins = cs_goitem-meins
        kostl = ls_screen-kostl
        anlue = ls_screen-anlue
      ).

      CALL FUNCTION 'Z_FM_TRIGGER_ASYNC_CREATION'
        EXPORTING is_input  = ls_asset_input
        IMPORTING ev_anln1  = DATA(lv_new_anln1)
                  ev_anln2  = DATA(lv_new_anln2)
                  ev_msg    = DATA(lv_msg).

      IF lv_new_anln1 IS NOT INITIAL.
        cs_goitem-anln1 = lv_new_anln1.
        cs_goitem-anln2 = lv_new_anln2.
      ELSE.
        MESSAGE lv_msg TYPE 'E' DISPLAY LIKE 'S'.
      ENDIF.
    ENDIF.
    FREE MEMORY ID 'Z_MIGO_CREATE_ASSET'.
  ENDIF.

  " Cập nhật lại bảng nội bộ của BAdI
  DELETE it_migo_items WHERE global_counter = i_line_id.
  MOVE-CORRESPONDING cs_goitem TO ls_screen.
  APPEND ls_screen TO it_migo_items.
ENDMETHOD.

Lưu trữ dữ liệu sau khi Post chứng từ (POST_DOCUMENT)

Sau khi MIGO hoàn tất việc lưu chứng từ vật tư, tiến hành lưu các thông tin mở rộng vào bảng Z.

METHOD if_ex_mb_migo_badi~post_document.
  LOOP AT it_migo_items ASSIGNING FIELD-SYMBOL(<fs_item>).
    <fs_item>-mblnr = is_mkpf-mblnr.
    <fs_item>-mjahr = is_mkpf-mjahr.
    <fs_item>-mandt = sy-mandt.
  ENDLOOP.

  " Chỉ lưu dữ liệu cho các dòng có liên quan đến tạo tài sản
  READ TABLE it_mseg INTO DATA(ls_mseg) INDEX 1.
  IF ls_mseg-bwart = '241'.
    CALL FUNCTION 'Z_FM_PERSIST_CUSTOM_DATA'
      TABLES t_data = it_migo_items.
  ENDIF.
ENDMETHOD.

Các phương thức hỗ trợ khác

METHOD if_ex_mb_migo_badi~init.
  APPEND 'Z_MIGO_ASSET_EXT' TO ct_init.
ENDMETHOD.

METHOD if_ex_mb_migo_badi~mode_set.
  CALL FUNCTION 'Z_FM_SET_ACTION_MODE'
    EXPORTING i_action = i_action
              i_refdoc = i_refdoc.
ENDMETHOD.

METHOD if_ex_mb_migo_badi~line_delete.
  DELETE it_migo_items WHERE global_counter = i_line_id.
ENDMETHOD.

METHOD if_ex_mb_migo_badi~reset.
  CLEAR it_migo_items.
ENDMETHOD.

Thẻ: SAP ABAP MIGO BAdI MB_MIGO_BADI

Đăng vào ngày 20 tháng 9 lúc 05:07