개요 및 이 글에서 다루는 범위
구매 발주(PO)의 납기는 한 줄이 아니라 여러 라인으로 분할되는 경우가 많습니다. 자재 1000개를 한 번에 받기보다 500개씩 두 차례 나눠 받는 식이죠. 이런 분할 납기 정보를 저장하는 EKET 테이블과, 이를 S/4HANA 환경에서 안전하게 소비할 수 있도록 SAP가 제공하는 I_ScheduleLine CDS 뷰의 동작 원리와 실전 활용법을 이 글에서 다룹니다.
이 글을 끝까지 따라가면 다음을 익힐 수 있습니다.
- EKET 핵심 컬럼(EBELN, EBELP, ETENR, EINDT, MENGE)의 의미와 키 구조 이해
I_ScheduleLine의 연관(Association) 구조와 필드 매핑 파악- 구매 발주 납기 지연 리스트를 CDS + ABAP RAP로 추출하는 방법
- 대량 데이터 환경에서의 인덱스/필터 전략과 권한 체크 적용 방법
읽기 전에 알고 있으면 좋은 것
이 글은 ABAP CDS 기본 문법(define view entity, association, annotation)과 OpenSQL SELECT 구문에 익숙한 개발자를 대상으로 합니다. MM 모듈의 구매 발주(ME21N/ME23N) 화면을 한 번이라도 본 경험이 있다면 EKET가 무엇을 의미하는지 더 빨리 와닿을 것입니다. ABAP RAP의 BDEF 정의나 Behavior Implementation까지는 몰라도 무방하나, 7.50 이상 NetWeaver 또는 S/4HANA 기반 ADT(Eclipse) 사용 경험은 전제로 합니다.
환경 및 사전 준비
예제 검증 환경은 다음과 같습니다.
- SAP S/4HANA 2022 (on-Premise) 또는 S/4HANA Cloud Private Edition
- ABAP Platform 2022, AS ABAP 7.57 이상
- ADT (ABAP Development Tools for Eclipse) 3.36 이상
- 권한:
S_DEVELOP(개발),M_BEST_BSA(구매 발주 조회) 권한 오브젝트 - 샘플 데이터: 트랜잭션 ME21N으로 분할 납기를 가진 PO 2~3건 생성 권장
S/4HANA 1909 이하의 경우 I_ScheduleLine이 존재하지 않거나 필드 구조가 다를 수 있습니다. 사용 전 ADT에서 Open Data Preview로 뷰 메타데이터를 확인하길 권장합니다. 또한 EKET를 직접 SELECT 하는 레거시 코드를 마이그레이션하는 경우, WERKS(플랜트), MATNR(자재)는 EKET가 아닌 EKPO에 있으므로 조인이 필요한 점을 기억해야 합니다.
핵심 개념 — EKET와 I_ScheduleLine의 관계
구매 발주 데이터는 크게 세 계층으로 나뉩니다. 머리 정보를 담는 EKKO, 라인 아이템을 담는 EKPO, 그리고 각 아이템의 납기 분할을 담는 EKET입니다. 비유하자면 EKKO는 영수증 전체, EKPO는 영수증의 각 품목, EKET는 그 품목을 "언제 몇 개씩 나눠 받을지"를 기록한 메모입니다.
EKET의 주요 키와 필드는 다음과 같습니다.
| 필드 | 의미 | 키 여부 |
|---|---|---|
| EBELN | 구매 발주 번호 | Key |
| EBELP | 발주 라인 아이템 번호 | Key |
| ETENR | 납기 일정 라인 번호 | Key |
| EINDT | 납기 예정일 | - |
| MENGE | 납기 수량 | - |
| WEMNG | 입고 완료 수량 | - |
| BANFN/BNFPO | 연결된 구매 요청 번호/라인 | - |
I_ScheduleLine은 이 EKET 테이블을 한 번 감싸서 가상 데이터 모델(VDM, Virtual Data Model)에 편입시킨 인터페이스 뷰입니다. 직접 EKET를 조회하는 것과 달리, 다음 이점이 있습니다.
- 필드명이 의미 중심으로 리네이밍됨 (예:
EBELN → PurchaseOrder,EBELP → PurchaseOrderItem,ETENR → ScheduleLine,EINDT → ScheduleLineDeliveryDate) - 상위 엔티티(
I_PurchaseOrderItem,I_PurchaseOrder)와 association으로 즉시 연결 가능 - 고객 확장(extension include) 및 권한 체크(
@AccessControl.authorizationCheck)가 일관되게 적용됨 - 릴리스 컨트랙트(C1 또는 C2)에 따라 향후 호환성 보장 수준이 명시됨
요약하면 EKET가 "원본 테이블"이라면 I_ScheduleLine은 "그 테이블 위에 표준화된 비즈니스 의미를 입힌 어댑터"입니다. 신규 개발에서는 EKET를 직접 건드리기보다 인터페이스 뷰를 통해 접근하는 편이 일반적으로 권장됩니다.
단계별 실전 예제 1 — 기본 조회
먼저 특정 구매 발주의 모든 납기 라인을 조회하는 가장 단순한 형태입니다. 화면에서 ME23N으로 PO 번호 4500000123을 조회한 결과를 ABAP에서 재현한다고 가정합니다.
REPORT zr_po_schedule_basic.
DATA: lt_schedule TYPE STANDARD TABLE OF i_scheduleline.
SELECT purchaseorder,
purchaseorderitem,
scheduleline,
schedulelinedeliverydate,
schedulelineorderquantity
FROM i_scheduleline
WHERE purchaseorder = '4500000123'
ORDER BY purchaseorderitem, scheduleline
INTO TABLE @lt_schedule.
LOOP AT lt_schedule ASSIGNING FIELD-SYMBOL(<ls_line>).
WRITE: / <ls_line>-purchaseorder,
<ls_line>-purchaseorderitem,
<ls_line>-scheduleline,
<ls_line>-schedulelinedeliverydate,
<ls_line>-schedulelineorderquantity.
ENDLOOP.
실제 필드명은 시스템 릴리스에 따라 약간 다를 수 있으므로 ADT에서 F2(Element Info)로 확인하는 습관이 중요합니다. 위 코드는 단일 PO에 대한 분할 납기를 라인 번호 순으로 출력합니다.
단계별 실전 예제 2 — 납기 지연 분석 (실무 시나리오)
실무에서는 단일 PO 조회보다 "오늘 기준으로 납기가 지난, 그러나 아직 입고가 완료되지 않은 라인"을 찾는 일이 훨씬 많습니다. 이를 위해 커스텀 CDS 뷰를 한 단계 더 올려 작성합니다.
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Overdue Purchase Order Schedule Lines'
@Metadata.allowExtensions: true
define view entity ZC_OverdueScheduleLine
as select from I_ScheduleLine as sl
association [1..1] to I_PurchaseOrderItem as _Item
on $projection.PurchaseOrder = _Item.PurchaseOrder
and $projection.PurchaseOrderItem = _Item.PurchaseOrderItem
{
key sl.PurchaseOrder,
key sl.PurchaseOrderItem,
key sl.ScheduleLine,
sl.ScheduleLineDeliveryDate as PlannedDeliveryDate,
sl.ScheduleLineOrderQuantity as PlannedQuantity,
sl.OpenQuantityInBaseUnit as OpenQuantity,
cast( dats_days_between(
sl.ScheduleLineDeliveryDate, $session.system_date
) as abap.int4 ) as DelayInDays,
_Item.Material,
_Item.Plant,
_Item.Supplier,
_Item
}
where sl.ScheduleLineDeliveryDate < $session.system_date
and sl.OpenQuantityInBaseUnit > 0
이 뷰를 ABAP 측에서 호출하면서 예외 처리와 애플리케이션 로그를 함께 적용하면 다음과 같습니다.
CLASS zcl_overdue_report DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
METHODS run IMPORTING iv_plant TYPE werks_d
RAISING cx_sy_open_sql_db.
ENDCLASS.
CLASS zcl_overdue_report IMPLEMENTATION.
METHOD run.
DATA(lo_log) = cl_bali_log=>create_with_header(
EXPORTING header = cl_bali_header_setter=>create(
object = 'ZPO_DELAY'
subobject = 'CHECK' ) ).
TRY.
SELECT PurchaseOrder, PurchaseOrderItem, ScheduleLine,
PlannedDeliveryDate, OpenQuantity, DelayInDays,
Material, Supplier
FROM zc_overduescheduleline
WHERE Plant = @iv_plant
ORDER BY DelayInDays DESCENDING
INTO TABLE @DATA(lt_overdue).
IF lt_overdue IS INITIAL.
MESSAGE 'No overdue schedule lines found' TYPE 'S'.
RETURN.
ENDIF.
cl_demo_output=>display( lt_overdue ).
CATCH cx_sy_open_sql_db INTO DATA(lx_sql).
lo_log->add_item( 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.
핵심은 두 가지입니다. 첫째, OpenQuantityInBaseUnit > 0 필터로 이미 입고 완료된 라인을 제외합니다. 둘째, dats_days_between 함수를 사용해 지연 일수를 DB 레벨에서 계산함으로써 ABAP 측 LOOP를 줄였습니다.
단계별 실전 예제 3 — 프로덕션 수준의 최적화와 권한
실 운영 환경에서는 대량 데이터, 권한, 테스트 가능성을 모두 고려해야 합니다. 다음은 RAP 서비스로 노출할 때 자주 쓰는 패턴입니다.
@AccessControl.authorizationCheck: #CHECK
@VDM.viewType: #COMPOSITE
@ObjectModel.usageType.serviceQuality: #D
@ObjectModel.usageType.sizeCategory: #XL
@ObjectModel.usageType.dataClass: #TRANSACTIONAL
define view entity ZC_ScheduleLineAggregate
as select from I_ScheduleLine as sl
association [0..1] to I_Plant as _Plant on $projection.Plant = _Plant.Plant
association [0..1] to I_Supplier as _Supp on $projection.Supplier = _Supp.Supplier
{
key sl._PurchaseOrderItem.Plant as Plant,
key sl._PurchaseOrderItem.Supplier as Supplier,
key sl.ScheduleLineDeliveryDate as DeliveryDate,
sum( sl.ScheduleLineOrderQuantity ) as TotalQuantity,
count( * ) as LineCount,
_Plant,
_Supp
}
group by sl._PurchaseOrderItem.Plant,
sl._PurchaseOrderItem.Supplier,
sl.ScheduleLineDeliveryDate
위 집계 뷰에는 다음 어노테이션이 의도적으로 들어가 있습니다.
@AccessControl.authorizationCheck: #CHECK— 사용자 권한이 PFCG 역할에 정의된 플랜트 범위로 자동 필터링됨@ObjectModel.usageType.sizeCategory: #XL— 옵티마이저에 대량 데이터 힌트 제공@VDM.viewType: #COMPOSITE— 복합 뷰로 분류되어 다른 컨슈머 뷰가 안전하게 재사용 가능
단위 테스트는 ABAP Unit과 CL_CDS_TEST_ENVIRONMENT를 함께 사용해 테스트 더블을 생성하는 방식이 일반적입니다.
CLASS ltc_schedule DEFINITION FINAL FOR TESTING
DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
CLASS-DATA mo_env TYPE REF TO if_cds_test_environment.
CLASS-METHODS class_setup.
METHODS overdue_lines_are_found FOR TESTING.
ENDCLASS.
CLASS ltc_schedule IMPLEMENTATION.
METHOD class_setup.
mo_env = cl_cds_test_environment=>create(
i_for_entity = 'ZC_OVERDUESCHEDULELINE' ).
ENDMETHOD.
METHOD overdue_lines_are_found.
mo_env->clear_doubles( ).
mo_env->insert_test_data( VALUE i_scheduleline_t(
( purchaseorder = '4500000999'
purchaseorderitem = '00010'
scheduleline = '0001'
schedulelinedeliverydate = '20260101'
openquantityinbaseunit = '50' ) ) ).
SELECT COUNT(*) FROM zc_overduescheduleline INTO @DATA(lv_count).
cl_abap_unit_assert=>assert_equals( exp = 1 act = lv_count ).
ENDMETHOD.
ENDCLASS.
자주 마주치는 함정과 해결책
Q1. EKET에 데이터는 있는데 I_ScheduleLine에는 나오지 않습니다.
대부분 권한 문제입니다. @AccessControl.authorizationCheck: #CHECK가 적용된 뷰는 PFCG 역할의 플랜트/구매 그룹 권한이 없으면 결과를 자동 필터링합니다. 디버깅 시에는 SE16N으로 EKET 직접 조회와 비교해 보세요. 또한 삭제 플래그(LOEKZ)가 설정된 라인은 표준 인터페이스 뷰에서 제외되는 경우가 있습니다.
Q2. 분할 납기 수량 합계가 EKPO의 발주 수량과 맞지 않습니다.
EKPO-MENGE와 EKET-MENGE의 합은 원칙적으로 일치해야 하지만, 자재 마스터 변경, 라인 분할 후 일부 취소, 또는 단위 변환 차이로 미세하게 어긋날 수 있습니다. 비교 리포트를 만들 때는 단위(MEINS)를 함께 검증하고, 필요하면 UNIT_CONVERSION 함수로 정규화한 뒤 비교해야 합니다.
Q3. 대용량 PO 데이터에서 쿼리가 느립니다.
I_ScheduleLine은 EKET 외 여러 연관 테이블을 LEFT OUTER JOIN으로 가져옵니다. 필요한 컬럼만 SELECT하고, PurchaseOrder 또는 ScheduleLineDeliveryDate 같은 인덱스 컬럼에 WHERE를 거는 것이 핵심입니다. 또한 ST05로 트레이스를 떠보면 의외로 ABAP 측 LOOP가 병목인 경우가 많아, 가능한 한 DB 집계 함수를 활용하는 편이 일반적으로 권장됩니다.
Q4. S/4HANA Cloud에서 EKET를 직접 읽으려 하면 인증 실패합니다.
Cloud Edition에서는 EKET 같은 클러스터/풀 테이블 직접 접근이 화이트리스트에 없습니다. 반드시 I_ScheduleLine 또는 I_PurchaseOrderScheduleLineAPI 같은 릴리스된 뷰를 통해 접근해야 합니다.
관련 주제로의 확장
여기까지 이해했다면 다음 주제로 넓혀 보길 권합니다. 첫째, I_PurchaseOrderItem과 I_PurReqnScheduleLine을 함께 다뤄 구매 요청(PR) → 구매 발주(PO) → 입고(GR) 전체 흐름을 하나의 분석 뷰로 묶어 보세요. 둘째, RAP의 Behavior Definition으로 납기일 변경 액션을 구현해 보면 트랜잭셔널 처리와 관리 시나리오를 함께 익힐 수 있습니다. 셋째, SAC(SAP Analytics Cloud)와 연결해 납기 지연 KPI 대시보드를 만들면 비즈니스 가치 측면에서 설득력 있는 산출물이 됩니다.
더 깊이 파고들 때 도움이 되는 자료
- SAP Help Portal — Procurement (S/4HANA on-Premise)
- SAP Help Portal — ABAP CDS Views Developer Guide
- SAP Help Portal — Virtual Data Model (VDM) in S/4HANA
- SAP API Business Hub — Purchase Order Process
- SAP Community — ABAP CDS Views
- SAP Blogs — RAP and CDS Tag Feed
댓글 0
아직 댓글이 없습니다.