본문 바로가기
ABAP

[ABAP] 엑셀로 구매정보레코드(Info Record) 대량 생성/변경

by 키노s 2025. 6. 29.

제가 주로 운영/개발하는 모듈은 MM 입니다.

 

공급업체와의 단가 계약이 갱신되거나 신규 자재가 대량으로 입고될 때마다 수많은 정보레코드를 수작업으로 수정해야 하는 불편함이 있습니다. 이런 단순 반복 작업에 드는 시간을 줄이고, 더 가치 있는 일에 집중하고 싶다는 생각, 다들 한 번쯤 해보셨을 겁니다.

그래서 오늘은, 이러한 반복 작업을 자동화하고 더 나아가 시스템 간 데이터 마이그레이션까지 대응하기 위해  정보레코드 일괄 처리 프로그램을 공유해볼까 합니다.



지난번에 PO생성 프로그램에 이어 이번에는 정보레코드 생성 프로그램입니다.

2025.06.28 - [ABAP] - [ABAP] BAPI_PO_CREATE1(엑셀 업로드)

 

[ABAP] BAPI_PO_CREATE1(엑셀 업로드)

이번엔 엑셀업로드로 PO 만드는 샘플입니다. PO만다는 소스는 너무나도 많죠~각 프로젝트마다 조금씩 요구사항이 추가로 있어서 약간의 기능이 추가된다고 보면 됩니다.주석은 최대한 달아 놓았

devnvest.com

 

 

이 글에서는 제가 어떤 고민을 통해 이 프로그램을 설계했고, 어떤 기술을 핵심적으로 사용했는지 상세히 설명해 드리겠습니다.
비슷한 요구사항을 마주한 분들께 작은 아이디어나마 되었으면 하는 바람입니다.

1. 프로그램의 주요 기능 및 실행 시나리오

저는 이 프로그램을 크게 두 가지 모드로 나누어, 목적에 따라 유연하게 사용할 수 있도록 설계했습니다.

  • ① 데이터 추출 모드 (원격 데이터 → Z-Table 저장)
    • 사용 시나리오
      :
      S/4HANA로 마이그레이션하기 위해 기존 ECC 시스템의 데이터를 가져오거나, 두 시스템의 데이터를 비교해야 할 때 사용합니다.
    • 기능
      :
      선택 화면에서 지정한 RFC 목적지(ECC)의 정보레코드 관련 테이블(EINA, EINE, KONP 등)에서 대량의 데이터를 조회하여, 현재 시스템의 Z-Table(Staging Table)에 저장합니다.
  • ② 생성/변경 실행 모드 (Z-Table → BAPI 실행)
    • 사용 시나리오
      :
      추출된 Staging 데이터를 이용해 실제 정보레코드를 생성하거나, 엑셀 등으로 가공된 데이터를 기반으로 정보레코드를 일괄 변경/생성할 때 사용합니다.
    • 기능
      :
      Z-Table의 데이터를 읽어 ALV 그리드에 보여주고, 사용자가 최종 확인 후 실행하면 BAPI를 통해 정보레코드를 안전하게 생성 및 변경합니다.

이렇게 단계를 분리함으로써, 대용량 데이터를 안정적으로 처리하고, 오류 발생 시 원인 추적을 용이하게 만들었습니다.

2. 핵심 설계 포인트와 구현 코드 리뷰

제가 이 프로그램을 만들면서 가장 고민했던 세 가지 핵심 설계 포인트와 그 구현 코드를 공유합니다.

2.1. 설계 포인트 1: 재사용성을 위한 '범용 RFC 래퍼'

원격 데이터 조회가 여러 테이블에 걸쳐 반복되는 것을 보고, 어떤 테이블이든 유연하게 조회할 수 있는 범용 서브루틴(f_get_table_data_from_ecc)을 만들었습니다. 이 서브루틴은 테이블명과 동적 WHERE 조건을 파라미터로 받아 데이터를 추출하고, 결과를 파싱하여 넘겨주는 역할을 합니다.

f_get_table_data_from_ecc 

*&---------------------------------------------------------------------*
*& FORM f_get_table_data_from_ecc: RFC로 테이블 데이터 가져오기 (범용)
*&---------------------------------------------------------------------*
FORM f_get_table_data_from_ecc TABLES   pt_options STRUCTURE /bods/rfc_db_opt
                                     pt_table   TYPE STANDARD TABLE
                               USING    pv_tabname TYPE c.
  "1. 범용 RFC 'ZCOMM_RFC_READ_TABLE_CALL' 호출
  CALL FUNCTION 'ZCOMM_RFC_READ_TABLE_CALL'
    EXPORTING
      query_table = CONV tabname( pv_tabname )
      delimiter   = p_delim
      dest        = p_dest
    TABLES
      options     = pt_options
      fields      = DATA(lt_fields)
      data        = DATA(lt_data).

  CHECK lt_data IS NOT INITIAL.

  "2. 수신된 Raw 데이터를 파싱하여 Target 인터널 테이블에 저장
  FIELD-SYMBOLS: <ls_data_stru> TYPE any, <lv_val_t> TYPE any.
  CREATE DATA DATA(lo_line) LIKE LINE OF pt_table.
  ASSIGN lo_line->* TO <ls_data_stru>.

  LOOP AT lt_data ASSIGNING FIELD-SYMBOL(<ls_data>).
    SPLIT <ls_data>-wa AT p_delim INTO TABLE DATA(lt_tmp).
    LOOP AT lt_tmp ASSIGNING FIELD-SYMBOL(<ls_tmp>).
      READ TABLE lt_fields ASSIGNING FIELD-SYMBOL(<ls_fields>) INDEX sy-tabix.
      ASSIGN COMPONENT <ls_fields>-fieldname OF STRUCTURE <ls_data_stru> TO <lv_val_t>.
      IF sy-subrc = 0. <lv_val_t> = <ls_tmp>-value. ENDIF.
    ENDLOOP.
    APPEND <ls_data_stru> TO pt_table.
  ENDLOOP.
ENDFORM.

2.2. 설계 포인트 2: 대용량 처리를 위한 '배치(Batch) 호출'

수만 건의 키 값(예: infnr)으로 WHERE절을 만들면 DUMP가 발생하거나 성능이 저하됩니다. 이를 방지하기 위해, 키 값을 1000개씩 묶어서 동적 WHERE절을 생성하고 RFC를 반복 호출하는 로직을 구현했습니다.

f_get_eina_from_ecc

*&---------------------------------------------------------------------*
*& FORM f_get_eina_from_ecc: EINA 데이터 추출 (배치 처리)
*&---------------------------------------------------------------------*
FORM f_get_eina_from_ecc.
  CHECK gt_eine[] IS NOT INITIAL.
  DATA: lt_eina_chunk TYPE STANDARD TABLE OF zmigeina,
        lt_eine_chunk TYPE STANDARD TABLE OF zmigeine.

  LOOP AT gt_eine ASSIGNING FIELD-SYMBOL(<ls_eine>).
    APPEND <ls_eine> TO lt_eine_chunk. "1000건씩 담을 임시 테이블

    "1000건이 되거나 마지막 데이터일 때
    IF sy-tabix MOD 1000 = 0 OR sy-tabix = lines( gt_eine ).
      "임시 테이블의 INFNR 값으로 동적 WHERE 조건 생성
      DATA(gt_options) = VALUE /bods/rfc_db_opt_t(
        FOR wa IN lt_eine_chunk ( text = |OR INFNR = '{ wa-infnr }'| )
      ).
      "...(WHERE 조건문 정리 로직)...

      "범용 RFC 호출
      PERFORM f_get_table_data_from_ecc TABLES gt_options lt_eina_chunk USING 'EINA'.
      APPEND LINES OF lt_eina_chunk TO gt_eina.

      CLEAR: lt_eine_chunk[], lt_eina_chunk[], gt_options[].
    ENDIF.
  ENDLOOP.
ENDFORM.

2.3. 설계 포인트 3: BAPI를 이용한 '안전한 데이터 처리'

가장 중요한 데이터 생성/변경 로직입니다. 저는 ME_INFORECORD_MAINTAIN BAPI를 사용하여 SAP 표준의 모든 유효성 검증 로직을 통과하고, 데이터 정합성을 완벽하게 보장하도록 구현했습니다.

f_run_inforecord_main_bapi

*&---------------------------------------------------------------------*
*& FORM f_run_inforecord_main_bapi: 정보레코드 생성/변경 BAPI 실행
*&---------------------------------------------------------------------*
FORM f_run_inforecord_main_bapi USING    pv_row_id
                               CHANGING pv_success_count
                                        pv_error_count.
  "ALV에서 선택한 행의 데이터 조회
  READ TABLE gt_alv INTO DATA(ls_alv) INDEX pv_row_id.
  CHECK sy-subrc = 0.

  "BAPI 파라미터용 변수 선언
  DATA: ls_eina     TYPE mewieina, ls_einax    TYPE mewieinax,
        ls_eine     TYPE mewieine, ls_einex    TYPE mewieinex,
        lt_cond_val TYPE STANDARD TABLE OF mewivalidity,
        lt_cond     TYPE STANDARD TABLE OF mewicondition,
        lt_return   TYPE mewi_t_return.

  "1. Staging Table에서 원본 데이터를 조회하여 BAPI 파라미터 구성
  PERFORM f_set_bapi_data_eina USING ... CHANGING ls_eina ls_einax.
  PERFORM f_set_bapi_data_eine USING ... CHANGING ls_eine ls_einex.
  " ... (가격 조건 데이터 구성 로직) ...

  "2. 정보레코드 생성/변경 BAPI 호출
  CALL FUNCTION 'ME_INFORECORD_MAINTAIN'
    EXPORTING
      i_eina        = ls_eina
      i_einax       = ls_einax
      i_eine        = ls_eine
      i_einex       = ls_einex
    TABLES
      cond_validity = lt_cond_val
      condition     = lt_cond
      return        = lt_return.

  "3. BAPI 결과에 따른 트랜잭션 처리
  READ TABLE lt_return WITH KEY type = 'E' OR type = 'A' TRANSPORTING NO FIELDS.
  IF sy-subrc = 0. "오류 발생 시
    pv_error_count += 1.
    CALL FUNCTION 'BAPI_TRANSACTION_ROLLBACK'.
    "Z-Table에 오류 상태 업데이트 후 COMMIT (상태 값만 저장)
    UPDATE zmigeineg SET zstatus = 'E', zmsg = ... WHERE ...
    COMMIT WORK.
  ELSE. "성공 시
    pv_success_count += 1.
    CALL FUNCTION 'BAPI_TRANSACTION_COMMIT' EXPORTING wait = 'X'.
    "Z-Table에 성공 상태 및 신규 정보레코드 번호 업데이트
    UPDATE zmigeineg SET zstatus = 'S', infnr_n = ... WHERE ...
  ENDIF.
ENDFORM.



오늘은 제가 실무에서 유용하게 사용했던 구매정보레코드 대량 처리 프로그램을 소개해 드렸습니다.
이 코드가 100% 정답은 아닐 수 있지만, '어떻게 하면 대량의 데이터를 안정적으로 다룰 수 있을까?', '어떻게 하면 코드를 재사용하기 좋게 만들 수 있을까?' 에 대한 저의 고민이 담겨 있습니다.

이 글이 비슷한 요구사항을 마주한 동료 개발자분들께 작은 아이디어나마 되었으면 하는 바람입니다.