개요 및 학습 체크리스트
SAP S/4HANA에서 자재 수량을 표현할 때 항상 따라붙는 것이 단위(Unit of Measure, UoM)입니다. EA(개), KG(킬로그램), L(리터) 같은 코드만 보여주면 사용자는 직관적으로 이해하기 어렵고, 다국어 환경에서는 더욱 그렇습니다. I_UnitOfMeasure는 이러한 단위 코드를 텍스트와 함께 연결해 주는 표준 CDS View로, 신규 비즈니스 객체나 Fiori Elements 앱을 만들 때 거의 반드시 결합하게 되는 핵심 마스터 데이터 뷰입니다.
이번 예제에서 다룰 내용은 다음과 같습니다.
- I_UnitOfMeasure의 구조와 기반 테이블(T006, T006A) 관계 이해
- ASSOCIATION과 LEFT OUTER JOIN 두 가지 결합 방식 비교
$session.system_language를 활용한 다국어 텍스트 처리- 재고/구매오더 단위 표시용 Consumption View 작성
- 성능 최적화와 흔히 발생하는 함정 회피
먼저 알고 있어야 할 배경
이 글은 ABAP CDS View의 기본 문법(DEFINE VIEW ENTITY, SELECT, ASSOCIATION)을 한 번이라도 작성해 본 개발자를 대상으로 합니다. ADT(ABAP Development Tools, Eclipse) 환경에서 DDLS 객체를 생성·활성화하는 절차, 그리고 @ObjectModel·@Semantics 같은 어노테이션의 의미를 알고 있으면 따라가기 쉽습니다. SAP GUI에서 T006 테이블을 SE16N으로 열어본 경험이 있다면 텍스트 연결의 필요성을 더욱 빠르게 체감할 수 있습니다.
환경과 준비물
예제는 다음 환경을 기준으로 설명합니다.
- SAP S/4HANA 2022 On-Premise 또는 SAP S/4HANA Cloud Private Edition 2023
- ABAP Platform 2022 이상 (I_UnitOfMeasure 표준 제공 버전)
- ADT 3.34 이상이 설치된 Eclipse 2023-09 이상
- 개발 패키지: 로컬 임시 패키지(
$TMP) 또는 사용자 정의 패키지(예:ZINV_UOM) - 권한:
S_DEVELOP권한 객체에 DDLS 활동(02, 03) 부여
일반적으로 S/4HANA 1909부터 I_UnitOfMeasure가 제공되지만, 시스템에 따라 필드 구성이 약간 다를 수 있습니다. ADT의 Data Preview 또는 SE11에서 사전에 구조를 확인하는 것을 권장합니다.
핵심 개념 다지기
단위 정보는 SAP 시스템 내부적으로 세 개의 클래식 테이블에 분산되어 있습니다.
T006: 단위의 마스터(코드, 차원, 환산 비율, ISO 코드 등 기술적 속성)T006A: 단위의 언어별 텍스트(외부 표현 3자, 외부 표현 6자, 단위 설명문)T006D: 차원(Dimension) 마스터
예전에는 개발자가 직접 INNER JOIN T006 INNER JOIN T006A를 작성해야 했지만, SAP는 이 패턴을 표준화하여 I_UnitOfMeasure(단위 마스터)와 I_UnitOfMeasureText(언어별 텍스트) CDS View로 제공합니다. 비유하자면 T006/T006A는 원재료이고, I_UnitOfMeasure는 곧바로 비즈니스 로직에 사용할 수 있도록 가공된 반제품입니다.
핵심 원리: I_UnitOfMeasure는 단위 코드 자체에 대한 검증(Foreign Key Check)과 텍스트 연결(Text Association)을 한 번에 제공합니다. 따라서 새 CDS View에서
UnitOfMeasure필드를 외래키로 선언할 때WITH DEFAULT FILTER나1:0..1카디널리티를 활용하여 깔끔하게 결합할 수 있습니다.
특히 _Text 관계 끝에 붙는 $session.system_language는 ABAP CDS의 세션 변수로, 사용자의 로그온 언어를 자동으로 바인딩합니다. 즉, 한국어 로그인 사용자에게는 자동으로 한국어 텍스트가, 영어 사용자에게는 영문 텍스트가 노출됩니다. 이는 다국어 처리에 있어 매우 중요한 패턴입니다.
구조를 도식화하면 다음과 같습니다.
[ T006 ] ---- (1:N) ---- [ T006A ]
| |
+-- I_UnitOfMeasure -------+
|
+-- _Text (1:0..1, SystemLanguage 필터)
+-- _Dimension
1단계 — 단위 코드와 텍스트를 결합한 기본 예제
가장 단순한 시나리오부터 시작합니다. 재고 항목(가상의 테이블 zinv_item)에 저장된 단위 코드를 텍스트와 함께 보여주는 CDS View를 작성합니다.
@AbapCatalog.viewEnhancementCategory: [#NONE]
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Inventory Item with UoM Text'
@Metadata.ignorePropagatedAnnotations: true
define view entity ZI_InventoryItemBasic
as select from zinv_item as Item
association [0..1] to I_UnitOfMeasure as _Uom
on Item.base_uom = _Uom.UnitOfMeasure
{
key Item.item_id as InventoryItemId,
Item.material as Material,
Item.stock_qty as StockQuantity,
@Semantics.unitOfMeasure: true
Item.base_uom as BaseUnit,
_Uom._Text.UnitOfMeasureText as BaseUnitText,
_Uom
}
여기서 주목할 부분은 두 가지입니다. 첫째, @Semantics.unitOfMeasure: true 어노테이션을 통해 해당 필드가 단위 코드임을 명시하면, Fiori Elements가 자동으로 정렬·필터에 적용합니다. 둘째, _Uom._Text.UnitOfMeasureText처럼 이중 ASSOCIATION을 타고 들어가면 LEFT OUTER JOIN이 두 번 발생하지만, CDS는 이를 효율적으로 컴파일합니다.
2단계 — 구매 오더 표시용 실무 시나리오와 다국어 처리
실제 업무에서는 구매 오더 라인 아이템처럼 양/단위가 동시에 등장하고, 다국어 사용자를 동시에 지원해야 하는 경우가 많습니다. 또한 결합 방식으로 LEFT OUTER JOIN을 선호하는 팀도 있어, 두 방식을 한 코드에서 비교해 봅니다.
@AbapCatalog.viewEnhancementCategory: [#NONE]
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Purchase Item Display with Unit Text'
@Metadata.allowExtensions: true
@VDM.viewType: #COMPOSITE
define view entity ZC_PurchaseItemDisplay
as select from zpur_item as Pur
association [0..1] to I_UnitOfMeasure as _OrderUom
on Pur.order_uom = _OrderUom.UnitOfMeasure
association [0..*] to I_UnitOfMeasureText as _OrderUomText
on Pur.order_uom = _OrderUomText.UnitOfMeasure
and _OrderUomText.Language = $session.system_language
{
key Pur.po_number as PurchaseOrderNumber,
key Pur.po_item as PurchaseOrderItem,
Pur.material as Material,
@Semantics.quantity.unitOfMeasure: 'OrderUnit'
Pur.order_qty as OrderQuantity,
@Semantics.unitOfMeasure: true
Pur.order_uom as OrderUnit,
// 방식 A: ASSOCIATION 체인 - 가장 권장
_OrderUom._Text.UnitOfMeasureText as OrderUnitTextViaChain,
// 방식 B: 명시적 언어 필터 ASSOCIATION
_OrderUomText.UnitOfMeasureText as OrderUnitTextExplicit,
// 방식 C: ISO 코드까지 함께 노출
_OrderUom.UnitOfMeasureISOCode as UnitIsoCode,
_OrderUom,
_OrderUomText
}
방식 A는 표준 ASSOCIATION 체이닝을 그대로 활용해 가독성이 좋습니다. 방식 B는 언어 필터를 외부로 노출해 디버깅이 쉽고, 특정 언어로 강제하고 싶을 때 유연합니다. 일반적으로 운영 환경에서는 방식 A를 사용하고, 다국어 리포트 등 특수 케이스에서만 방식 B를 사용하는 것이 권장됩니다.
또한 @Semantics.quantity.unitOfMeasure: 'OrderUnit' 어노테이션으로 수량 필드와 단위 필드를 묶어두면, OData 노출 시 자동으로 단위 변환 가능 여부가 클라이언트에 전달됩니다.
3단계 — 프로덕션 품질의 Consumption View와 권한·성능 고려
프로덕션에서는 권한 체크, 캐싱, 무효 단위 방어, 단위 환산까지 고려해야 합니다. 다음은 Fiori UI에 노출할 Consumption View로, CDS Currency/Quantity 변환 함수(UNIT_CONVERSION)와 권한 어노테이션을 함께 적용한 예제입니다.
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Purchase Item UoM Production View'
@Metadata.allowExtensions: true
@OData.publish: true
@UI.headerInfo: { typeName: 'Purchase Item',
typeNamePlural: 'Purchase Items' }
define view entity ZC_PurchaseItemUomProd
as select from ZC_PurchaseItemDisplay as Base
association [0..1] to I_UnitOfMeasure as _TargetUom
on $projection.TargetUnit = _TargetUom.UnitOfMeasure
{
key Base.PurchaseOrderNumber,
key Base.PurchaseOrderItem,
Base.Material,
@Semantics.quantity.unitOfMeasure: 'OrderUnit'
Base.OrderQuantity,
@Semantics.unitOfMeasure: true
Base.OrderUnit,
Base.OrderUnitTextViaChain as OrderUnitText,
// 타깃 단위로 환산 (예: EA -> CAR)
cast( 'CAR' as abap.unit( 3 ) ) as TargetUnit,
@Semantics.quantity.unitOfMeasure: 'TargetUnit'
unit_conversion(
quantity => Base.OrderQuantity,
source_unit => Base.OrderUnit,
target_unit => cast( 'CAR' as abap.unit( 3 ) ),
error_handling => 'SET_TO_NULL'
) as OrderQuantityInTarget,
_TargetUom._Text.UnitOfMeasureText as TargetUnitText
}
핵심 포인트는 세 가지입니다.
@AccessControl.authorizationCheck: #CHECK로 DCL(Data Control Language) 적용을 강제합니다. 운영에서는 권한 누락이 잦은 실수입니다.unit_conversion함수의error_handling을SET_TO_NULL로 두면, 환산 실패 시 시스템이 SHORT DUMP를 내지 않고 NULL을 반환합니다. 사용자 화면에 예기치 못한 오류를 방지할 수 있습니다.- OData를 통해 UI5/Fiori Elements에 노출할 때,
@Semantics어노테이션이 정확해야 Smart Field가 단위와 수량을 자동 페어링합니다.
실수하기 쉬운 지점과 트러블슈팅
Q1. UnitOfMeasureText가 공백으로 나옵니다.
가장 흔한 원인은 $session.system_language가 로그온 언어와 매칭되지 않는 경우입니다. 예를 들어 한국어(KO) 사용자인데 T006A에 KO 텍스트가 없으면 빈 값이 반환됩니다. 해결책은 (1) SM30에서 T006A에 누락된 언어 텍스트를 추가하거나, (2) Fallback 로직으로 영어(E)를 함께 가져오는 별도 컬럼을 제공하는 것입니다.
Q2. ASSOCIATION을 선언했는데 SELECT 결과에서 LEFT OUTER JOIN으로 보이지 않습니다.
CDS는 ASSOCIATION을 실제 사용(필드를 SELECT 절에 노출)할 때만 조인으로 펼칩니다. 따라서 _Uom.UnitOfMeasure를 SELECT 절에 쓰지 않으면 런타임 조인이 발생하지 않습니다. 이는 성능상 이점이지만, 어노테이션만으로 단위 결합을 기대하면 의도와 달라질 수 있습니다.
Q3. unit_conversion이 환산 실패로 자주 dump를 발생시킵니다.
T006의 DIMID가 동일한 단위끼리만 환산이 가능합니다. 예를 들어 EA(piece)와 KG(weight)는 차원이 다르므로 환산 불가입니다. error_handling => 'SET_TO_NULL'로 방어하거나, 사전에 I_UnitOfMeasure의 UnitOfMeasureDimension을 비교해 같은 차원일 때만 환산하도록 분기 처리해야 합니다.
추가 팁으로, 디버깅 시 ADT의 Data Preview에서 SQL을 추출(F2 → Show Generated DDL Source)하면 실제 실행되는 JOIN 구문을 확인할 수 있어 성능 분석에 큰 도움이 됩니다.
이어서 살펴볼 주제
I_UnitOfMeasure에 익숙해졌다면 같은 패턴의 통화(Currency) 마스터인 I_Currency, 국가 마스터 I_Country, 언어 마스터 I_Language로 확장해 보세요. 또한 RAP(RESTful Application Programming Model)에서 Behavior Definition에 단위 필드를 사용할 때의 검증 패턴, Analytical Query(@Analytics.query: true)에서 단위별 집계를 다루는 방법까지 함께 학습하면 마스터 데이터 결합 전반에 대한 감각을 얻을 수 있습니다. 한 걸음 더 나아가 Embedded Analytics나 Fiori Elements List Report에 적용해 실제 비즈니스 사용자에게 단위 텍스트가 어떻게 노출되는지 확인해 보시기 바랍니다.
도움이 되는 링크
댓글 0
아직 댓글이 없습니다.