이번은 PO를 마이그레이션 하는데 엑셀을 만들어야 하는 불편함을 없애고자
직접 구시스템에서 가져와서 생성하는 프로그램입니다.
가져오기
구시스템의 PO 테이블을 가져오는 기능입니다.
가져온 후 미리 생성된 테이블에 저장합니다.
생성하기
저장된 테이블을 바탕으로 ALV에 보여준 후 선택하여 생성하게 됩니다.
생성하는 로직은 엑셀을 참조하는 것과 별반 다른것이 없습니다.
그래서 타SAP에서 가져오는 것을 코딩하겠습니다.
먼저 어떤정보를 가져올지랑 초기값 세팅하는 부분입니다.
FORM get_data_from_ecc.
"--------------------------------------------------------------------
" 1. 필수 입력 값 검증
"--------------------------------------------------------------------
IF s_bukrs[] IS INITIAL.
MESSAGE '회사코드를 입력해주세요.' TYPE 'E'.
LEAVE LIST-PROCESSING.
ENDIF.
IF s_aedat[] IS INITIAL.
MESSAGE '생성일을 입력해주세요.' TYPE 'E'.
LEAVE LIST-PROCESSING.
ENDIF.
"데이터 일괄 처리(Chunk Processing)를 위한 카운트 변수 선언
DATA: lv_total TYPE i, lv_cnt TYPE i, lv_mod TYPE i,
lv_total0 TYPE i, lv_cnt0 TYPE i, lv_mod0 TYPE i,
lv_total1 TYPE i, lv_cnt1 TYPE i, lv_mod1 TYPE i,
lv_total2 TYPE i, lv_cnt2 TYPE i, lv_mod2 TYPE i,
lv_total3 TYPE i, lv_cnt3 TYPE i, lv_mod3 TYPE i,
lv_total4 TYPE i, lv_cnt4 TYPE i, lv_mod4 TYPE i,
lv_total5 TYPE i, lv_cnt5 TYPE i, lv_mod5 TYPE i,
lv_total6 TYPE i, lv_cnt6 TYPE i, lv_mod6 TYPE i.
"--------------------------------------------------------------------
" 2. 구매 오더 헤더(EKKO) 데이터 추출
"--------------------------------------------------------------------
IF p_only = 'X'. "선택: 이미 Z-Table에 있는 PO 목록만 대상으로 추출
SELECT ebeln
INTO CORRESPONDING FIELDS OF TABLE @gt_eccpo
FROM zmigpo_ecc.
SORT gt_eccpo.
DELETE ADJACENT DUPLICATES FROM gt_eccpo.
lv_total = lines( gt_eccpo ).
LOOP AT gt_eccpo INTO gs_eccpo.
lv_cnt = lv_cnt + 1.
lv_total = lv_total - 1.
lv_mod = lv_total MOD 5000.
gt_ebeln = VALUE #( BASE gt_ebeln ( ebeln = gs_eccpo-ebeln ) ).
"5000건씩 묶어서 RFC 호출
IF lv_mod = 0 OR lv_total = 0.
PERFORM get_material_data_from_ecc USING 'EKKO'.
CLEAR gt_ebeln.
ENDIF.
ENDLOOP.
ELSE. "선택화면의 조건(회사코드, 생성일)으로 대상 PO 추출
PERFORM get_material_data_from_ecc USING 'EKKO'.
ENDIF.
"--------------------------------------------------------------------
" 3. 구매 오더 품목/납품/계정 (EKPO/EKET/EKKN) 데이터 추출
"--------------------------------------------------------------------
lv_total = lines( gt_zmigpoekko ).
LOOP AT gt_zmigpoekko INTO gs_zmigpoekko.
lv_cnt = lv_cnt + 1.
lv_total = lv_total - 1.
lv_mod = lv_total MOD 5000.
gt_ebeln = VALUE #( BASE gt_ebeln ( ebeln = gs_zmigpoekko-ebeln ) ).
"5000건씩 묶어서 관련 테이블 데이터 추출
IF lv_mod = 0 OR lv_total = 0.
PERFORM get_material_data_from_ecc USING 'EKPO'.
PERFORM get_material_data_from_ecc USING 'EKET'.
PERFORM get_material_data_from_ecc USING 'EKKN'.
CLEAR gt_ebeln.
ENDIF.
ENDLOOP.
"--------------------------------------------------------------------
" 4. 가격 조건(KONV) 데이터 추출
"--------------------------------------------------------------------
"--------------------------------------------------------------------
" 5. 예약(RESB) 데이터 추출 (외주/하청 품목 대상)
"--------------------------------------------------------------------
"--------------------------------------------------------------------
" 6. 주소(ADRC) 데이터 추출
"--------------------------------------------------------------------
"--------------------------------------------------------------------
" 7. 구매 오더 이력(EKBE) 데이터 추출
"--------------------------------------------------------------------
"--------------------------------------------------------------------
" 8. 서비스(ESLH/ESLL/ESKL) 데이터 추출 (서비스 품목 대상)
"--------------------------------------------------------------------
"--------------------------------------------------------------------
" 9. WBS 및 공정(PRPS/AFVC) 데이터 추출 (프로젝트 관련)
"--------------------------------------------------------------------
"--------------------------------------------------------------------
" 10. 추출한 모든 데이터를 Z-Table에 저장
"--------------------------------------------------------------------
PERFORM save_ecc_data.
ENDFORM.
FORM get_material_data_from_ecc USING pv_tabname.
"--------------------------------------------------------------------
" 1. RFC 호출을 위한 변수 선언
"--------------------------------------------------------------------
DATA: lt_data TYPE TABLE OF zcom3000, "RFC로부터 받은 Raw 데이터
lt_options LIKE TABLE OF /bods/rfc_db_opt, "동적 WHERE 조건절
lt_fields LIKE TABLE OF rfc_db_fld. "추출할 필드 리스트
DATA(lv_fdate) = s_aedat-low.
DATA(lv_tdate) = s_aedat-high.
IF lv_tdate IS INITIAL.
lv_tdate = sy-datum.
ENDIF.
"--------------------------------------------------------------------
" 2. 테이블 이름(pv_tabname)에 따라 동적으로 WHERE 조건 생성
"--------------------------------------------------------------------
CASE pv_tabname.
WHEN 'EKKO'. "구매 오더 헤더(EKKO) 조건 생성
IF p_only = 'X'. "Z-Table의 PO 목록만 대상일 경우
lt_options = VALUE #( FOR wa IN gt_ebeln ( text = |OR EBELN = '{ wa-ebeln }' | ) ).
ELSE. "선택 화면의 조건으로 대상 선정
lt_options = VALUE #( ( text = |( AEDAT BETWEEN '{ lv_fdate }' AND '{ lv_tdate }' )| ) ).
IF s_bukrs[] IS NOT INITIAL.
lt_options = VALUE #( BASE lt_options ( text = |AND BUKRS IN @s_bukrs | ) ).
ENDIF.
IF s_ebeln[] IS NOT INITIAL.
lt_options = VALUE #( BASE lt_options ( text = |AND EBELN IN @s_ebeln | ) ).
ENDIF.
IF s_lifnr[] IS NOT INITIAL.
lt_options = VALUE #( BASE lt_options ( text = |AND LIFNR IN @s_lifnr | ) ).
ENDIF.
ENDIF.
WHEN 'EKPO'. "구매 오더 품목(EKPO) 조건 생성
IF gt_ebeln[] IS NOT INITIAL.
lt_options = VALUE #( FOR wa IN gt_ebeln ( text = |OR EBELN = '{ wa-ebeln }' | ) ).
IF s_werks[] IS NOT INITIAL.
lt_options = VALUE #( BASE lt_options ( text = |AND WERKS IN @s_werks | ) ).
ENDIF.
ENDIF.
WHEN 'EKET' OR 'EKKN' OR 'RESB' OR 'ESLH' OR 'EKBE'. "PO 번호가 Key인 하위 테이블 조건
IF gt_ebeln[] IS NOT INITIAL.
lt_options = VALUE #( FOR wa IN gt_ebeln ( text = |OR EBELN = '{ wa-ebeln }' | ) ).
IF pv_tabname = 'EKBE'. "구매이력은 입고(E)만 대상
lt_options = VALUE #( BASE lt_options ( text = |AND ( BEWTP = 'E' )| ) ).
ENDIF.
ENDIF.
WHEN 'ESLL' OR 'ESKL'. "서비스 라인/계정 (Packno 기준)
IF gt_packno[] IS NOT INITIAL.
lt_options = VALUE #( FOR wa IN gt_packno ( text = |OR PACKNO = '{ wa-packno }'| ) ).
ENDIF.
WHEN 'PRPS'. "WBS 마스터 (PSPNR 기준)
IF gt_ps_psp_pnr[] IS NOT INITIAL.
lt_options = VALUE #( FOR wa IN gt_ps_psp_pnr ( text = |OR PSPNR = '{ wa-ps_psp_pnr }'| ) ).
ENDIF.
WHEN 'AFVC'. "공정 데이터 (AUFPL, APLZL 기준)
IF gt_nplnr[] IS NOT INITIAL.
lt_options = VALUE #( FOR wa IN gt_nplnr ( text = |OR ( AUFPL = '{ wa-aufpl }' AND APLZL = '{ wa-aplzl }' )| ) ).
ENDIF.
WHEN 'ADRC'. "주소 마스터 (Address Number 기준)
IF gt_adrn2[] IS NOT INITIAL.
DELETE ADJACENT DUPLICATES FROM gt_adrn2.
lt_options = VALUE #( FOR wa IN gt_adrn2 ( text = |OR ADDRNUMBER = '{ wa-addrnumber }'| ) ).
ENDIF.
WHEN 'KONV'. "가격 조건 (KNUMV 기준)
IF gt_konv[] IS NOT INITIAL.
lt_options = VALUE #( FOR wa IN gt_konv ( text = |OR KNUMV = '{ wa-knumv }'| ) ).
ENDIF.
WHEN 'STXH' OR 'STXL'. "텍스트 테이블
" ... (텍스트 테이블용 조건 생성 로직) ...
ENDCASE.
"WHERE 조건이 없는 경우 RFC 호출 방지
IF lt_options IS INITIAL.
RETURN.
ENDIF.
"첫번째 'OR'을 '(' 로 변경하여 올바른 괄호 구조의 WHERE 절 생성
READ TABLE lt_options INDEX 1.
IF sy-subrc = 0.
REPLACE 'OR' IN lt_options[ 1 ]-text WITH '('.
MODIFY lt_options INDEX 1.
CONCATENATE lt_options[ lines( lt_options ) ]-text ')' INTO lt_options[ lines( lt_options ) ]-text.
ENDIF.
"--------------------------------------------------------------------
" 3. 원격 시스템(ECC)의 테이블 데이터를 RFC로 호출
"--------------------------------------------------------------------
CALL FUNCTION 'ZCOMM_RFC_READ_TABLE_CALL'
EXPORTING
query_table = CONV tabname( pv_tabname )
delimiter = p_delim
dest = p_dest
TABLES
options = lt_options
fields = lt_fields
data = lt_data.
"--------------------------------------------------------------------
" 4. 수신된 Raw 데이터를 파싱하여 Target 인터널 테이블에 저장
"--------------------------------------------------------------------
"저장할 Target 테이블과 Work Area의 이름을 동적으로 생성
DATA(lv_wa_name) = |GS_ZMIGPO{ pv_tabname }|.
DATA(lv_itab_name) = |GT_ZMIGPO{ pv_tabname }|.
"Field Symbol을 이용해 동적으로 Work Area와 테이블 할당
FIELD-SYMBOLS: <fs_wa> TYPE any,
<fs_itab> TYPE STANDARD TABLE,
<fs_comp> TYPE any.
ASSIGN (lv_wa_name) TO <fs_wa>.
ASSIGN (lv_itab_name) TO <fs_itab>.
"수신된 데이터 한 줄(ls_data)을 LOOP
LOOP AT lt_data INTO DATA(ls_data).
DATA(lv_raw_string) = ls_data-wa.
"해당 테이블의 필드 목록을 LOOP
LOOP AT lt_fields INTO DATA(ls_fields).
"문자열을 구분자(p_delim)로 잘라내어 필드 값을 분리
SPLIT lv_raw_string AT p_delim INTO DATA(lv_value) lv_raw_string.
"분리된 값을 Target Work Area의 해당 필드에 동적으로 할당
ASSIGN COMPONENT ls_fields-fieldname OF STRUCTURE <fs_wa> TO <fs_comp>.
IF sy-subrc = 0.
<fs_comp> = lv_value.
ENDIF.
ENDLOOP.
"한 줄이 완성된 Work Area를 Target 인터널 테이블에 추가
APPEND <fs_wa> TO <fs_itab>.
ENDLOOP.
ENDFORM.
이제 실제로 구시스템과 연결하는 펑션입니다.
FUNCTION ZCOMM_RFC_READ_TABLE_CALL.
*"----------------------------------------------------------------------
*"*"Local Interface:
*" IMPORTING
*" REFERENCE(QUERY_TABLE) LIKE DD02L-TABNAME
*" REFERENCE(QUERY_TABLE_S4H) LIKE DD02L-TABNAME OPTIONAL
*" REFERENCE(DELIMITER) TYPE SONV-FLAG DEFAULT '|'
*" REFERENCE(NO_DATA) TYPE SONV-FLAG DEFAULT SPACE
*" REFERENCE(ROWSKIPS) TYPE SOID-ACCNT DEFAULT 0
*" REFERENCE(ROWCOUNT) TYPE SOID-ACCNT DEFAULT 0
*" REFERENCE(DEST) TYPE RFCDEST DEFAULT 'R3PCLNT100'
*" REFERENCE(MODE) TYPE SONV-FLAG DEFAULT ''
*" EXPORTING
*" VALUE(E_DBCNT) TYPE SY-DBCNT
*" TABLES
*" OPTIONS STRUCTURE RFC_DB_OPT
*" FIELDS STRUCTURE RFC_DB_FLD
*" DATA STRUCTURE ZCOM3000
*"----------------------------------------------------------------------
"--------------------------------------------------------------------
" 1. 안전장치: Standard Table을 'M'(수정) 모드로 실행 시 확인 팝업
"--------------------------------------------------------------------
CASE QUERY_TABLE+0(1).
WHEN 'Y' OR 'Z'. "Y 또는 Z로 시작하는 Custom Table은 통과
WHEN OTHERS. "Standard Table일 경우
IF MODE = 'M'. "수정 모드일 때만
PERFORM POPUP_CONFIRM
USING '확인'
'Standard table 업데이트를 요청하셨습니다. 정말 실행하시겠습니까?'
DATA(LV_ANSWER).
CHECK LV_ANSWER = '1'. "사용자가 '예'를 선택하지 않으면 중단
ENDIF.
ENDCASE.
"--------------------------------------------------------------------
" 2. 동적 데이터 객체 생성
"--------------------------------------------------------------------
"IMPORTING으로 받은 테이블 이름(QUERY_TABLE)으로 동적으로 인터널 테이블과 Work Area 생성
"이를 통해 어떤 테이블 구조든 유연하게 처리 가능
CREATE DATA GT_DATA TYPE TABLE OF (QUERY_TABLE).
ASSIGN GT_DATA->* TO <FS_TABLE>.
CREATE DATA GS_DATA TYPE (QUERY_TABLE).
ASSIGN GS_DATA->* TO <WA_TABLE>.
"--------------------------------------------------------------------
" 3. 원격 RFC 호출하여 데이터 조회
"--------------------------------------------------------------------
"실제 데이터 조회는 원격지의 'ZCOMM_RFC_READ_TABLE' FM이 수행
CALL FUNCTION 'ZCOMM_RFC_READ_TABLE'
DESTINATION DEST
EXPORTING
QUERY_TABLE = QUERY_TABLE
DELIMITER = DELIMITER
NO_DATA = NO_DATA
ROWSKIPS = ROWSKIPS
ROWCOUNT = ROWCOUNT
IMPORTING
UNICODE = GV_UNICODE
TABLES
OPTIONS = OPTIONS
FIELDS = FIELDS
DATA = DATA
EXCEPTIONS
TABLE_NOT_AVAILABLE = 1
TABLE_WITHOUT_DATA = 2
OPTION_NOT_VALID = 3
FIELD_NOT_VALID = 4
NOT_AUTHORIZED = 5
DATA_BUFFER_EXCEEDED = 6
OTHERS = 7.
"--------------------------------------------------------------------
" 4. RFC 호출 결과에 따른 분기 처리
"--------------------------------------------------------------------
IF SY-SUBRC <> 0. "RFC 호출 실패 시 오류 처리
CASE SY-SUBRC.
WHEN 1. MESSAGE '잘못된 테이블 이름입니다.' TYPE 'S' DISPLAY LIKE 'E'.
WHEN 2. MESSAGE '데이터가 없습니다.' TYPE 'S'.
WHEN 3. MESSAGE '사용할 수 없는 QUERY입니다.' TYPE 'S' DISPLAY LIKE 'E'.
WHEN 4. MESSAGE '잘못된 필드입니다.' TYPE 'S' DISPLAY LIKE 'E'.
WHEN 5. MESSAGE '조회 권한이 없습니다.' TYPE 'S' DISPLAY LIKE 'E'.
WHEN 6. MESSAGE '테이블 길이가 3000자를 초과합니다.' TYPE 'S' DISPLAY LIKE 'E'.
WHEN OTHERS. MESSAGE '알 수 없는 오류입니다.' TYPE 'S' DISPLAY LIKE 'E'.
ENDCASE.
ELSE. "RFC 호출 성공 시 후속 처리
I_DATA[] = DATA[].
I_FIELDS[] = FIELDS[].
"MODE가 지정된 경우에만 데이터 변환 및 후속 작업 수행
CHECK MODE IS NOT INITIAL.
"수신된 Raw 데이터를 파싱하여 동적으로 생성된 인터널 테이블(<FS_TABLE>)에 채움
PERFORM CONVERT_TABLE_DATA
USING DATA(LV_LINES) "처리된 라인 수
DELIMITER. "구분자
"수행 모드(MODE)에 따라 다른 작업 수행
CASE MODE.
WHEN 'M'. "Modify Mode: 로컬 DB 테이블 업데이트
IF LV_LINES > 0.
IF QUERY_TABLE_S4H IS NOT INITIAL. "S4H용 테이블 이름이 지정된 경우
MODIFY (QUERY_TABLE_S4H) FROM TABLE <FS_TABLE>.
ELSE.
MODIFY (QUERY_TABLE) FROM TABLE <FS_TABLE>.
ENDIF.
IF SY-SUBRC = 0.
MESSAGE S001(00) WITH LV_LINES '건의 데이터를 업데이트하였습니다.'.
ENDIF.
END
이제 구시스템의 펑션입니다.
FUNCTION ZCOMM_RFC_READ_TABLE.
*"----------------------------------------------------------------------
*"*"Local Interface:
*" IMPORTING
*" VALUE(QUERY_TABLE) LIKE DD02L-TABNAME
*" VALUE(DELIMITER) LIKE SONV-FLAG DEFAULT SPACE
*" VALUE(NO_DATA) LIKE SONV-FLAG DEFAULT SPACE
*" VALUE(ROWSKIPS) LIKE SOID-ACCNT DEFAULT 0
*" VALUE(ROWCOUNT) LIKE SOID-ACCNT DEFAULT 0
*" EXPORTING
*" VALUE(UNICODE) TYPE CRMUCSYS
*" TABLES
*" OPTIONS STRUCTURE RFC_DB_OPT
*" FIELDS STRUCTURE RFC_DB_FLD
*" DATA STRUCTURE ZCOM3000
*" EXCEPTIONS
*" TABLE_NOT_AVAILABLE
*" TABLE_WITHOUT_DATA
*" OPTION_NOT_VALID
*" FIELD_NOT_VALID
*" NOT_AUTHORIZED
*" DATA_BUFFER_EXCEEDED
*"----------------------------------------------------------------------
"--------------------------------------------------------------------
" 1. 시스템 환경 및 조회 권한 확인
"--------------------------------------------------------------------
"현재 시스템이 유니코드 시스템인지 확인하여 EXPORTING 파라미터에 설정
CASE SYST-SYSID.
WHEN 'R3T' OR 'R3P' OR 'R3Q'. "Non-Unicode 시스템 목록
UNICODE = ''.
WHEN OTHERS.
UNICODE = 'X'.
ENDCASE.
"사용자가 해당 테이블을 조회할 권한(S_TABU_DIS)이 있는지 확인
CALL FUNCTION 'VIEW_AUTHORITY_CHECK'
EXPORTING
VIEW_ACTION = 'S' "조회(Select)
VIEW_NAME = QUERY_TABLE
EXCEPTIONS
NO_AUTHORITY = 2
OTHERS = 1.
IF SY-SUBRC = 2.
RAISE NOT_AUTHORIZED. "권한 없음 예외 발생
ELSEIF SY-SUBRC = 1.
RAISE TABLE_NOT_AVAILABLE. "테이블 없음 예외 발생
ENDIF.
"--------------------------------------------------------------------
" 2. 조회 대상 테이블의 구조 정보 읽기
"--------------------------------------------------------------------
DATA: TABLE_STRUCTURE TYPE STANDARD TABLE OF DFIES,
TABLE_TYPE TYPE DD02V-TABCLASS.
"ABAP Dictionary에서 테이블의 필드 정보를 가져옴
CALL FUNCTION 'DDIF_FIELDINFO_GET'
EXPORTING
TABNAME = QUERY_TABLE
IMPORTING
DDOBJTYPE = TABLE_TYPE
TABLES
DFIES_TAB = TABLE_STRUCTURE
EXCEPTIONS
OTHERS = 1.
IF SY-SUBRC <> 0.
RAISE TABLE_NOT_AVAILABLE. "구조 정보 읽기 실패 시 예외 발생
ENDIF.
"--------------------------------------------------------------------
이제 가져온 데이터를 저장해야겠죠~
소스를 보시면 알수 있듯이.. 구조를 동일하게 맞춰서 미리 생성해 놓습니다.
FORM save_ecc_data.
"--------------------------------------------------------------------
" 각 인터널 테이블의 데이터를 해당하는 Z-Table에 저장 (UPDATE or INSERT)
" 주의: 현재 로직은 각 테이블 저장 후 바로 COMMIT WORK를 수행합니다.
" -> 이상적으로는 모든 MODIFY가 성공한 후 마지막에 한 번만 COMMIT 하는 것이
" 데이터 정합성 측면에서 더 안전합니다.
"--------------------------------------------------------------------
"구매 오더 헤더(EKKO) 데이터 저장
IF gt_zmigpoekko IS NOT INITIAL.
MODIFY zmigpoekko FROM TABLE gt_zmigpoekko.
IF sy-subrc = 0. COMMIT WORK. ENDIF.
ENDIF.
"구매 오더 품목(EKPO) 데이터 저장
IF gt_zmigpoekpo IS NOT INITIAL.
MODIFY zmigpoekpo FROM TABLE gt_zmigpoekpo.
IF sy-subrc = 0. COMMIT WORK. ENDIF.
ENDIF.
... 정의된 테이블 등등 .....
"모든 작업 완료 후 성공 메시지 표시
MESSAGE s010(zmm1).
ENDFORM.
엄청 단순하죠~
개발이 다 그렇습니다. ^^;
도움이 되셨으면 좋겠습니다.
'ABAP' 카테고리의 다른 글
[ABAP] 재고는 있는데 재고 부족? BAPI 이슈 해결 (0) | 2025.06.29 |
---|---|
[ABAP] 엑셀로 구매정보레코드(Info Record) 대량 생성/변경 (0) | 2025.06.29 |
[ABAP] BAPI_PO_CREATE1(엑셀 업로드) (0) | 2025.06.28 |
[ABAP] BAPI_PO_CREATE1을 활용한 마이그레이션용 구매오더(PO) 생성 가이드 (0) | 2025.06.24 |
[step-3] executable program templete - PBO,PAI,CLS,F01 (0) | 2025.06.24 |