ABAP

EKPO vs I_PurchaseOrderItem — 구매 아이템 조회 차이 #shorts #SAP #ABAP

▶ YouTube에서 보기

이 글이 답하는 질문

구매 오더 아이템을 조회할 때 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_ProductI_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-MEINS
  • NetPriceAmount / DocumentCurrency — EKPO-NETPR, EKKO-WAERS
  • Plant / StorageLocation — EKPO-WERKS, EKPO-LGORT
  • PurchaseOrderItemCategory — EKPO-PSTYP (표준/하청/소비 등)

4) 따라오는 연관(Association)

이 뷰는 단독으로도 유용하지만, 진짜 강점은 association입니다. 표준에서 다음과 같은 경로가 제공됩니다(릴리즈마다 명칭이 약간 다를 수 있음).

  • _PurchaseOrderI_PurchaseOrder (헤더)
  • _ProductI_Product (자재 마스터)
  • _ScheduleLineI_PurchaseOrderScheduleLine (납품 일정)
  • _PlantI_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) 확인 및 호출

더 읽어볼 자료

핵심 한 줄

EKPO는 여전히 살아 있지만, S/4HANA 시대의 구매 오더 아이템 조회는 I_PurchaseOrderItem을 시작점으로 잡고 association으로 헤더/자재/일정라인까지 의미 단위로 따라가는 패턴이 코드 가독성, 권한 처리, 재사용성 측면에서 일반적으로 더 유리합니다.

댓글 0

아직 댓글이 없습니다.