ABAP

EKKO vs I_PurchaseOrder — 뭐가 더 들어있나 #shorts #SAP #ABAP

▶ YouTube에서 보기

개요 및 이 글에서 다루는 핵심 포인트

SAP S/4HANA 환경에서 구매 오더 헤더 데이터를 다룰 때, 과거 ABAP 개발자들은 EKKO 테이블을 직접 SELECT 하는 방식에 익숙했습니다. 그러나 S/4HANA 도입 이후 SAP는 VDM(Virtual Data Model) 기반의 CDS View를 표준 데이터 액세스 계층으로 권장하고 있으며, 그 대표적인 사례가 바로 I_PurchaseOrder입니다. 이 글은 EKKO 테이블의 한계를 보완하고 표준화된 데이터를 제공하는 I_PurchaseOrder의 구조, 조인 관계, 실전 활용법을 단계별 예제로 풀어갑니다.

  • I_PurchaseOrder가 EKKO와 어떤 관계를 가지는지 이해한다
  • EKKO 외에 어떤 테이블(LFA1, T001, T024 등)이 조인되어 있는지 파악한다
  • Released API 관점에서 I_PurchaseOrder를 안전하게 소비하는 방법을 익힌다
  • Consumption View와 Association 패턴을 실전 코드로 구현한다
  • ABAP 프로그램에서 SELECT 문으로 데이터를 추출하는 실무 패턴을 습득한다

이 글을 읽기 전에 알아두면 좋은 것들

이 글은 ABAP 개발 경험이 있는 중급 개발자를 대상으로 합니다. EKKO/EKPO 테이블 구조에 대한 기본 이해, ABAP Open SQL 문법, CDS View의 기본 개념(DEFINE VIEW, ASSOCIATION TO, Annotation), 그리고 SAP S/4HANA의 VDM 계층(Basic/Composite/Consumption View) 구분을 사전에 학습해두면 따라가기 수월합니다. ADT(ABAP Development Tools for Eclipse) 사용 경험도 권장합니다.

실습 환경 및 사전 준비 사항

이 글에서 다루는 코드와 개념은 다음 환경을 기준으로 작성되었습니다.

  • 시스템: SAP S/4HANA 2022 또는 그 이상 (On-Premise 또는 Private Cloud Edition)
  • ABAP 릴리스: ABAP Platform 7.57 이상
  • 개발 도구: Eclipse 2023-03 이상 + ABAP Development Tools (ADT) 3.34+
  • 권한: S_DEVELOP, MM_MASTER 권한 그룹, 그리고 EKKO/EKPO 테이블 조회 권한
  • 샘플 데이터: 표준 MM 구매 트랜잭션 데이터 (ME21N으로 생성 가능)

SAP S/4HANA Cloud Public Edition에서도 I_PurchaseOrder는 Released 상태로 제공되지만, 일반적으로 Custom CDS View나 RAP Business Object를 통해 간접적으로 소비하는 패턴이 권장됩니다. On-Premise 환경에서는 SE11이나 ADT의 View Editor를 통해 소스를 직접 확인할 수 있습니다.

I_PurchaseOrder의 본질과 EKKO와의 관계

I_PurchaseOrder는 SAP VDM 계층에서 Basic Interface View로 분류되며, 이름 앞에 붙은 'I_' 접두어가 이를 의미합니다. 이 뷰는 EKKO(구매 오더 헤더) 테이블을 핵심 기반으로 삼되, 단순히 EKKO를 1:1로 노출하는 것이 아니라 다음과 같은 부가 가치를 제공합니다.

  • 필드명 표준화: EKKO의 EBELN → PurchaseOrder, BUKRS → CompanyCode 처럼 의미가 명확한 영문 필드명으로 매핑
  • 관련 마스터 데이터 조인: 공급업체(LFA1), 회사코드(T001), 구매 조직(T024E), 구매 그룹(T024) 등의 텍스트 정보가 Association을 통해 즉시 접근 가능
  • Released API 보장: SAP가 호환성을 일반적으로 유지하는 안정 계약이므로 업그레이드 시 영향이 최소화됨
  • Analytical/Transactional 어노테이션: Fiori, OData, CDP 등 다양한 소비 채널을 위한 메타데이터가 사전 구성

비유하자면 EKKO가 '날 것의 재료가 담긴 창고'라면, I_PurchaseOrder는 '재료 + 양념 + 라벨이 표준 규격으로 포장된 키트'입니다. 개발자는 더 이상 EKKO에서 통화 단위, 공급업체 이름, 조직 텍스트를 별도 JOIN으로 가져올 필요 없이 Association 한 번으로 접근할 수 있습니다.

아래 도식은 I_PurchaseOrder를 중심으로 한 데이터 흐름을 단순화한 것입니다.


        [EKKO]                       [LFA1 - 공급업체]
        구매오더 헤더 ────────┐    ┌──── Supplier Name/Address
                              │    │
                              ▼    ▼
                    ┌──────────────────────┐
                    │   I_PurchaseOrder     │ <── I_Supplier (Assoc)
                    │   (Basic VDM View)    │ <── I_CompanyCode (Assoc)
                    └──────────────────────┘ <── I_PurchaseOrderItem (Assoc)
                              ▲    ▲
                              │    │
        [T001]                │    │   [T024/T024E]
        회사코드 ─────────────┘    └─── 구매조직/구매그룹

EKKO에 약 150개 이상의 필드가 존재하지만, I_PurchaseOrder는 그중 비즈니스적으로 유의미한 약 80~90개의 필드만 노출합니다. 사용 빈도가 낮거나 마이그레이션 잔재 필드는 제외되며, 일부는 의미 단위로 재구성됩니다.

실전 예제 1단계 — 기본 SELECT로 구매 오더 헤더 조회

가장 단순한 시나리오부터 시작합니다. 특정 회사코드와 공급업체 조건으로 최근 30일간 등록된 구매 오더 헤더를 추출하는 ABAP 코드입니다.

REPORT zr_vendor_po_basic.

DATA: lt_vendor_po TYPE STANDARD TABLE OF i_purchaseorder,
      lv_from_date TYPE dats.

lv_from_date = sy-datum - 30.

SELECT PurchaseOrder,
       CompanyCode,
       Supplier,
       PurchaseOrderType,
       DocumentCurrency,
       CreationDate,
       CreatedByUser,
       PurchaseOrderDate
  FROM i_purchaseorder
  WHERE CompanyCode  = '1010'
    AND Supplier     = '0000100214'
    AND CreationDate >= @lv_from_date
  ORDER BY CreationDate DESCENDING
  INTO TABLE @lt_vendor_po.

LOOP AT lt_vendor_po INTO DATA(ls_po).
  WRITE: / ls_po-purchaseorder,
           ls_po-supplier,
           ls_po-documentcurrency,
           ls_po-purchaseorderdate.
ENDLOOP.

이 예제의 핵심은 EKKO를 직접 조회할 때와 비교해 필드명이 비즈니스 친화적이라는 점입니다. EBELN 대신 PurchaseOrder, LIFNR 대신 Supplier를 사용하므로 코드 가독성이 크게 향상됩니다.

실전 예제 2단계 — Consumption View 생성과 Association 활용

실무에서는 단순 SELECT를 넘어 자체 Consumption View를 만들어 Fiori Elements나 OData 서비스에서 활용하는 경우가 많습니다. 아래는 I_PurchaseOrder를 기반으로 공급업체 명칭과 회사코드 명칭을 함께 노출하는 Custom Consumption View 예시입니다.

@AbapCatalog.sqlViewName: 'ZCVENDORPOLIST'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Vendor PO Dashboard Consumption'
@OData.publish: true
@VDM.viewType: #CONSUMPTION

define view ZC_VendorPoOverview
  as select from I_PurchaseOrder as PoHeader

  association [0..1] to I_Supplier     as _Supplier
    on $projection.Supplier = _Supplier.Supplier

  association [0..1] to I_CompanyCode  as _CompanyCode
    on $projection.CompanyCode = _CompanyCode.CompanyCode

  association [0..*] to I_PurchaseOrderItem as _Items
    on $projection.PurchaseOrder = _Items.PurchaseOrder
{
  key PoHeader.PurchaseOrder,
      PoHeader.CompanyCode,
      _CompanyCode.CompanyCodeName       as CompanyCodeText,
      PoHeader.Supplier,
      _Supplier.SupplierName             as SupplierName,
      PoHeader.PurchaseOrderType,
      PoHeader.DocumentCurrency,
      PoHeader.PurchaseOrderDate,
      PoHeader.CreatedByUser,

      cast( 0 as abap.curr( 23, 2 ) )    as TotalNetAmount,

      _Supplier,
      _CompanyCode,
      _Items
}

이 코드의 학습 포인트는 다음과 같습니다. 첫째, I_PurchaseOrder는 이미 EKKO 기반으로 정규화되어 있어 별도 JOIN 없이 마스터 데이터에 접근할 수 있습니다. 둘째, I_SupplierI_CompanyCode 같은 다른 Basic View들과 Association을 맺어 텍스트 정보를 자연스럽게 끌어옵니다. 셋째, _Items Association을 노출하면 OData 서비스에서 $expand=_Items 형태로 헤더-아이템을 한 번에 조회할 수 있습니다.

실전 예제 3단계 — 운영 환경을 고려한 ABAP 클래스 설계

실무 코드는 단순 SELECT보다 더 견고한 에러 처리, 로깅, 성능 최적화가 필요합니다. 아래는 구매 오더 헤더 데이터를 추출해 JSON으로 직렬화하는 서비스 클래스 예시입니다.

CLASS zcl_vendor_po_reader DEFINITION
  PUBLIC FINAL CREATE PUBLIC.

  PUBLIC SECTION.
    TYPES: BEGIN OF ty_po_summary,
             purchase_order TYPE i_purchaseorder-purchaseorder,
             supplier       TYPE i_purchaseorder-supplier,
             company_code   TYPE i_purchaseorder-companycode,
             currency       TYPE i_purchaseorder-documentcurrency,
             po_date        TYPE i_purchaseorder-purchaseorderdate,
           END OF ty_po_summary,
           tt_po_summary TYPE STANDARD TABLE OF ty_po_summary WITH EMPTY KEY.

    METHODS: fetch_recent_orders
               IMPORTING iv_company_code TYPE bukrs
                         iv_days_back    TYPE i DEFAULT 30
                         iv_max_rows     TYPE i DEFAULT 1000
               RETURNING VALUE(rt_orders) TYPE tt_po_summary
               RAISING   cx_sy_open_sql_db.

  PRIVATE SECTION.
    METHODS: log_message
               IMPORTING iv_text TYPE string.
ENDCLASS.

CLASS zcl_vendor_po_reader IMPLEMENTATION.
  METHOD fetch_recent_orders.
    DATA(lv_from_date) = CONV dats( sy-datum - iv_days_back ).

    TRY.
        SELECT FROM i_purchaseorder
          FIELDS PurchaseOrder,
                 Supplier,
                 CompanyCode,
                 DocumentCurrency,
                 PurchaseOrderDate
          WHERE CompanyCode      = @iv_company_code
            AND PurchaseOrderDate >= @lv_from_date
            AND PurchasingDocumentDeletionCode = ''
          ORDER BY PurchaseOrderDate DESCENDING
          INTO CORRESPONDING FIELDS OF TABLE @rt_orders
          UP TO @iv_max_rows ROWS.

        log_message( |Fetched { lines( rt_orders ) } POs for { iv_company_code }| ).

      CATCH cx_sy_open_sql_db INTO DATA(lx_db).
        log_message( |DB error: { lx_db->get_text( ) }| ).
        RAISE EXCEPTION lx_db.
    ENDTRY.
  ENDMETHOD.

  METHOD log_message.
    cl_demo_output=>write( iv_text ).
  ENDMETHOD.
ENDCLASS.

이 패턴의 핵심 고려사항. 최대 행 수 제한으로 대량 조회 시 메모리 폭주를 방지합니다. 삭제 플래그 필터(PurchasingDocumentDeletionCode)를 추가해 논리 삭제된 PO를 제외합니다. 예외 처리를 통해 DB 단절이나 권한 오류 시 명확한 진단 정보를 제공합니다.

자주 마주치는 함정과 해결 방법

현장에서 I_PurchaseOrder를 사용할 때 흔히 발생하는 문제와 대응책을 정리합니다.

Q1. EKKO에는 있는 필드가 I_PurchaseOrder에서는 보이지 않습니다.
A. VDM Basic View는 의도적으로 일부 필드를 제외합니다. 정말 필요하다면 ADT에서 I_PurchaseOrder 소스를 열어 미노출 필드를 확인하고, Custom Extension View(EXTEND VIEW)를 만들어 추가하는 방식이 권장됩니다. EKKO를 직접 JOIN 하는 것은 가능한 한 피하세요.

Q2. SELECT 성능이 EKKO 직접 조회보다 느립니다.
A. SELECT *로 모든 필드를 가져오면 일부 가상 필드 계산 비용이 발생할 수 있으므로, 필요한 필드만 명시적으로 지정하는 것이 권장됩니다. WHERE 절에 인덱스 친화적인 키(CompanyCode, PurchaseOrder, Supplier)를 사용해야 합니다.

Q3. S/4HANA Cloud에서 I_PurchaseOrder를 직접 SELECT 할 수 없다고 합니다.
A. Cloud Public Edition에서는 Released Object 정책에 따라 직접 액세스가 제한될 수 있습니다. 이 경우 RAP Business Object의 Behavior나 공개된 OData API(API_PURCHASEORDER_PROCESS_SRV)를 통해 접근하는 것이 일반적입니다.

Q4. 삭제된 PO도 같이 조회됩니다.
A. I_PurchaseOrder는 EKKO의 모든 레코드를 노출하므로 논리 삭제 항목도 포함됩니다. PurchasingDocumentDeletionCode 필드로 필터링하거나, 릴리스 상태(PurchasingDocumentReleaseStatus)를 함께 확인해야 합니다.

이 글 이후 더 깊이 파고들 만한 주제

  • I_PurchaseOrderItem: EKPO 기반 아이템 뷰. 헤더-아이템 연결 패턴의 핵심
  • C_PurchaseOrderTP (RAP BO): RAP 기반 트랜잭션 처리를 위한 Composite View
  • API_PURCHASEORDER_PROCESS_SRV: 외부 시스템 연동용 OData API
  • CDS Extension: 표준 뷰를 확장해 커스텀 필드 추가하기
  • Analytical Query: I_PurchaseOrder 기반으로 KPI 대시보드 구성

더 깊이 공부할 수 있는 외부 리소스

  • help.sap.com — SAP S/4HANA 공식 도움말 포털
  • help.sap.com — CDS Virtual Data Model (VDM) 가이드
  • help.sap.com — Released Objects for S/4HANA
  • SAP Business Accelerator Hub: PO Process API
  • SAP Community 블로그 - CDS View 태그

댓글 0

아직 댓글이 없습니다.