ABAP

VBAP 없이 — CDS 뷰 판매 아이템 조회 3줄로 #shorts #SAP #ABAP

▶ YouTube에서 보기

개요 및 이 글에서 다루는 것

S/4HANA 전환 이후 판매 오더 라인 아이템 조회의 표준 접근 방식은 더 이상 VBAP 직접 SELECT가 아닙니다. SAP는 Virtual Data Model(VDM) 계층에서 I_SalesOrderItem이라는 CDS 뷰를 제공하며, 이를 통해 필드 시맨틱·연관관계·권한체크가 표준화된 형태로 제공됩니다. 이 글에서는 VBAP의 한계에서 출발해 I_SalesOrderItem의 내부 구조, 실전 SELECT, OData 노출, 성능 튜닝까지 다룹니다.

  • VBAP와 I_SalesOrderItem의 차이점 이해
  • CDS 뷰의 연관관계(Association) 활용법 습득
  • ABAP SQL로 라인 아이템 필터링·조인 작성
  • OData/RAP 시나리오에서 CDS 뷰 재사용 패턴 파악
  • 운영 환경 성능 최적화 포인트 정리

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

ABAP Open SQL 기본 문법(SELECT/JOIN/WHERE)과 판매 오더의 헤더(VBAK)-아이템(VBAP) 구조에 대한 이해가 있으면 좋습니다. 또한 ABAP CDS 뷰의 기본 개념(DDIC 대비 관점, @AbapCatalog, @AccessControl 애노테이션 정도)을 알고 있으면 흐름을 따라오기 수월합니다. SAP GUI보다는 ADT(Eclipse ABAP Development Tools) 사용을 권장합니다.

환경 및 준비물

이 글의 코드는 다음 환경을 전제로 작성되었습니다.

  • SAP S/4HANA 2022 이상 (On-Premise) 또는 SAP S/4HANA Cloud, public/private edition
  • ABAP Platform 7.58 이상 (Release-dependent 필드 존재 여부는 릴리스 노트 확인 필요)
  • ADT(Eclipse ABAP Development Tools) 3.34 이상
  • 테스트용 판매 오더 데이터: 트랜잭션 VA01 또는 API_SALES_ORDER_SRV_V2 로 생성한 오더
  • 권한: 뷰 실행을 위한 S_RS_AUTH, 판매 조직/문서 유형에 대한 V_VBAK_VKO 권한 오브젝트

ADT의 CDS Navigator에서 I_SalesOrderItem을 열어 필드 목록과 Association을 미리 확인하면 이후 실습이 빠릅니다. Private cloud/On-Premise 환경에서는 SE11에서 뷰의 SQL View 이름(ISALESORDERITEM)으로도 참조 가능합니다.

핵심 개념: VBAP의 한계와 CDS VDM의 등장

클래식 ABAP에서 라인 아이템 조회는 SELECT * FROM vbap으로 시작했습니다. 이 방식은 세 가지 근본적인 제약이 있었습니다.

  1. 필드 시맨틱 부재: VBAP-KWMENG이 "주문 수량"이라는 의미는 개발자의 암묵적 지식에 의존했고, 단위(VRKME)와 짝을 이룬다는 사실도 별도 문서로만 존재했습니다.
  2. 비즈니스 로직 중복: 상태 필드(GBSTA, ABSTA)에서 "미완료 아이템"을 걸러내는 로직을 화면·리포트·인터페이스마다 다시 작성해야 했습니다.
  3. 연관 테이블 조인 반복: 헤더(VBAK), 파트너(VBPA), 스케줄 라인(VBEP), 딜리버리(LIPS)와의 조인을 매번 수동으로 작성했습니다.

SAP는 이 문제를 Virtual Data Model 계층으로 해결했습니다. VDM은 세 계층으로 나뉩니다.

비유: VDM을 "레고 부품 상자"에 비유하면, Basic View(I_*)는 낱개 블록, Composite View(C_*)는 조립된 미니 모델, Consumption View(Publish 이름)는 완성품 전시대에 해당합니다. I_SalesOrderItem은 그중 낱개 블록으로, 다른 뷰에서 재사용됩니다.

I_SalesOrderItemVBAP을 기반으로 하되, 다음을 추가로 제공합니다.

  • 의미 있는 필드명: KWMENGRequestedQuantity, NETWRNetAmount
  • Amount/Quantity에 통화·단위 참조가 애노테이션으로 연결(@Semantics.amount.currencyCode)
  • Association 통한 관련 엔티티 접근: _SalesOrder, _Product, _SalesOrderScheduleLine
  • 데이터 프라이버시/권한 체크가 @AccessControl.authorizationCheck: #CHECK로 표준화

실전 코드 3단계

1단계 — 기본 SELECT 예제

가장 단순한 형태로 특정 판매 오더의 라인 아이템을 조회합니다. VBAP에서 하던 SELECT를 I_SalesOrderItem으로 옮기면 필드명이 자기 설명적으로 바뀝니다.

REPORT z_so_item_basic.

DATA: lt_items TYPE TABLE OF i_salesorderitem,
      ls_item  TYPE i_salesorderitem.

SELECT SalesOrder,
       SalesOrderItem,
       Material,
       RequestedQuantity,
       RequestedQuantityUnit,
       NetAmount,
       TransactionCurrency
  FROM i_salesorderitem
  WHERE SalesOrder = '0000123456'
  INTO CORRESPONDING FIELDS OF TABLE @lt_items.

LOOP AT lt_items INTO ls_item.
  WRITE: / ls_item-salesorderitem,
           ls_item-material,
           ls_item-requestedquantity,
           ls_item-requestedquantityunit,
           ls_item-netamount,
           ls_item-transactioncurrency.
ENDLOOP.

주목할 점은 @ 이스케이프 문법(신 Open SQL)과 필드명이 CamelCase라는 점입니다. 결과 셀에는 통화·단위가 자동으로 세트로 담겨 나오므로 별도의 조인이 필요 없습니다.

2단계 — 실무 시나리오: Association 조인, 상태 필터, 예외 처리

실무에서는 "특정 판매 조직의 미완료(open) 아이템을 헤더 정보와 함께 조회"하는 요건이 잦습니다. Association을 활용하면 JOIN 절 없이 경로 표현식으로 헤더 필드에 접근할 수 있습니다.

CLASS zcl_open_so_reader DEFINITION PUBLIC FINAL CREATE PUBLIC.
  PUBLIC SECTION.
    TYPES: BEGIN OF ty_open_item,
             sales_order      TYPE i_salesorderitem-salesorder,
             item             TYPE i_salesorderitem-salesorderitem,
             sold_to_party    TYPE i_salesorder-soldtoparty,
             material         TYPE i_salesorderitem-material,
             open_qty         TYPE i_salesorderitem-requestedquantity,
             uom              TYPE i_salesorderitem-requestedquantityunit,
             net_amount       TYPE i_salesorderitem-netamount,
             currency         TYPE i_salesorderitem-transactioncurrency,
           END OF ty_open_item.

    METHODS read_open_items
      IMPORTING iv_sales_org TYPE i_salesorder-salesorganization
      RETURNING VALUE(rt_items) TYPE STANDARD TABLE OF ty_open_item
      RAISING   cx_sy_open_sql_db.
ENDCLASS.

CLASS zcl_open_so_reader IMPLEMENTATION.
  METHOD read_open_items.
    TRY.
        SELECT itm~salesorder,
               itm~salesorderitem,
               itm~_salesorder-soldtoparty AS sold_to_party,
               itm~material,
               itm~requestedquantity      AS open_qty,
               itm~requestedquantityunit  AS uom,
               itm~netamount              AS net_amount,
               itm~transactioncurrency    AS currency
          FROM i_salesorderitem AS itm
          WHERE itm~_salesorder-salesorganization = @iv_sales_org
            AND itm~overalldeliverystatus         = 'A'
            AND itm~salesdocumentrejectionstatus  = 'A'
          ORDER BY itm~salesorder, itm~salesorderitem
          INTO TABLE @rt_items.

        IF sy-subrc <> 0.
          MESSAGE i001(zsd) WITH iv_sales_org.
        ENDIF.

      CATCH cx_sy_open_sql_db INTO DATA(lx_sql).
        cl_bali_log=>create_with_header( io_header = cl_bali_header_setter=>create(
          object    = 'ZSD'
          subobject = 'OPEN_ITEMS' ) ).
        RAISE EXCEPTION lx_sql.
    ENDTRY.
  ENDMETHOD.
ENDCLASS.

여기서 itm~_salesorder-soldtoparty는 Association 경로 표현식입니다. 뒤에서 I_SalesOrder로의 조인이 자동 생성됩니다. 상태 코드 'A'는 일반적으로 "미착수"를 의미하지만, 릴리스별로 값 집합이 다를 수 있으니 도메인 GBSTA를 확인하는 것이 안전합니다.

3단계 — 프로덕션: OData 노출, 성능, 권한

프로덕션에서는 CDS 뷰를 그대로 리포트에 노출하기보다 Projection View로 Consumption 계층을 분리하는 것이 권장됩니다. RAP(Restful ABAP Programming) 시나리오에서 read-only 노출 예제입니다.

@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Open Sales Order Items (Custom)'
@Metadata.allowExtensions: true
@Search.searchable: true
define root view entity ZC_OpenSalesOrderItem
  as projection on I_SalesOrderItem as Item
{
  @UI.lineItem: [{ position: 10 }]
  key SalesOrder,

  @UI.lineItem: [{ position: 20 }]
  key SalesOrderItem,

  @UI.lineItem: [{ position: 30 }]
  @ObjectModel.foreignKey.association: '_Product'
  Material,

  @Semantics.quantity.unitOfMeasure: 'RequestedQuantityUnit'
  @UI.lineItem: [{ position: 40 }]
  RequestedQuantity,
  RequestedQuantityUnit,

  @Semantics.amount.currencyCode: 'TransactionCurrency'
  @UI.lineItem: [{ position: 50 }]
  NetAmount,
  TransactionCurrency,

  _SalesOrder,
  _Product,
  _SalesOrderScheduleLine
}
where OverallDeliveryStatus       = 'A'
  and SalesDocumentRejectionStatus = 'A';

이 Projection View에 서비스 정의를 연결하면 Fiori Elements 리스트 리포트에서 즉시 사용 가능합니다. 성능 관점에서 다음을 지키는 것이 일반적입니다.

  • 필드는 실제 사용하는 것만 SELECT (와일드카드 * 지양)
  • WHERE 조건에 SalesOrganization, SalesOrder 같은 인덱스 필드 포함
  • 페이징: OData $top, $skip 또는 ABAP SQL UP TO n ROWS
  • Association은 필요한 경로만 참조 — 참조하지 않으면 조인이 생성되지 않음
  • DCL(Data Control Language)로 사용자별 판매 조직 필터링을 위임

흔한 실수와 트러블슈팅

Q1. VBAP에는 있는 필드가 I_SalesOrderItem에 없습니다.
Basic View는 표준 시맨틱 필드만 노출합니다. 커스텀 필드나 잘 안 쓰이는 필드는 확장(extend view entity)으로 추가하거나, 필요 시 확장 CDS를 만들어 사용하는 것이 권장됩니다. 직접 VBAP을 함께 SELECT하면 권한/DCL 우회 위험이 있습니다.

Q2. Association 경로 표현식이 조인으로 폭발합니다.
경로 표현식은 SELECT 리스트·WHERE·ORDER BY에 등장할 때마다 조인을 유발합니다. 같은 Association을 여러 번 참조해도 옵티마이저가 항상 병합하지는 않으므로, 필요한 필드를 명시적으로 뽑아 임시 테이블에 담는 방식이 안정적입니다.

Q3. 데이터가 화면과 다릅니다.
I_SalesOrderItem@AccessControl.authorizationCheck: #CHECK가 걸려 있어 DCL로 정의된 권한 필터가 자동 적용됩니다. 실행 사용자가 특정 판매 조직에 권한이 없으면 그 데이터는 결과에서 사라집니다. 배치/RFC 사용자로 실행할 때는 권한 프로파일을 확인해야 합니다.

Q4. 상태 필드 값 해석이 헷갈립니다.
OverallDeliveryStatus는 도메인 GBSTA를 사용하며 A(미착수), B(부분), C(완료)로 구성됩니다. 하드코딩보다 상수 클래스로 관리하는 것이 유지보수에 유리합니다.

응용 확장 방향

이 글의 I_SalesOrderItem을 익혔다면, 다음 주제로 확장해 볼 수 있습니다.

  • I_SalesOrder, I_SalesOrderScheduleLine, I_SalesOrderPartner 등 인접 Basic View 학습
  • Analytical View C_SalesOrderItemCube로 집계·차원 분석 구성
  • RAP Managed BO로 판매 오더 생성/변경 시나리오 구현
  • ABAP Environment(BTP)에서 API_SALES_ORDER_SRV Remote 소비
  • Custom CDS View 확장(extend view entity) 및 Behavior Definition 확장

댓글 0

아직 댓글이 없습니다.