ABAP

판매 오더 아이템 조회 실수 3가지 — VBAP vs CDS #shorts #SAP #ABAP

▶ YouTube에서 보기

개요 및 이 글에서 다룰 내용

S/4HANA 환경에서 판매 오더 아이템 데이터를 다루는 일은 매우 빈번합니다. 전통적으로는 VBAP 테이블을 직접 SELECT 했지만, S/4HANA에서는 가상 데이터 모델(VDM)인I_SalesOrderItem CDS 뷰를 통해 동일한 데이터에 의미론적으로 접근하는 것을 권장합니다. 이 글에서는 I_SalesOrderItem의 구조와 VBAP와의 관계, 그리고 실무에서 자재(Material)와 수량(Quantity) 정보를 읽는 방법을 단계적으로 다룹니다.

  • I_SalesOrderItem과 VBAP의 매핑 관계 이해
  • 필드 의미와 연관 뷰(Association) 활용
  • ABAP 프로그램에서 CDS 뷰를 SELECT 하는 패턴
  • 집계/필터링/조인 실무 시나리오 적용
  • OData/RAP 연계 시 고려 사항 파악

읽기 전에 알아두면 좋은 배경

본문을 따라가려면 ABAP Open SQL 기본 문법과 SD 모듈의 판매 오더 구조(헤더 VBAK, 아이템 VBAP)에 대한 개념적 이해가 필요합니다. 또한 CDS View Entity 또는 DDIC 기반 CDS View의 차이, Association(_AssocName) 표기법, 그리고 어노테이션(@) 기본 사용법을 알고 있으면 흐름을 따라가기 수월합니다. ADT(ABAP Development Tools in Eclipse) 환경에서 CDS 뷰 정의를 직접 열어볼 수 있어야 실습이 가능합니다.

버전과 사용 환경

이 글의 예제는 다음 환경을 기준으로 작성되었습니다.

  • SAP S/4HANA 2022 이상 (On-Premise 또는 Private Cloud Edition)
  • ABAP Platform 2022 (NetWeaver 7.57 이상)
  • ADT 3.40 이상 (Eclipse 2023-03 이상 권장)
  • I_SalesOrderItem 릴리스 상태: C1 (Cloud Development) 일반적으로 공개
  • VDM 계층: Basic Interface View (I_ Prefix)

SAP S/4HANA Cloud Public Edition의 경우 일부 필드와 Association이 다를 수 있으므로 ADT의 Element Info(F2) 또는 SAP API Business Hub를 통해 릴리스된 필드를 확인하는 것이 일반적으로 안전합니다. On-Premise 환경에서도 SP 레벨에 따라 일부 필드 가용성이 달라질 수 있습니다.

I_SalesOrderItem이 VBAP를 추상화하는 방식

VBAP는 약 350개 이상의 컬럼을 가진 거대한 클러스터 형태의 트랜잭션 테이블입니다. 필드 이름은 독일어 약어(POSNR, MATNR, KWMENG 등)로 되어 있어 의미를 파악하기 어렵고, 통화/수량 단위 처리, 클라이언트 처리, 삭제된 아이템 필터 등 비즈니스 로직이 별도로 필요했습니다.

I_SalesOrderItem은 이러한 VBAP를 "비즈니스 의미가 명확한 한 장의 뷰"로 감싼 일종의 의미 계층 어댑터입니다. 비유하자면, VBAP가 부품 창고의 원자재 박스라면 I_SalesOrderItem은 부품에 라벨을 붙이고 단위까지 환산해 진열대에 올려놓은 상태에 해당합니다. 주요 매핑은 다음과 같습니다.

I_SalesOrderItem 필드VBAP 원본의미
SalesOrderVBELN판매 오더 번호
SalesOrderItemPOSNR아이템 번호
MaterialMATNR자재 번호
RequestedQuantityKWMENG요청 수량
RequestedQuantityUnitVRKME판매 단위
NetAmountNETWR아이템 순 금액
TransactionCurrencyWAERK거래 통화
PlantWERKS플랜트

또한 _SalesOrder, _Material, _Plant, _ScheduleLine, _PricingElement 같은 Association이 미리 정의되어 있어, 헤더 데이터나 자재 마스터로 조인이 한 줄로 표현됩니다. 클라이언트 필터링과 삭제 플래그 처리도 내부적으로 일관되게 적용되므로, 일반적으로 비즈니스 로직 개발자가 신경 쓸 부분이 줄어드는 구조입니다.

VDM 계층에서 I_로 시작하는 뷰는 Interface View로, 직접 UI에 노출하기보다는 Consumption View(C_) 또는 Composite View의 빌딩 블록으로 사용하는 것이 권장됩니다.

실전 예제 1단계 — 기본 SELECT로 아이템 읽기

가장 단순한 시나리오부터 시작합니다. 특정 판매 오더 번호로 모든 아이템과 자재, 수량을 가져오는 예제입니다.

REPORT z_demo_so_item_basic.

PARAMETERS: p_vbeln TYPE vbeln_va OBLIGATORY.

DATA: lt_items TYPE TABLE OF i_salesorderitem.

SELECT salesorder,
       salesorderitem,
       material,
       requestedquantity,
       requestedquantityunit,
       netamount,
       transactioncurrency,
       plant
  FROM i_salesorderitem
  WHERE salesorder = @p_vbeln
  INTO TABLE @lt_items.

IF sy-subrc = 0.
  LOOP AT lt_items INTO DATA(ls_item).
    WRITE: / ls_item-salesorderitem,
             ls_item-material,
             ls_item-requestedquantity,
             ls_item-requestedquantityunit,
             ls_item-netamount,
             ls_item-transactioncurrency.
  ENDLOOP.
ELSE.
  WRITE: / '해당 판매 오더에 대한 아이템이 없습니다.'.
ENDIF.

주목할 부분은 FROM i_salesorderitem으로 VBAP가 아니라 CDS 뷰를 직접 사용한다는 점, 그리고 필드명이 비즈니스 친화적으로 표현된다는 점입니다. 클라이언트 처리(MANDT) 역시 명시적으로 작성할 필요가 없습니다.

실전 예제 2단계 — 자재별 미결 수량 집계와 로깅

실무에서 자주 등장하는 시나리오는 "특정 플랜트의 특정 자재가 오늘 기준으로 얼마나 미결(Open)로 남아있는가"입니다. 이 단계에서는 자재 기준으로 그룹핑하고, 단위/통화별로 합계를 내며, 처리 중 오류를 애플리케이션 로그로 남기는 패턴을 보여줍니다.

CLASS zcl_open_so_item_agg DEFINITION PUBLIC FINAL CREATE PUBLIC.
  PUBLIC SECTION.
    TYPES: BEGIN OF ty_agg,
             plant      TYPE werks_d,
             material   TYPE matnr,
             unit       TYPE vrkme,
             open_qty   TYPE kwmeng,
             net_amount TYPE netwr,
             currency   TYPE waerk,
           END OF ty_agg,
           tt_agg TYPE STANDARD TABLE OF ty_agg WITH EMPTY KEY.

    METHODS aggregate
      IMPORTING iv_plant      TYPE werks_d
                iv_material   TYPE matnr OPTIONAL
      RETURNING VALUE(rt_agg) TYPE tt_agg
      RAISING   cx_sy_open_sql_db.
ENDCLASS.

CLASS zcl_open_so_item_agg IMPLEMENTATION.
  METHOD aggregate.
    TRY.
        SELECT plant,
               material,
               requestedquantityunit AS unit,
               SUM( requestedquantity ) AS open_qty,
               SUM( netamount )         AS net_amount,
               transactioncurrency      AS currency
          FROM i_salesorderitem
         WHERE plant = @iv_plant
           AND ( @iv_material IS INITIAL OR material = @iv_material )
           AND salesorderitemdeliverystatus <> 'C'
         GROUP BY plant, material, requestedquantityunit, transactioncurrency
         ORDER BY material
          INTO TABLE @rt_agg.

      CATCH cx_sy_open_sql_db INTO DATA(lx_db).
        MESSAGE lx_db TYPE 'I'.
        RAISE EXCEPTION lx_db.
    ENDTRY.
  ENDMETHOD.
ENDCLASS.

여기서 핵심은 세 가지입니다. 첫째, SalesOrderItemDeliveryStatus처럼 VBAP 원본에서는 여러 필드 조합으로 판별해야 했던 상태값이 CDS 뷰에서 의미 있는 단일 필드로 노출됩니다. 둘째, SUMGROUP BY를 CDS 뷰 위에서 그대로 사용할 수 있어 HANA로 푸시다운됩니다. 셋째, 예외 처리와 로깅을 분리해 호출자가 적절히 처리할 수 있도록 설계합니다.

실전 예제 3단계 — Association 활용과 RAP/OData 연계

운영 환경에서는 헤더의 판매 조직과 자재 마스터의 자재 그룹까지 함께 조회해야 하는 경우가 많습니다. I_SalesOrderItem의 Association을 활용하면 다음과 같이 간결하게 작성할 수 있습니다.

SELECT item~salesorder,
       item~salesorderitem,
       item~material,
       item~_material-materialgroup AS material_group,
       item~requestedquantity,
       item~requestedquantityunit,
       item~netamount,
       item~transactioncurrency,
       item~_salesorder-salesorganization AS sales_org,
       item~_salesorder-distributionchannel AS dist_channel
  FROM i_salesorderitem AS item
  WHERE item~_salesorder-soldtoparty = @iv_customer
    AND item~creationdate >= @iv_from_date
  ORDER BY item~salesorder, item~salesorderitem
  INTO TABLE @DATA(lt_enriched)
  UP TO 5000 ROWS.

OData 서비스로 노출할 때는 I_SalesOrderItem을 직접 외부에 공개하기보다, 다음과 같이 Projection View로 한 번 감싸 노출 필드를 통제하는 것이 일반적입니다.

@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Sales Order Item Projection for Reporting'
define root view entity ZC_SalesOrderItemReport
  provider contract transactional_query
  as projection on I_SalesOrderItem
{
  key SalesOrder,
  key SalesOrderItem,
      Material,
      _Material.MaterialGroup as MaterialGroup,
      Plant,
      RequestedQuantity,
      RequestedQuantityUnit,
      NetAmount,
      TransactionCurrency,
      _SalesOrder.SoldToParty   as SoldToParty,
      _SalesOrder.SalesOrganization as SalesOrganization
}

자주 만나는 함정과 해결 방법

Q1. 데이터가 분명 있는데 SELECT 결과가 비어 있습니다. 가장 흔한 원인은 삭제된 아이템이나 인도 완료 상태 필터입니다. I_SalesOrderItem은 일부 뷰에서 거절된 아이템을 그대로 보여주므로 SalesDocumentRjcnReason 또는 SalesOrderItemDeliveryStatus 같은 상태 필드를 명시적으로 검토해야 합니다.

Q2. RequestedQuantity가 음수로 나오거나 단위가 예상과 다릅니다. 반품/취소 오더의 경우 부호가 반대이거나 영업 단위(VRKME)와 기본 단위(MEINS)가 다를 수 있습니다. 단위 환산이 필요하다면 BaseUnit 필드와 I_ProductUnitsOfMeasure 뷰를 함께 활용하는 것이 권장됩니다.

Q3. 성능이 VBAP 직접 SELECT보다 떨어집니다. Association을 무분별하게 펼치면 다중 LEFT JOIN이 생성됩니다. 실제 필요한 필드만 SELECT 목록에 포함하고, WHERE 절을 가능한 한 베이스 필드(SalesOrder, Material, Plant, CreationDate)에 걸어 인덱스가 활용되도록 작성합니다. ST05 트레이스로 푸시다운된 SQL을 검토하는 것이 일반적으로 효과적입니다.

Q4. 권한 체크가 너무 강합니다. Interface View에는 @AccessControl.authorizationCheck: #NOT_REQUIRED 또는 #CHECK가 걸려 있을 수 있습니다. 백엔드 배치에서 사용할 때는 적절한 권한 객체(V_VBAK_VKO 등)를 가진 시스템 사용자 컨텍스트에서 실행해야 합니다.

실무에서 확장할 수 있는 주제

판매 오더 아이템을 다뤘다면 다음 주제로 자연스럽게 확장할 수 있습니다.

  • I_SalesOrder 헤더 뷰와의 결합, 그리고 I_SalesOrderScheduleLine을 통한 인도 일정 분석
  • I_SalesOrderItemPricingElement로 컨디션(가격, 할인) 상세 분해
  • RAP(BO) 기반 판매 오더 생성/변경 시나리오에서 Behavior Definition 작성
  • CDS Analytical Query를 통한 매출 KPI 대시보드 구성
  • Embedded Steampunk 환경에서 Released API 기반의 클린 코어 개발

댓글 0

아직 댓글이 없습니다.