개요 및 이 글에서 다루는 것
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으로 시작했습니다. 이 방식은 세 가지 근본적인 제약이 있었습니다.
- 필드 시맨틱 부재:
VBAP-KWMENG이 "주문 수량"이라는 의미는 개발자의 암묵적 지식에 의존했고, 단위(VRKME)와 짝을 이룬다는 사실도 별도 문서로만 존재했습니다. - 비즈니스 로직 중복: 상태 필드(
GBSTA,ABSTA)에서 "미완료 아이템"을 걸러내는 로직을 화면·리포트·인터페이스마다 다시 작성해야 했습니다. - 연관 테이블 조인 반복: 헤더(
VBAK), 파트너(VBPA), 스케줄 라인(VBEP), 딜리버리(LIPS)와의 조인을 매번 수동으로 작성했습니다.
SAP는 이 문제를 Virtual Data Model 계층으로 해결했습니다. VDM은 세 계층으로 나뉩니다.
비유: VDM을 "레고 부품 상자"에 비유하면, Basic View(I_*)는 낱개 블록, Composite View(C_*)는 조립된 미니 모델, Consumption View(Publish 이름)는 완성품 전시대에 해당합니다.
I_SalesOrderItem은 그중 낱개 블록으로, 다른 뷰에서 재사용됩니다.
I_SalesOrderItem은 VBAP을 기반으로 하되, 다음을 추가로 제공합니다.
- 의미 있는 필드명:
KWMENG→RequestedQuantity,NETWR→NetAmount - 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 SQLUP 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_SRVRemote 소비 - Custom CDS View 확장(
extend view entity) 및 Behavior Definition 확장
댓글 0
아직 댓글이 없습니다.