이 글이 답하는 질문
구매 오더 아이템을 조회할 때 EKPO 테이블에 바로 SELECT를 쏘는 코드를 한 번쯤 작성해 보셨을 겁니다. 그런데 SAP S/4HANA로 넘어오면서 표준 가상 데이터 모델(VDM)인 I_PurchaseOrderItem CDS View가 등장했고, 많은 실무 시나리오에서 EKPO 직접 조회 대신 이 뷰를 쓰는 것이 권장되는 흐름이 됐습니다. 이 글은 다음 질문에 답합니다.
- EKPO를 직접 SELECT 하는 것과
I_PurchaseOrderItem을 사용하는 것은 무엇이 다른가요? - 자재(Material), 수량(Quantity), 납품 일자(Delivery Date) 같은 필드를 어떤 이름으로 가져와야 하나요?
- 구매 오더 헤더(
I_PurchaseOrder)와 어떻게 조인하나요? - RAP/Fiori Elements 앱에서 이 뷰를 어떻게 재사용할 수 있나요?
이 글을 보기 전에
아래 항목들은 본문에서 깊이 설명하지 않으니 익숙해진 상태로 읽으시는 편이 좋습니다.
- ABAP CDS View 기본 문법 (
@AbapCatalog.sqlViewName,define view,association) - MM 모듈의 구매 오더 데이터 구조 (EKKO 헤더 / EKPO 아이템 / EKET 일정라인)
- Open SQL과 ABAP SQL의 차이 (S/4HANA 1909 이후 ABAP SQL 표기)
- S/4HANA Virtual Data Model(VDM)의 I/C/P 계층 (Interface / Composite / Private)
S/4HANA의 표준 CDS View를 한 번도 본 적이 없다면 먼저 I_Product나 I_BusinessPartner 같은 마스터 데이터 뷰를 ADT(Eclipse)에서 열어 구조를 훑어보고 오시면 이해가 빠릅니다.
테스트 환경
아래 환경 기준으로 작성했습니다. 릴리즈에 따라 필드명이 살짝 다를 수 있으니 최종 확인은 ADT의 Data Preview로 하시는 것을 권장합니다.
- SAP S/4HANA 2022 (on-premise) / S/4HANA Cloud Public Edition 2308 기준
- ABAP Development Tools (ADT) for Eclipse 2023-12 이상
- NetWeaver AS ABAP 7.58, ABAP Platform 2022
- 권한: 구매 오더 조회 권한(M_BEST_*), CDS View 표시 권한
- 샘플 데이터: 표준 IDES 또는 Best Practices 컨텐츠에 포함된 구매 오더
S/4HANA 1809 이전 릴리즈에서는 I_PurchaseOrderItem이 존재하지 않거나 필드 구성이 다를 수 있습니다. ECC 환경이라면 본문에서 다루는 CDS 부분은 그대로 적용되지 않고, 비교 대상인 EKPO 직접 조회만 유효합니다.
핵심 개념
1) EKPO 직접 조회의 한계
EKPO는 클러스터/풀 테이블이 아닌 투명 테이블이라 기술적으로는 어디서든 SELECT가 가능합니다. 하지만 실무에 들어가면 다음 같은 부담이 생깁니다.
- 필드명이 4자리 약어(MATNR, MENGE, NETPR, BUKRS)로 되어 있어 코드 가독성이 떨어집니다.
- 금액 단위(WAERS), 수량 단위(MEINS)가 별도 필드로 흩어져 있어 변환을 직접 해야 합니다.
- 삭제 플래그(LOEKZ), Item Category(PSTYP) 같은 상태 필드를 매번 조건에 추가해야 합니다.
- EKKO(헤더), EKET(일정라인), MAKT(자재 텍스트) 등과의 조인을 매 SELECT마다 작성해야 합니다.
2) I_PurchaseOrderItem의 위치
VDM 계층에서 I_PurchaseOrderItem은 Interface View (접두어 I_)로 분류됩니다. 즉, 다른 CDS에서 재사용하기 위한 "안정된 계약"으로 SAP가 관리하는 뷰입니다. 비유하자면 EKPO가 "원자재 창고"라면, I_PurchaseOrderItem은 "포장 완료된 박스"입니다. 박스 안에는 자재 정보, 수량, 단가, 납품 일정으로 가는 연관(association)까지 함께 들어 있어, 우리가 추가로 조립할 일이 줄어듭니다.
3) 노출되는 핵심 필드
PurchaseOrder/PurchaseOrderItem— EKKO-EBELN, EKPO-EBELP의 의미적 이름Material— EKPO-MATNR (자재번호)OrderQuantity/PurchaseOrderQuantityUnit— EKPO-MENGE, EKPO-MEINSNetPriceAmount/DocumentCurrency— EKPO-NETPR, EKKO-WAERSPlant/StorageLocation— EKPO-WERKS, EKPO-LGORTPurchaseOrderItemCategory— EKPO-PSTYP (표준/하청/소비 등)
4) 따라오는 연관(Association)
이 뷰는 단독으로도 유용하지만, 진짜 강점은 association입니다. 표준에서 다음과 같은 경로가 제공됩니다(릴리즈마다 명칭이 약간 다를 수 있음).
_PurchaseOrder→I_PurchaseOrder(헤더)_Product→I_Product(자재 마스터)_ScheduleLine→I_PurchaseOrderScheduleLine(납품 일정)_Plant→I_Plant(플랜트)
덕분에 SELECT 한 번에 \_Product.ProductName처럼 점 표기로 자재 텍스트를 따라갈 수 있어, EKPO + MAKT 조인 같은 작업이 사라집니다.
직접 해보기
예제 1: EKPO 직접 조회 vs CDS View 비교
같은 요구사항(특정 회사코드의 활성 구매 오더 아이템에서 자재/수량/플랜트를 조회)을 두 방식으로 작성해 비교합니다.
" 방식 A: EKPO 직접 조회
DATA: lt_items_raw TYPE TABLE OF ekpo,
lt_headers TYPE TABLE OF ekko.
SELECT k~ebeln, k~bukrs, k~waers,
p~ebelp, p~matnr, p~menge, p~meins, p~werks
FROM ekko AS k
INNER JOIN ekpo AS p ON k~ebeln = p~ebeln
WHERE k~bukrs = @iv_company_code
AND p~loekz = @space
INTO TABLE @DATA(lt_legacy_items).
" 방식 B: I_PurchaseOrderItem 활용
SELECT PurchaseOrder,
PurchaseOrderItem,
Material,
OrderQuantity,
PurchaseOrderQuantityUnit,
Plant,
CompanyCode,
DocumentCurrency
FROM I_PurchaseOrderItem
WHERE CompanyCode = @iv_company_code
AND PurchaseOrderItemCategory <> '9' " 삭제 카테고리 제외 예시
INTO TABLE @DATA(lt_po_items).
방식 B는 조인이 사라졌고, 필드명이 의미 있는 단어로 바뀐 점이 한눈에 보입니다. 회사코드(CompanyCode), 통화(DocumentCurrency)는 헤더 조인 없이 이 뷰에서 바로 나옵니다(내부적으로는 SAP가 EKKO와 조인해 둔 상태).
예제 2: 자재 텍스트와 납품 일정까지 한번에
특정 플랜트의 최근 6개월간 구매 오더 아이템에 대해, 자재명과 첫 번째 납품 일정의 납기일을 함께 가져오는 시나리오입니다.
DATA(lv_from_date) = cl_abap_context_info=>get_system_date( ) - 180.
SELECT poi~PurchaseOrder,
poi~PurchaseOrderItem,
poi~Material,
poi~OrderQuantity,
poi~PurchaseOrderQuantityUnit,
prod~ProductName AS material_name,
hdr~Supplier AS supplier_id,
hdr~PurchaseOrderDate AS po_date,
sched~ScheduleLineDeliveryDate AS first_delivery_date
FROM I_PurchaseOrderItem AS poi
LEFT OUTER JOIN I_Product AS prod
ON poi~Material = prod~Product
LEFT OUTER JOIN I_PurchaseOrder AS hdr
ON poi~PurchaseOrder = hdr~PurchaseOrder
LEFT OUTER JOIN I_PurchaseOrderScheduleLine AS sched
ON poi~PurchaseOrder = sched~PurchaseOrder
AND poi~PurchaseOrderItem = sched~PurchaseOrderItem
WHERE poi~Plant = @iv_plant_id
AND hdr~PurchaseOrderDate >= @lv_from_date
ORDER BY poi~PurchaseOrder, poi~PurchaseOrderItem
INTO TABLE @DATA(lt_po_view).
로그 관점에서는 BAL(Application Log) 대신 간단히 cl_demo_output으로 결과를 찍어 빠르게 확인하는 것을 추천합니다. 운영 코드에서는 NO_SUBMITS 옵션이 붙는 백그라운드 잡 컨텍스트인지에 따라 로그 전략을 다르게 가져가야 합니다.
TRY.
" 위 SELECT 블록 호출
CATCH cx_sy_open_sql_db INTO DATA(lx_sql).
MESSAGE lx_sql->get_text( ) TYPE 'E'.
ENDTRY.
IF lt_po_view IS INITIAL.
cl_demo_output=>display( |No purchase order items found for plant { iv_plant_id }| ).
ELSE.
cl_demo_output=>display( lt_po_view ).
ENDIF.
예제 3: 재사용 가능한 프로젝션 뷰로 감싸기
매번 같은 조인 코드를 반복하지 않도록, 우리 업무에 맞춘 Custom CDS View를 한 겹 더 두는 패턴입니다. 이렇게 두면 Fiori Elements나 RAP에서도 같은 뷰를 재활용할 수 있습니다.
@AbapCatalog.sqlViewName: 'ZVPOITEMOVW'
@AbapCatalog.compiler.compareFilter: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'PO Item Overview (custom)'
define view ZC_PoItemOverview
as select from I_PurchaseOrderItem as poi
association [0..1] to I_Product as _Product
on poi.Material = _Product.Product
association [0..1] to I_PurchaseOrder as _Header
on poi.PurchaseOrder = _Header.PurchaseOrder
association [0..*] to I_PurchaseOrderScheduleLine as _Schedule
on poi.PurchaseOrder = _Schedule.PurchaseOrder
and poi.PurchaseOrderItem = _Schedule.PurchaseOrderItem
{
key poi.PurchaseOrder,
key poi.PurchaseOrderItem,
poi.Material,
_Product.ProductType as material_type,
poi.Plant,
poi.OrderQuantity,
poi.PurchaseOrderQuantityUnit,
poi.NetPriceAmount,
poi.DocumentCurrency,
_Header.Supplier,
_Header.PurchaseOrderDate,
// 첫 일정라인 납기일을 단일 값으로 노출하고 싶다면
// 별도 helper view에서 min(ScheduleLineDeliveryDate)을 만들어 association 권장
_Product,
_Header,
_Schedule
}
프로덕션 관점의 보안/성능 체크 포인트입니다.
@AccessControl.authorizationCheck: #CHECK로 설정해 PFCG 권한 객체(M_BEST_BSA 등)가 자동 평가되도록 합니다.#NOT_REQUIRED는 분석용으로만 한정해 사용합니다.- WHERE 조건이 거의 항상
CompanyCode또는Plant단위라면, 호출 코드에서 반드시 해당 값을 binding해 풀스캔을 피합니다. - 대량 추출 잡에서는
PACKAGE SIZE를 사용한 커서 기반 패턴을 적용해 메모리 사용을 제한합니다. - 단위 테스트는 ABAP Unit + CDS Test Double Framework(
cl_cds_test_environment)로I_PurchaseOrderItem을 더블링해, EKPO에 의존하지 않는 테스트를 작성합니다.
삽질 노트
Q1. EKPO에는 분명히 데이터가 있는데 I_PurchaseOrderItem에서 0건이 나옵니다.
대표적인 원인은 두 가지입니다. 첫째, 권한 체크가 #CHECK로 걸려 있어서 호출 사용자에게 해당 회사코드/플랜트 권한이 없을 때입니다. SU53으로 확인해 보면 EKPO 자체 권한이 아니라 VDM이 적용한 권한 객체가 누락된 경우가 많습니다. 둘째, 표준 뷰가 특정 Item Category나 삭제 플래그를 내부에서 필터링하고 있을 수 있습니다. ADT에서 뷰의 DDL Source를 열어 WHERE 절을 직접 확인하는 것이 가장 확실합니다.
Q2. NetPriceAmount의 통화 변환이 이상하게 나옵니다.
CDS에서 금액 필드는 @Semantics.amount.currencyCode로 통화 필드와 짝지어져 있어야 정상적으로 표시됩니다. 커스텀 뷰에서 가공할 때 통화 필드를 빠뜨리면 SELECT 결과는 숫자만 나와도, Fiori 리스트에서 포매팅이 깨지거나 단위 변환이 0으로 처리될 수 있습니다. DocumentCurrency를 함께 가져오고, 가공 뷰에서도 annotation을 유지해 주세요.
Q3. EKPO에 있던 커스텀 필드(ZZ_*)가 표준 뷰에는 보이지 않습니다.
이 경우 두 가지 접근이 있습니다. 권장되는 방식은 Custom Field & Logic 앱이나 Key User Extensibility로 EKPO 확장 필드를 만들고, 표준 뷰의 Extension Include에 자동 반영되도록 하는 흐름입니다. 클래식 ABAP에서는 EXTEND VIEW 구문으로 I_PurchaseOrderItem에 필드를 추가하는 패턴이 있으나, 표준 업데이트로 깨질 위험이 있어 가능하면 SAP가 제공하는 확장 포인트를 우선 검토하는 편이 안전합니다.
Q4. 성능이 EKPO 직접 조회보다 느린 것 같습니다.
대부분 WHERE 조건을 association을 통해 따라간 필드에 거는 경우입니다. 예를 들어 _Header.Supplier로 필터하면 헤더 조인이 항상 평가됩니다. 가능하면 I_PurchaseOrderItem이 자체 필드로 가진 항목(CompanyCode, Plant, Material 등)에 먼저 조건을 걸고, 그다음에 헤더 필드를 사용하는 것이 일반적으로 더 빠릅니다. ST05/SAT로 실제 실행 계획을 한 번 떠 보는 것을 추천합니다.
더 파볼 주제
- I_PurchaseOrder — 헤더 뷰에서 공급업체, 결제 조건, 통화 흐름 살펴보기
- I_PurchaseOrderScheduleLine / I_PurchaseOrderHistory — 납기 분석과 GR/IR 분석으로 확장
- C_PurchaseOrderItemTP — RAP 트랜잭셔널 모델로 구매 오더 아이템 변경 처리
- Analytical Query (Q_) — 같은 데이터 소스 위에 OData/Analytics용 뷰를 얹어 Fiori KPI로 활용
- ABAP Cloud / Released APIs — Steampunk 환경에서
I_PurchaseOrderItem의 릴리즈 상태(C1/C2) 확인 및 호출
더 읽어볼 자료
- SAP S/4HANA Help Portal — 제품 도움말 홈
- Virtual Data Model (VDM) 개념 안내
- ABAP CDS — Core Data Services Reference
- SAP S/4HANA Cloud, public edition — Help Portal
- SAP API Business Hub — Purchase Order 관련 OData/CDS
- SAP Community — ABAP 토픽 페이지
- SAP Developers — ADT 설치 안내
핵심 한 줄
EKPO는 여전히 살아 있지만, S/4HANA 시대의 구매 오더 아이템 조회는 I_PurchaseOrderItem을 시작점으로 잡고 association으로 헤더/자재/일정라인까지 의미 단위로 따라가는 패턴이 코드 가독성, 권한 처리, 재사용성 측면에서 일반적으로 더 유리합니다.
댓글 0
아직 댓글이 없습니다.