개요 및 이 글에서 다루는 내용
SAP S/4HANA의 SD(Sales & Distribution) 영역에서 판매 오더를 다루다 보면 가장 먼저 마주치게 되는 테이블이 VBAK(판매 오더 헤더)입니다. 그러나 S/4HANA 환경에서는 더 이상 VBAK를 직접 SELECT 하기보다 I_SalesOrder라는 CDS Interface View를 통해 접근하는 패턴이 권장됩니다. 이 글은 VBAK 직접 조회의 한계를 짚고, I_SalesOrder의 필드 매핑·association 구조를 분석한 뒤, 실무에서 자주 발생하는 "고객별 미결 오더 집계", "가격 조건(Pricing Element) 분석"까지 단계적으로 풀어냅니다.
- VBAK ↔ I_SalesOrder 필드 매핑과 추상화 의도 이해
- I_Customer, I_PricingElement와의 association 활용
- 미결 오더(open order) 판별 로직과 집계 패턴
- 대용량 트랜잭션 환경에서의 성능·권한·테스트 고려사항
읽기 전 알아두면 좋은 배경
이 글은 ABAP Open SQL과 기본 CDS 구문(define view, association, annotation)을 한 번이라도 작성해 본 개발자를 가정합니다. SD 모듈의 판매 오더 라이프사이클(생성 → 출하 → 청구), VBAK/VBAP/VBKD/KONV 같은 핵심 테이블 명칭, ABAP의 SELECT ... FOR ALL ENTRIES 또는 WITH 절 사용 경험이 있으면 이해가 빠릅니다.
환경, 릴리스, 준비물
I_SalesOrder는 S/4HANA 표준 Virtual Data Model(VDM)에 포함된 Interface View로, 일반적으로 S/4HANA 1709 이후 릴리스부터 사용 가능하며 릴리스 업그레이드에 따라 필드와 association이 점진적으로 확장되어 왔습니다. 본문 예제는 다음 환경을 기준으로 합니다.
- SAP S/4HANA 2022(또는 그 이후) on-premise / Private Cloud Edition
- ABAP Platform 2022, ADT 3.32 이상 (Eclipse 2023-09 권장)
- DB: SAP HANA 2.0 SPS06 이상
- 권한:
S_RFC,S_DEVELOP(개발자), 데이터 접근을 위한V_VBAK_VKO(판매 조직 권한) 등
Cloud(Public Edition) 환경에서는 ABAP Cloud 모델로 인해 직접 VBAK SELECT는 차단되며, I_SalesOrder(또는 C1/C2 등급의 Released API)만 사용 가능하다는 점을 유의해야 합니다.
핵심 개념: VBAK에서 I_SalesOrder로
VBAK는 1990년대 R/3 시절부터 사용된 물리 테이블입니다. 컬럼명이 VBELN(오더 번호), KUNNR(고객), VKORG(판매 조직), NETWR(순액)처럼 4~6자리 독일어 약어로 되어 있어 가독성이 낮습니다. 또한 통화 단위(WAERK)와 금액(NETWR)의 연결, 삭제 플래그(LOEKZ) 등 보일러플레이트 로직이 매번 반복됩니다.
I_SalesOrder는 이를 다음과 같은 의미 기반 필드명으로 추상화합니다.
| VBAK 필드 | I_SalesOrder 필드 | 의미 |
| VBELN | SalesOrder | 판매 오더 번호 |
| AUART | SalesOrderType | 오더 유형 |
| KUNNR | SoldToParty | 판매처 고객 |
| VKORG | SalesOrganization | 판매 조직 |
| NETWR | TotalNetAmount | 순액 총계 |
| WAERK | TransactionCurrency | 거래 통화 |
| ERDAT | CreationDate | 생성일 |
여기에 더해 I_SalesOrder는 _SoldToParty(→ I_Customer), _SalesOrderItem(→ I_SalesOrderItem), _PricingElement(→ I_PricingElement, KONV 기반) 같은 association을 노출합니다.
일반적으로 VBAK 직접 조회는 (1) 필드 의미 파악 비용, (2) 권한 체크 누락, (3) Cloud 호환성 부재, (4) 향후 필드 확장 시 코드 깨짐 등의 리스크가 있어 신규 개발에서는 지양하는 것이 권장됩니다.
실전 예제 1단계: 기본 SELECT와 association 따라가기
가장 단순한 형태로, 특정 판매 조직에 속한 최근 30일간의 오더 헤더와 판매처 고객 이름을 한 번에 가져오는 예제입니다.
REPORT zr_so_basic_lookup.
SELECT FROM I_SalesOrder AS so
FIELDS so~SalesOrder,
so~SalesOrderType,
so~SoldToParty,
so~_SoldToParty-CustomerName AS sold_to_name,
so~TotalNetAmount,
so~TransactionCurrency,
so~CreationDate
WHERE so~SalesOrganization = @'1010'
AND so~CreationDate >= @( cl_abap_context_info=>get_system_date( ) - 30 )
ORDER BY so~CreationDate DESCENDING
INTO TABLE @DATA(lt_orders).
cl_demo_output=>display( lt_orders ).
여기서 주목할 부분은 so~_SoldToParty-CustomerName입니다. 이는 I_Customer로 가는 association을 자동 JOIN으로 변환합니다. 동일한 결과를 VBAK + KNA1 조합으로 작성하면 약 두 배 길이의 코드가 되며, 클라이언트 필드, 삭제 플래그, 권한 체크를 모두 직접 다뤄야 합니다.
실전 예제 2단계: 고객별 미결 오더 집계와 예외 처리
실무에서 가장 빈번한 시나리오 중 하나는 "특정 고객이 아직 출하/청구되지 않은 오더가 얼마나 있는가?"를 빠르게 보여주는 것입니다. 헤더만으로는 미결 여부를 알 수 없고, 아이템 레벨의 전체 처리 상태(OverallSDProcessStatus)를 확인해야 합니다.
@AbapCatalog.viewEnhancementCategory: [#NONE]
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Open Sales Orders by Customer'
define view entity ZC_OpenOrdersByCustomer
as select from I_SalesOrder as so
association [0..1] to I_Customer as _Customer
on $projection.SoldToParty = _Customer.Customer
{
key so.SoldToParty as Customer,
_Customer.CustomerName as CustomerName,
count( distinct so.SalesOrder ) as OpenOrderCount,
sum( so.TotalNetAmount ) as OpenNetAmount,
so.TransactionCurrency as Currency
}
where so.OverallSDProcessStatus in ( 'A', 'B' )
group by so.SoldToParty,
_Customer.CustomerName,
so.TransactionCurrency
METHOD report_open_orders.
TRY.
SELECT FROM zc_openordersbycustomer
FIELDS Customer, CustomerName, OpenOrderCount, OpenNetAmount, Currency
WHERE Customer IN @it_customer_range
ORDER BY OpenNetAmount DESCENDING
INTO TABLE @DATA(lt_result).
IF lt_result IS INITIAL.
RETURN.
ENDIF.
LOOP AT lt_result ASSIGNING FIELD-SYMBOL().
IF -openNetAmount > 100000.
send_alert( iv_customer = -customer
iv_amount = -openNetAmount ).
ENDIF.
ENDLOOP.
CATCH cx_sy_open_sql_db INTO DATA(lx_sql).
cl_aff_log=>add_message(
EXPORTING iv_msgid = 'ZSD'
iv_msgno = '010'
iv_msgty = 'E'
iv_msgv1 = lx_sql->get_text( ) ).
RAISE EXCEPTION TYPE cx_zsd_open_orders
EXPORTING previous = lx_sql.
ENDTRY.
ENDMETHOD.
이 단계에서 중요한 포인트는 두 가지입니다. 첫째, 미결 판정 로직(OverallSDProcessStatus in ('A','B'))을 CDS 뷰 안에 캡슐화해서 모든 호출자가 동일한 정의를 공유합니다. 둘째, ABAP 호출부는 비즈니스 분기와 로깅에만 집중합니다.
실전 예제 3단계: 가격 조건 분석과 운영 품질 고려
전통적으로 KONV(또는 PRCD_ELEMENTS) 테이블을 직접 조회해 조건 유형별(예: PR00=기본가, K007=할인) 금액을 추출했지만, I_SalesOrder의 _PricingElement association을 사용하면 헤더·아이템 키 매칭과 통화 변환 부담을 줄일 수 있습니다.
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Discount Ratio per Sales Order'
define view entity ZC_OrderDiscountRatio
as select from I_SalesOrder as so
{
key so.SalesOrder as SalesOrder,
so.SoldToParty as Customer,
so.TotalNetAmount as NetAmount,
so.TransactionCurrency as Currency,
cast(
coalesce(
( select sum( p.ConditionAmount )
from I_PricingElement as p
where p.SalesOrder = so.SalesOrder
and p.ConditionType = 'K007' ),
0 )
as abap.dec( 15, 2 ) ) as TotalDiscount,
case when so.TotalNetAmount > 0
then division(
coalesce( ( select sum( p.ConditionAmount )
from I_PricingElement as p
where p.SalesOrder = so.SalesOrder
and p.ConditionType = 'K007' ), 0 ),
so.TotalNetAmount, 4 )
else cast( 0 as abap.dec( 5, 4 ) )
end as DiscountRatio
}
운영 단계에서는 다음 사항을 함께 고려합니다.
- 성능: 가격 조건은 오더당 수십 행이 되기 쉬우므로, 분석 대상 기간을 반드시
CreationDate등으로 제한합니다. - 테스트: ABAP Unit + CDS Test Double Framework로
I_SalesOrder를 더블링하면 실제 트랜잭션 데이터 없이도 시나리오를 재현할 수 있습니다. - 보안:
@AccessControl.authorizationCheck: #CHECK는 DCL에서 정의된 판매 조직/영업처 권한을 자동 적용합니다. - 릴리스 호환: Released API 사용 여부를 ADT의 API State 탭에서 확인합니다.
자주 마주치는 함정과 트러블슈팅 FAQ
Q1. VBAK에는 있는 필드가 I_SalesOrder에는 없어 보입니다.
일부 운영용 필드는 Interface View에 노출되지 않습니다. 이런 경우 ZI_SalesOrderExt 뷰에서 I_SalesOrder를 base로 두고 추가 association을 만드는 패턴이 일반적으로 권장됩니다.
Q2. 미결 상태를 헤더 한 필드로만 판단해도 되나요?
헤더의 OverallSDProcessStatus는 아이템 상태의 집계입니다. 부분 출하·부분 청구가 빈번한 환경에서는 아이템 레벨(I_SalesOrderItem)의 DeliveryStatus를 함께 확인하는 것이 더 안전합니다.
Q3. CDS에서 association을 썼는데 실제로 JOIN이 발생하지 않는 것 같습니다.
association은 SELECT 절·WHERE 절에서 실제 필드를 참조해야 JOIN으로 펼쳐집니다. ST05 SQL 트레이스로 확인하고, LOOP 안에서 매번 참조하는 N+1 패턴에 주의합니다.
이어서 보면 좋은 주제
I_SalesOrderItem,I_SalesOrderScheduleLine을 활용한 아이템·납기 분석- RAP(ABAP RESTful Application Programming Model)로 판매 오더 조회용 OData 서비스 노출
- Analytical Query(
C_SalesOrderQry계열)로 Fiori Analytical List Page 구성 - CDS Test Double Framework로 단위 테스트 자동화
댓글 0
아직 댓글이 없습니다.