개요 및 실습 목표
SAP S/4HANA 도입 이후에도 여전히 많은 개발자가 구매 오더 정보를 조회할 때 EKKO(구매 오더 헤더)와 EKPO(구매 오더 아이템) 테이블을 직접 SELECT하는 관행을 유지하고 있습니다. 이 방식은 익숙하지만 S/4HANA의 Virtual Data Model(VDM) 철학과 어긋나며, 필드 매핑·권한·조인 로직을 매번 재작성해야 하는 부담을 낳습니다. 이 글에서는 SAP가 제공하는 릴리즈된 CDS View I_PurchaseOrder와 I_PurchaseOrderItem을 활용해 EKKO/EKPO에 직접 손대지 않고도 안정적으로 구매 오더를 조회하는 방법을 정리합니다.
- EKKO/EKPO 직접 SELECT의 리스크와 마이그레이션 이슈 이해
- I_PurchaseOrder / I_PurchaseOrderItem의 구조·핵심 필드 파악
- 실무 시나리오 기반 ABAP OPEN SQL 조회 예제 3단계 구현
- Virtual Data Model(VDM) 계층(Basic / Composite / Consumption) 이해
- 성능·권한·테스트까지 고려한 프로덕션 코드 작성
사전에 알아두면 좋은 지식
이 글은 ABAP 개발 경험이 있고 OPEN SQL(SELECT ... INTO TABLE ...) 문법에 익숙한 독자를 대상으로 합니다. CDS View 기본 문법(@AbapCatalog, define view)과 S/4HANA VDM의 존재를 들어봤다면 이해가 훨씬 빠릅니다. 구매 프로세스(MM 모듈)의 오더 헤더/아이템 개념, 회사 코드(BUKRS)·플랜트(WERKS)·공급업체(LIFNR) 필드의 의미 정도는 알고 있어야 합니다.
실습 환경과 준비물
이 글의 예제는 다음 환경을 기준으로 검증되었습니다. 릴리즈된 CDS View 명세는 시스템 버전에 따라 필드가 조금씩 다르므로, 실제 프로젝트에서는 ADT (Eclipse) → SAP GUI → SE11 또는 View Browser Fiori 앱으로 필드 리스트를 재확인하는 것이 안전합니다.
- SAP S/4HANA 2022 이상 (On-Premise) 또는 SAP S/4HANA Cloud, private edition
- ABAP Development Tools (ADT) for Eclipse 2024-03 이상 권장
- 백엔드: NetWeaver AS ABAP 7.57+ / ABAP Platform 2023
- DB: SAP HANA (컬럼 스토어 기반, VDM 성능 전제)
- 권한:
S_RFC, MM 조회용M_BEST_*계열 권한 오브젝트 - 테스트 데이터: 구매 오더 최소 10건 이상, 공급업체(LIFNR) 2~3개 분포
참고로 I_PurchaseOrder는 SAP가 릴리즈한 Basic Interface View로, S/4HANA 1610부터 존재하지만 필드가 지속적으로 확장되어 왔습니다. 최근 릴리즈일수록 PurchaseOrderSubtype, PurgDocReleaseStatus 같은 필드가 추가되어 있으니 시스템 릴리즈에 맞는 문서를 확인하시기 바랍니다.
핵심 개념 정리
EKKO/EKPO를 직접 조회하는 방식은 마치 도서관에서 매번 서고에 직접 들어가 책을 찾는 것과 같습니다. 어디에 뭐가 있는지 훤히 알고 있을 때는 빠르지만, 서고 구조가 바뀌면(테이블 확장, 필드 변경, 통화·수량 필드 재편) 이용자가 모두 헤매게 됩니다. 반면 CDS View는 사서에게 원하는 책 목록을 요청하는 것과 같습니다. 사서(SAP)가 서고 구조 변경을 흡수해 주기 때문에 요청자(개발자)는 인터페이스만 신경 쓰면 됩니다.
S/4HANA의 VDM은 다음 3계층으로 구성됩니다.
- Basic View (I_*): 원천 테이블(EKKO, EKPO 등)을 1:1에 가깝게 재노출하되, 어소시에이션(연관)·의미론적 애노테이션이 추가된 계층. 예:
I_PurchaseOrder,I_PurchaseOrderItem - Composite View (C_*): 여러 Basic View를 조합해 비즈니스 의미가 있는 개념을 만든 뷰. 예: 미결 구매 오더 요약
- Consumption View (C_* 또는 UI 접미어): Fiori·OData·분석 도구가 직접 소비하도록 설계된 최상위 뷰
EKKO 직접 접근의 실질적 문제는 다음과 같습니다.
- 강한 결합: EKKO 컬럼 변경 시 코드 전체가 영향을 받음
- 권한 누락: EKKO 자체는 권한 체크를 하지 않아 직접 SELECT 시 조직 권한 필터를 개발자가 수동 추가해야 함.
I_PurchaseOrder는 DCL을 통해 접근 통제가 걸려 있어 안전한 경우가 많음 - 단위·통화 필드 처리 누락: 금액/수량 필드의 통화·단위 참조를
@Semantics.amount.currencyCode애노테이션으로 자동 처리하는 이점이 없음 - 어소시에이션 미활용:
_Supplier,_CompanyCode,_PurchaseOrderItem같은 편리한 네비게이션을 사용하지 못함 - 클라우드 이식성: SAP S/4HANA Cloud, public edition에서는 EKKO 직접 접근 자체가 차단되어 있음
단계별 실전 예제
1단계 — 기본 조회 예제
가장 단순한 형태로 특정 회사 코드에 속한 구매 오더 헤더 몇 건을 가져오는 코드부터 시작합니다. 기존 EKKO 스타일과 비교해 필드명이 의미를 그대로 담고 있다는 점을 눈여겨보세요.
REPORT z_po_basic_read.
DATA: lt_po TYPE STANDARD TABLE OF i_purchaseorder,
ls_po TYPE i_purchaseorder.
SELECT purchaseorder,
companycode,
supplier,
purchaseordertype,
documentcurrency,
creationdate
FROM i_purchaseorder
INTO TABLE @lt_po
UP TO 20 ROWS
WHERE companycode = '1010'.
LOOP AT lt_po INTO ls_po.
WRITE: / ls_po-purchaseorder,
ls_po-supplier,
ls_po-documentcurrency,
ls_po-creationdate.
ENDLOOP.
EKKO에서 EBELN, BUKRS, LIFNR이라는 4자리 약어를 외워야 했던 것과 달리, PurchaseOrder·CompanyCode·Supplier처럼 필드명을 그대로 읽을 수 있습니다. 신규 입사자 온보딩 속도가 빨라지는 지점입니다.
2단계 — 실무 시나리오: 특정 공급업체의 미결 구매 오더 조회 + 로깅
구매팀에서 자주 요청하는 리포트는 "특정 공급업체(LIFNR)의 미결(Open) 구매 오더 목록"입니다. 헤더 정보만으로는 부족하고 아이템 레벨의 수량·순금액이 필요하므로, I_PurchaseOrder와 I_PurchaseOrderItem을 조인합니다. 삭제 표시된 아이템은 제외하고, 오류 상황은 애플리케이션 로그에 기록합니다.
CLASS zcl_open_po_reader DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
TYPES: BEGIN OF ty_open_po,
purchase_order TYPE i_purchaseorderitem-purchaseorder,
po_item TYPE i_purchaseorderitem-purchaseorderitem,
supplier TYPE i_purchaseorder-supplier,
material TYPE i_purchaseorderitem-material,
order_quantity TYPE i_purchaseorderitem-orderquantity,
net_price_amount TYPE i_purchaseorderitem-netpriceamount,
document_currency TYPE i_purchaseorder-documentcurrency,
END OF ty_open_po,
tt_open_po TYPE STANDARD TABLE OF ty_open_po WITH KEY purchase_order po_item.
METHODS get_open_pos
IMPORTING iv_supplier TYPE i_purchaseorder-supplier
iv_company_code TYPE i_purchaseorder-companycode
RETURNING VALUE(rt_result) TYPE tt_open_po
RAISING cx_sy_open_sql_db.
ENDCLASS.
CLASS zcl_open_po_reader IMPLEMENTATION.
METHOD get_open_pos.
TRY.
SELECT hdr~purchaseorder,
itm~purchaseorderitem,
hdr~supplier,
itm~material,
itm~orderquantity,
itm~netpriceamount,
hdr~documentcurrency
FROM i_purchaseorder AS hdr
INNER JOIN i_purchaseorderitem AS itm
ON itm~purchaseorder = hdr~purchaseorder
INTO TABLE @rt_result
WHERE hdr~supplier = @iv_supplier
AND hdr~companycode = @iv_company_code
AND itm~purchaseorderitemdeletioncode = @space.
CATCH cx_sy_open_sql_db INTO DATA(lx_sql).
MESSAGE ID 'ZPO' TYPE 'E' NUMBER '001'
WITH lx_sql->get_text( ) INTO DATA(lv_dummy).
" 애플리케이션 로그로 흘려보내기
cl_bali_free_text_setter=>create(
severity = if_bali_constants=>c_severity_error
text = lx_sql->get_text( ) ).
RAISE EXCEPTION lx_sql.
ENDTRY.
ENDMETHOD.
ENDCLASS.
주목할 점은 PurchaseOrderItemDeletionCode(EKPO의 LOEKZ에 대응) 필드로 삭제 아이템을 걸러낸다는 것과, NetPriceAmount가 자동으로 DocumentCurrency를 참조 통화로 잡고 있다는 것입니다. EKPO 직접 조회 때는 NETPR × MENGE를 개발자가 곱하고 통화를 별도로 조회해야 했던 코드가 크게 줄어듭니다.
3단계 — 프로덕션 레벨: 어소시에이션·집계·단위 테스트
프로덕션 코드에서는 (1) 어소시에이션(_Supplier)을 활용해 조인을 줄이고, (2) 필요한 필드만 명시적으로 나열해 컬럼 스토어 이점을 살리며, (3) 집계 함수로 DB에서 계산을 끝내야 합니다. 아래는 공급업체별 미결 구매 오더의 총 순금액을 집계하는 예제입니다.
METHOD summarize_open_po_by_supplier.
SELECT hdr~supplier AS supplier,
hdr~_supplier-suppliername AS supplier_name,
hdr~documentcurrency AS currency,
SUM( itm~netpriceamount * itm~orderquantity ) AS total_amount,
COUNT( DISTINCT hdr~purchaseorder ) AS po_count
FROM i_purchaseorder AS hdr
INNER JOIN i_purchaseorderitem AS itm
ON itm~purchaseorder = hdr~purchaseorder
INTO TABLE @DATA(lt_summary)
WHERE hdr~companycode = @iv_company_code
AND hdr~purgdocreleasestatus = 'R' " Released
AND itm~purchaseorderitemdeletioncode = @space
GROUP BY hdr~supplier,
hdr~_supplier-suppliername,
hdr~documentcurrency
ORDER BY total_amount DESCENDING.
rt_summary = lt_summary.
ENDMETHOD.
테스트는 ABAP Unit + SQL Test Double Framework로 I_PurchaseOrder를 모킹해 결정론적으로 검증할 수 있습니다.
CLASS ltc_reader DEFINITION FINAL FOR TESTING
DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
DATA mo_osql TYPE REF TO if_osql_test_environment.
METHODS setup.
METHODS returns_only_open_items FOR TESTING RAISING cx_static_check.
ENDCLASS.
CLASS ltc_reader IMPLEMENTATION.
METHOD setup.
mo_osql = cl_osql_test_environment=>create(
i_dependency_list = VALUE #(
( 'I_PURCHASEORDER' )
( 'I_PURCHASEORDERITEM' ) ) ).
ENDMETHOD.
METHOD returns_only_open_items.
" given: 삭제 아이템 1건 + 정상 아이템 2건 주입
" when: get_open_pos 호출
" then: 결과 2건, 삭제 아이템 없음 검증
ENDMETHOD.
ENDCLASS.
자주 마주치는 함정과 트러블슈팅
Q1. I_PurchaseOrder가 SE11에서 안 보입니다.
CDS View는 DDIC 테이블이 아니라 DDL 소스이므로 SE11에서는 SQL View(예: IPRCHSORDR)만 노출됩니다. Eclipse ADT의 Open ABAP Development Object (Ctrl+Shift+A)에서 I_PurchaseOrder를 검색해야 원본이 열립니다.
Q2. 성능이 EKKO 직접 조회보다 느려졌습니다.
가장 흔한 원인은 SELECT *입니다. VDM 뷰는 수십 개 필드와 어소시에이션을 포함하므로 필요한 필드만 명시적으로 나열해야 합니다. 또한 WHERE에서 인덱스가 걸린 CompanyCode, PurchaseOrder, Supplier를 우선 사용하고, 문자열 함수(LIKE '%...%')는 피하세요. HANA에서는 컬럼 스토어 특성상 필드 수 축소가 성능에 크게 기여합니다.
Q3. 릴리즈되지 않은 필드를 사용했다가 릴리즈 노트에서 경고가 나옵니다.
릴리즈된 CDS View라도 개별 필드가 아직 C1(Stable) 계약이 아닐 수 있습니다. API State가 Released인지 확인하고, 클라우드 환경이라면 Released Objects 앱에서 사용 가능한 필드만 사용해야 업그레이드 시 안전합니다.
Q4. 권한 오류로 결과가 비어 나옵니다.
I_PurchaseOrder는 DCL(Data Control Language)이 적용되어 있어 사용자의 회사코드·구매조직 권한 밖의 데이터는 자동으로 필터링됩니다. EKKO 직접 SELECT에서는 나오던 데이터가 안 보일 수 있는데, 이는 버그가 아니라 정상적인 권한 통제입니다. 디버깅 시 SU53과 함께 DCL 로그를 확인하세요.
확장 학습 방향
이 글에서 다룬 조회 패턴을 익혔다면, 다음 주제로 확장해 보시길 권장합니다.
- RAP(RESTful ABAP Programming Model)로
I_PurchaseOrder를 기반 삼아 Fiori Elements 앱 만들기 C_PurchaseOrderItemAPI계열 Consumption View와 OData 서비스 노출 방법- Analytical CDS View(
@Analytics.dataCategory: #CUBE)로 Embedded Analytics 대시보드 구축 - ABAP Cloud 개발 모델에서의 released API 사용 원칙과 Custom Entity
PurchasingDocument관련 Business Events(Event Mesh) 연동
더 읽어볼 문서
- SAP Help Portal — Virtual Data Model in SAP S/4HANA
- SAP Help Portal — ABAP CDS Development Guide
- SAP Help Portal — Purchase Order Processing (MM-PUR)
- SAP API Business Hub — I_PurchaseOrder 관련 OData 서비스
- SAP Community — ABAP CDS 실전 사례 모음
- SAP Blogs — ABAP CDS 태그 아카이브
- SAP Help Portal — Released ABAP Objects (Clean Core)
댓글 0
아직 댓글이 없습니다.