ABAP

아직도 LIKP 직접 조회? 배송 상태 추적 3가지 #shorts #SAP #ABAP

개요와 이번 글에서 다루는 범위

SAP S/4HANA의 배송(Delivery) 영역은 SD(Sales & Distribution)와 MM(Materials Management)의 교차점에 위치하며, 주문에서 출고까지의 흐름을 추적하는 핵심 트랜잭션 데이터입니다. 전통적으로 LIKP(배송 헤더) / LIPS(배송 아이템) 테이블에 직접 접근해 ABAP Open SQL로 조회했지만, S/4HANA에서는 가상 데이터 모델(VDM, Virtual Data Model)인 I_DeliveryDocument CDS 뷰를 통해 표준화된 인터페이스로 접근하는 것이 권장됩니다.

이 글에서 확인할 항목은 다음과 같습니다.

  • I_DeliveryDocument 뷰의 구조와 LIKP와의 매핑 관계 파악
  • 출고(Goods Issue) 상태 필드(GoodsMovementStatus)의 의미와 추적 패턴
  • 실무에서 자주 쓰이는 헤더-아이템 조인 패턴(I_DeliveryDocumentItem 연계)
  • 성능 고려 사항과 권한 체크(AccessControl) 적용 방식
  • RAP/Fiori 앱과의 연동 시 주의점

이 글을 읽기 전에 알아두면 좋은 내용

본 내용은 ABAP CDS View(DDL) 작성 경험이 있는 개발자를 대상으로 합니다. @AbapCatalog.sqlViewName, association, $projection 같은 기본 어노테이션과 SD 모듈의 표준 흐름(VA01 판매오더 → VL01N 납품 생성 → VL02N 출고전기) 정도는 익숙해야 합니다. 또한 LIKP(헤더)와 LIPS(아이템) 테이블 구조를 한 번이라도 살펴본 경험이 있다면 매핑 이해가 훨씬 빠릅니다.

실습 환경 및 사전 준비

아래 예제는 다음 환경을 기준으로 작성했습니다. 환경에 따라 일부 필드가 다를 수 있으므로 ADT(Eclipse ABAP Development Tools)에서 실제 메타데이터를 확인하는 절차를 함께 권장합니다.

  • SAP S/4HANA 2022 FPS02 이상 (On-Premise 기준)
  • ABAP Platform 7.58 / ABAP Cloud Development Model 지원
  • ADT(Eclipse) 3.40 이상, NetWeaver 백엔드 연결 필수
  • 권한: S_DEVELOP(개발), S_RFC(테스트), 그리고 배송 데이터 조회용 V_LIKP_VST 등 SD 권한 객체
  • 조회 도구: ADT의 Data Preview, 또는 트랜잭션 SE16N으로 LIKP 비교용 데이터 확보

리포지토리에서 I_DeliveryDocument를 직접 열어보려면 ADT에서 "Open ABAP Development Object" (Ctrl+Shift+A)에 뷰 이름을 입력하면 됩니다. 이 뷰는 SAP가 제공하는 Released 객체(API State: Released)로 분류되어 있어 커스텀 확장(Extension Include) 또는 컨슈밍 뷰의 베이스로 사용할 수 있습니다.

핵심 개념: VDM 계층과 배송 문서 모델

SAP의 VDM은 데이터베이스 테이블을 사용자가 직접 접근하지 않도록 캡슐화한 계층입니다. 비유하자면 LIKP는 "원자재 창고"이고 I_DeliveryDocument는 "포장된 제품 진열대"입니다. 진열대는 원자재의 위치를 알지만, 소비자(애플리케이션)는 진열대만 보면 됩니다.

VDM에는 세 가지 계층이 있습니다.

  1. Basic View (I_*): 테이블을 1:1에 가깝게 매핑하면서 의미 있는 필드 이름과 어소시에이션을 부여. I_DeliveryDocument가 여기에 해당합니다.
  2. Composite View (C_*): Basic View들을 조합해 분석/리포팅에 적합하게 가공.
  3. Consumption View (C_*, X_*): Fiori 앱이나 OData 서비스에서 직접 노출되는 최종 뷰.

I_DeliveryDocument는 LIKP를 기반으로 하지만 단순한 SELECT가 아닙니다. 다음과 같은 변환이 자동으로 처리됩니다.

  • VBELN → DeliveryDocument (의미론적 이름)
  • LFART → DeliveryDocumentType
  • WADAT_IST → ActualGoodsMovementDate
  • WBSTK → OverallGoodsMovementStatus (A=미처리, B=부분처리, C=완료)
  • KOSTK → OverallPickingStatus / OverallPickingConfStatus
  • 통화/단위 필드는 @Semantics.amount.currencyCode로 메타데이터 부여

특히 출고 상태 추적은 GoodsMovementStatus 필드 하나가 아니라 여러 상태 필드의 조합으로 판단합니다. 예를 들어 "포장은 끝났지만 출고전기는 안 된 납품"을 찾으려면 OverallPackingStatus = 'C' AND OverallGoodsMovementStatus <> 'C' 조합을 사용합니다.

참고: I_DeliveryDocument는 Inbound Delivery(입고 납품)도 포함합니다. 발송 방향만 구분하려면 DeliveryDocumentCategory(LIKP-VBTYP) = 'J'(Outbound)로 필터링해야 합니다.

실전 코드 1단계: 기본 조회 예제

먼저 I_DeliveryDocument에서 특정 기간의 발송 납품을 조회하는 가장 단순한 형태입니다. ABAP Open SQL에서 CDS View는 일반 테이블과 동일하게 SELECT 가능합니다.

REPORT zr_delivery_basic_query.

DATA: lt_delivery TYPE STANDARD TABLE OF i_deliverydocument.

SELECT DeliveryDocument,
       DeliveryDocumentType,
       ShippingPoint,
       ActualGoodsMovementDate,
       OverallGoodsMovementStatus,
       OverallPickingStatus,
       TotalWeight,
       WeightUnit
  FROM i_deliverydocument
  WHERE DeliveryDocumentCategory = 'J'         "Outbound Delivery
    AND ActualGoodsMovementDate >= @( cl_abap_context_info=>get_system_date( ) - 30 )
  INTO TABLE @lt_delivery
  UP TO 100 ROWS.

LOOP AT lt_delivery ASSIGNING FIELD-SYMBOL(<ls_dlv>).
  WRITE: / <ls_dlv>-deliverydocument,
           <ls_dlv>-shippingpoint,
           <ls_dlv>-overallgoodsmovementstatus,
           <ls_dlv>-totalweight,
           <ls_dlv>-weightunit.
ENDLOOP.

여기서 핵심 포인트는 호스트 변수에 인라인 표현식(@( ... ))을 사용했다는 점과, UP TO 100 ROWS로 첫 실행 시 데이터 폭증을 막았다는 점입니다. LIKP를 직접 조회할 때보다 필드명이 길어지지만, 의미가 명확해 유지보수 비용이 줄어듭니다.

실전 코드 2단계: 출고 상태별 집계와 예외 처리

실무에서는 "출고 지연 납품 리포트"가 자주 요구됩니다. 계획된 출고일(PlannedGoodsIssueDate)을 지났는데도 OverallGoodsMovementStatus가 'C'가 아닌 건들을 헤더-아이템 단위로 추출하는 시나리오를 작성해 봅니다.

CLASS zcl_delivery_lag_report DEFINITION PUBLIC FINAL CREATE PUBLIC.
  PUBLIC SECTION.
    TYPES: BEGIN OF ty_lag,
             delivery_doc       TYPE i_deliverydocument-deliverydocument,
             shipping_point     TYPE i_deliverydocument-shippingpoint,
             planned_gi_date    TYPE i_deliverydocument-plannedgoodsissuedate,
             gm_status          TYPE i_deliverydocument-overallgoodsmovementstatus,
             lag_days           TYPE i,
             item_count         TYPE i,
           END OF ty_lag.

    METHODS run RETURNING VALUE(rt_result) TYPE STANDARD TABLE OF ty_lag.
ENDCLASS.

CLASS zcl_delivery_lag_report IMPLEMENTATION.
  METHOD run.
    DATA(lv_today) = cl_abap_context_info=>get_system_date( ).

    TRY.
        SELECT hdr~deliverydocument,
               hdr~shippingpoint,
               hdr~plannedgoodsissuedate,
               hdr~overallgoodsmovementstatus AS gm_status,
               CAST( ( @lv_today - hdr~plannedgoodsissuedate ) AS abap.int4 ) AS lag_days,
               COUNT( itm~deliverydocumentitem ) AS item_count
          FROM i_deliverydocument AS hdr
          INNER JOIN i_deliverydocumentitem AS itm
            ON itm~deliverydocument = hdr~deliverydocument
         WHERE hdr~deliverydocumentcategory  = 'J'
           AND hdr~overallgoodsmovementstatus <> 'C'
           AND hdr~plannedgoodsissuedate     < @lv_today
         GROUP BY hdr~deliverydocument,
                  hdr~shippingpoint,
                  hdr~plannedgoodsissuedate,
                  hdr~overallgoodsmovementstatus
         ORDER BY lag_days DESCENDING
         INTO CORRESPONDING FIELDS OF TABLE @rt_result
         UP TO 500 ROWS.

      CATCH cx_sy_open_sql_db INTO DATA(lx_sql).
        cl_demo_output=>display( |Open SQL 오류: { lx_sql->get_text( ) }| ).
    ENDTRY.

    IF rt_result IS INITIAL.
      MESSAGE 'No delayed deliveries found' TYPE 'S'.
    ENDIF.
  ENDMETHOD.
ENDCLASS.

이 예제에서 주목할 점은 세 가지입니다. 첫째, I_DeliveryDocumentItem과의 INNER JOIN으로 헤더당 아이템 수를 함께 집계했습니다. LIKP/LIPS를 직접 조인할 수도 있지만, CDS 뷰는 권한 체크가 묵시적으로 적용되기 때문에 비즈니스 규칙 준수 측면에서 안전합니다. 둘째, 날짜 차이를 SQL 레벨에서 계산해 ABAP 측 LOOP 비용을 줄였습니다. 셋째, cx_sy_open_sql_db로 예외를 잡아 운영 환경에서 통신 오류나 권한 변경 시 로깅이 가능하도록 했습니다.

실전 코드 3단계: 프로덕션용 컨슈밍 CDS 뷰와 RAP 노출

한 단계 더 나아가, 위 리포트를 Fiori Elements 앱에서 사용할 수 있도록 컨슈밍 CDS 뷰로 캡슐화하고 권한/메타데이터를 부여하는 예제입니다.

@AbapCatalog.sqlViewName: 'ZDLVLAGV'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Delayed Outbound Deliveries'
@VDM.viewType: #CONSUMPTION
@Analytics.dataCategory: #FACT
@ObjectModel.usageType.serviceQuality: #D
@ObjectModel.usageType.sizeCategory: #L
@ObjectModel.usageType.dataClass: #TRANSACTIONAL

define view entity ZC_DeliveryShipmentLag
  as select from I_DeliveryDocument as Hdr
  association [0..*] to I_DeliveryDocumentItem as _Item
    on  $projection.DeliveryDocument = _Item.DeliveryDocument
{
  key Hdr.DeliveryDocument,
      Hdr.ShippingPoint,
      Hdr.DeliveryDocumentType,
      Hdr.PlannedGoodsIssueDate,
      Hdr.ActualGoodsMovementDate,

      @Semantics.text: true
      Hdr._DeliveryDocumentType._Text[1: Language = $session.system_language].DeliveryDocumentTypeName
        as DeliveryTypeText,

      Hdr.OverallGoodsMovementStatus,
      Hdr.OverallPickingStatus,
      Hdr.OverallPackingStatus,

      cast( dats_days_between(
              Hdr.PlannedGoodsIssueDate,
              $session.system_date
            ) as abap.int4 ) as LagDays,

      @Semantics.quantity.unitOfMeasure: 'WeightUnit'
      Hdr.TotalWeight,
      Hdr.WeightUnit,

      _Item
}
where Hdr.DeliveryDocumentCategory   = 'J'
  and Hdr.OverallGoodsMovementStatus <> 'C';

위 컨슈밍 뷰에서 핵심은 다음과 같습니다.

  • @AccessControl.authorizationCheck: #CHECK — DCL(Data Control Language)을 통한 행 수준 권한 적용. V_LIKP_VST 같은 권한 객체와 연동됩니다.
  • @Semantics.quantity.unitOfMeasure — Fiori에서 무게와 단위를 함께 표시할 수 있도록 메타데이터 부여.
  • dats_days_between — 시스템 날짜와의 차이를 SQL 함수로 계산해 HANA 푸시다운 활용.
  • Text association(_Text[1: Language = ...]) — 언어별 텍스트를 자동으로 가져옴.

여기에 RAP Service Definition과 Service Binding을 추가하면 OData V4 서비스로 노출되어 Fiori Elements List Report 템플릿에서 바로 사용할 수 있습니다. 또한 단위 테스트를 위해 ABAP Unit Framework의 CDS Test Double Framework(cl_cds_test_environment)를 사용하면 실제 LIKP 데이터 없이도 시나리오 검증이 가능합니다.

현장에서 자주 마주치는 함정과 해결책

Q1. I_DeliveryDocument에 PO 번호(EBELN)가 안 보입니다.
구매 오더 참조는 헤더가 아니라 아이템 레벨에 있습니다. I_DeliveryDocumentItemPurchaseOrder, PurchaseOrderItem 필드를 조인해야 합니다. LIKP 헤더에 PO를 넣는 커스텀 확장이 있다면 별도 Extension View가 필요합니다.

Q2. OverallGoodsMovementStatus = 'C'인데도 LIKP-WBSTK가 빈 값입니다.
WBSTK는 출고 전기 후 업데이트되는 필드입니다. 결제/회계 전기와의 타이밍 차이로 잠시 비어 있을 수 있습니다. 실제 출고 완료 여부는 ActualGoodsMovementDate가 채워졌는지, 그리고 MSEG(자재 문서)에 해당 납품 참조가 있는지로 이중 확인하는 것이 안전합니다.

Q3. 데이터가 너무 많아 ADT Data Preview가 타임아웃 됩니다.
I_DeliveryDocument는 전체 LIKP를 대상으로 하므로 수백만 건이 흔합니다. ADT Data Preview에서 필터(Ctrl+F8)로 ShippingPoint, CreationDate를 먼저 지정하거나, 커스텀 컨슈밍 뷰에서 @Search.searchable: true와 함께 검색 가능 필드를 지정하세요. 또한 ABAP Open SQL에서는 항상 PlannedGoodsIssueDate, ShippingPoint 등 인덱스 친화 필드를 WHERE에 포함하길 권장합니다.

그 밖에 흔한 실수로는 (1) DeliveryDocumentCategory를 필터링하지 않아 입고/출고를 섞어 조회하는 경우, (2) 통화/수량 필드를 단위 없이 표시해 Fiori UI에서 경고가 발생하는 경우, (3) $session.client를 무시하고 크로스 클라이언트 조회를 시도하는 경우가 있습니다. CDS는 기본적으로 client-dependent로 동작하므로 명시적으로 다룰 일이 거의 없지만, AMDP에서 호출할 때는 클라이언트 처리 로직을 직접 작성해야 합니다.

이후 살펴보면 좋은 관련 주제

I_DeliveryDocument에 익숙해졌다면 다음 주제로 확장하는 것을 권장합니다.

  • I_DeliveryDocumentItem: 아이템 단위 수량/배치/저장위치 분석
  • I_BillingDocument: 배송 → 청구 연계(VF01과의 흐름)
  • I_OutboundDeliveryOrder (TM 모듈): Transportation Management 연계 시 사용
  • RAP Managed Scenario: 컨슈밍 뷰를 트랜잭션 가능 비즈니스 객체로 확장
  • CDS Test Double Framework: 배송 시나리오를 단위 테스트로 검증
  • Embedded Analytics: I_DeliveryDocument 위에 KPI/Query 뷰를 얹어 Fiori Analytical List Page 구축

추가로 확인할 수 있는 자료

  • SAP Help Portal — S/4HANA On-Premise Documentation
  • SAP Help Portal — Outbound Delivery Process
  • SAP Help Portal — ABAP CDS Development User Guide
  • SAP API Business Hub — I_DeliveryDocument 메타데이터 검색
  • SAP Community — ABAP CDS 및 RAP 토론
  • SAP Blogs — Virtual Data Model 관련 게시글

마지막으로, Released API 상태는 SAP 릴리스에 따라 변경될 수 있습니다. 운영 환경 도입 전 트랜잭션 SRTUTIL 또는 ADT의 "Released APIs" 뷰에서 현재 시스템의 API State를 확인하는 절차를 함께 권장합니다.

댓글 0

아직 댓글이 없습니다.