제가 주로 운영/개발하는 모듈은 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% 정답은 아닐 수 있지만, '어떻게 하면 대량의 데이터를 안정적으로 다룰 수 있을까?', '어떻게 하면 코드를 재사용하기 좋게 만들 수 있을까?' 에 대한 저의 고민이 담겨 있습니다.
이 글이 비슷한 요구사항을 마주한 동료 개발자분들께 작은 아이디어나마 되었으면 하는 바람입니다.
'ABAP' 카테고리의 다른 글
[ABAP] 표준 자재 검색(F4)에 나만의 탭 추가하기 (Append Search Help) (0) | 2025.07.02 |
---|---|
[ABAP] 재고는 있는데 재고 부족? BAPI 이슈 해결 (0) | 2025.06.29 |
[ABAP] 구시스템 참조하여 데이터 가져오기 (0) | 2025.06.28 |
[ABAP] BAPI_PO_CREATE1(엑셀 업로드) (0) | 2025.06.28 |
[ABAP] BAPI_PO_CREATE1을 활용한 마이그레이션용 구매오더(PO) 생성 가이드 (0) | 2025.06.24 |