개요 및 이 글에서 다루는 것
ABAP CDS(Core Data Services)에서 금액 필드를 다룰 때 가장 까다로운 부분은 통화(Currency) 처리입니다. 한 회사가 KRW, USD, EUR을 동시에 다룰 때 단순한 DECIMAL 필드만으로는 의미가 불완전합니다. 이 글에서는 어노테이션 기반 시맨틱 선언으로 금액-통화 필드를 연결하고, Fiori Elements에서 자동으로 통화 드롭다운을 받고, 최종적으로 CURRENCY_CONVERSION 함수를 직접 호출하지 않고도 환율 변환 결과를 뷰에서 노출하는 방법을 단계별로 다룹니다.
- @Semantics.amount.currencyCode 어노테이션으로 금액-통화 컬럼 연결하기
- @Consumption.valueHelpDefinition으로 Fiori 통화 F4 검색도움말 생성하기
- CURRENCY_CONVERSION 함수의 동작 원리와 어노테이션 기반 자동 변환 비교
- TCURR, TCURX 테이블이 변환에 미치는 영향과 예외 처리
읽기 전에 알고 있으면 좋은 것
이 글은 중급 ABAP 개발자를 대상으로 합니다. CDS View의 define view 문법, SE11에서 도메인/데이터 요소를 다뤄본 경험, ADT(ABAP Development Tools in Eclipse) 사용 경험이 있으면 좋습니다. 또한 TCURR(환율), TCURX(통화별 소수점 자리수), TCURC(통화 코드 마스터) 테이블의 존재만 알고 있어도 충분히 따라올 수 있습니다.
실습 환경 및 준비물
예제는 SAP S/4HANA 2022(또는 ABAP Platform 2022 on-premise) 기준으로 작성했으며, ABAP Cloud(Steampunk) 환경에서도 일부 어노테이션 차이를 제외하면 동일하게 동작하는 것으로 알려져 있습니다. 클라이언트는 ADT 3.36 이상을 권장합니다.
- SAP NetWeaver 7.55+ 또는 S/4HANA 2020 이상 (CDS amount.currencyCode 어노테이션 지원)
- ADT(Eclipse) + ABAP Development Tools 플러그인
- 샘플 데이터: 자체 ZPO_HEADER 테이블 또는 EKKO/EKPO 같은 표준 구매 테이블
- TCURR 테이블에 환율 데이터가 적재되어 있어야 변환 함수가 정상 동작합니다
- 권한: S_DEVELOP(CDS DDL 생성), S_RFC(테스트 시)
참고: 일부 어노테이션(@Consumption.valueHelpDefinition)은 OData 노출 시점에 의미가 있으므로, RAP(BO 정의) 또는 클래식 게이트웨이 서비스로 익스포즈한 뒤 Fiori Elements 프리뷰로 검증하는 것이 일반적입니다.
핵심 개념 - 시맨틱 어노테이션이 왜 필요한가
관계형 데이터베이스 입장에서 NET_AMOUNT DECIMAL(15,2)와 CURRENCY_CODE CHAR(5)는 별개의 컬럼입니다. DB는 둘 사이의 관계를 모릅니다. 그러나 비즈니스 관점에서는 "1000"이라는 숫자가 KRW인지 USD인지에 따라 가치가 1300배 차이날 수 있습니다. ABAP 클래식 환경에서는 데이터 요소(Data Element)의 Reference 탭에서 Reference field를 지정해 이 관계를 표현했습니다. CDS는 이 사고방식을 어노테이션으로 끌어올렸습니다.
비유하자면 금액 필드는 "숫자"가 적힌 영수증이고, 통화 필드는 영수증 상단의 "₩/$/€" 기호입니다. 둘을 떼어 놓으면 영수증이 의미를 잃습니다. @Semantics.amount.currencyCode: 'CURRENCY_FIELD' 어노테이션은 "이 금액의 통화 기호는 저기에 있다"고 시스템에 알려주는 일종의 풀(string)입니다.
이 풀이 연결되면 세 가지 자동화가 따라옵니다. 첫째, Fiori UI에서 금액을 표시할 때 자동으로 통화에 맞는 소수점(JPY는 0자리, KWD는 3자리)이 적용됩니다. 둘째, OData $metadata에 sap:semantics="currency-code"가 노출되어 클라이언트가 별도 매핑 없이 인식합니다. 셋째, 가장 중요한 점인데, CDS 빌트인 함수 CURRENCY_CONVERSION이 의미적으로 정합성을 가집니다. TCURX의 소수점 보정이 자동으로 들어가기 때문에 ABAP 개발자가 직접 곱셈/나눗셈을 하지 않아도 됩니다.
도식으로 표현하면 다음과 같습니다:
[DB Layer] NET_AMOUNT (1000.00) + CURRENCY (KRW)
| |
+---- @Semantics ----------+
|
[CDS Layer] define view ...
|
+-- CURRENCY_CONVERSION( amount, source, target, rate_date )
|
[OData] sap:semantics="currency-code" + value help
|
[Fiori UI] 1,000 KRW -> 0.77 USD (드롭다운 + 자동 포맷)
실전 코드 1단계 - 기본 금액-통화 연결 뷰
가장 단순한 형태입니다. 구매 발주 헤더(PurchaseOrder)에서 발주 총액과 통화를 노출하는 뷰를 만듭니다. 자체 테이블 zpo_header가 있다고 가정합니다(필드: po_id, vendor_id, total_amount, doc_currency, posting_date).
@AbapCatalog.sqlViewName: 'ZVPO_BASIC'
@AbapCatalog.compiler.compareFilter: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: '구매발주 기본 금액 뷰'
define view Z_I_PurchaseOrder_Basic
as select from zpo_header as po
{
key po.po_id as PurchaseOrderId,
po.vendor_id as VendorId,
po.posting_date as PostingDate,
@Semantics.amount.currencyCode: 'DocumentCurrency'
po.total_amount as TotalAmount,
@Semantics.currencyCode: true
po.doc_currency as DocumentCurrency
}
중요한 두 어노테이션을 보세요. @Semantics.amount.currencyCode: 'DocumentCurrency'는 TotalAmount 위에 붙어 "내 통화 컬럼의 별칭은 DocumentCurrency다"라고 선언합니다. 그리고 통화 필드 위의 @Semantics.currencyCode: true는 "이게 통화 코드다"라고 표시합니다. 별칭(alias) 이름을 참조해야 하며 원본 컬럼명(doc_currency)이 아닙니다. 이 부분을 헷갈리는 개발자가 많습니다.
액티베이트 후 Data Preview로 확인해보면 금액이 통화별 소수점 규칙대로 보입니다. JPY 행은 정수, KRW 행은 소수점 없음, USD는 2자리로 정렬됩니다.
실전 코드 2단계 - 환율 변환과 F4 도움말, 에러 처리
실무에서는 단지 표시만 하는 게 아니라 "원화 환산 금액"을 함께 보여달라는 요구가 흔합니다. CDS 빌트인 CURRENCY_CONVERSION을 사용하되 어노테이션과 결합합니다. 동시에 통화 코드 입력 필드에 F4 검색도움말을 자동 생성합니다.
@AbapCatalog.sqlViewName: 'ZVPO_CONV'
@AbapCatalog.compiler.compareFilter: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: '구매발주 환율 변환 뷰'
define view Z_I_PurchaseOrder_Converted
as select from zpo_header as po
{
key po.po_id as PurchaseOrderId,
po.vendor_id as VendorId,
po.posting_date as PostingDate,
@Semantics.amount.currencyCode: 'DocumentCurrency'
po.total_amount as TotalAmount,
@Consumption.valueHelpDefinition: [{ entity: {
name: 'I_Currency', element: 'Currency' } }]
@Semantics.currencyCode: true
po.doc_currency as DocumentCurrency,
// KRW 환산 금액 - 빌트인 함수 사용
@Semantics.amount.currencyCode: 'LocalCurrency'
currency_conversion(
amount => po.total_amount,
source_currency => po.doc_currency,
target_currency => cast( 'KRW' as abap.cuky( 5 ) ),
exchange_rate_date => po.posting_date,
exchange_rate_type => cast( 'M' as abap.char( 4 ) ),
error_handling => 'SET_TO_NULL'
) as AmountInKRW,
@Semantics.currencyCode: true
cast( 'KRW' as abap.cuky( 5 ) ) as LocalCurrency
}
핵심 포인트를 짚어봅니다. error_handling 파라미터를 'SET_TO_NULL'로 지정한 이유는 TCURR에 해당 일자/통화쌍의 환율이 없을 때 덤프(예외)가 발생하면 뷰 전체 조회가 실패하기 때문입니다. 대안은 'KEEP_AMOUNT'(원금 그대로 유지)와 'FAIL_ON_ERROR'가 있습니다. 운영 환경에서는 SET_TO_NULL을 쓰고 UI에서 별도 표시하는 패턴이 일반적입니다.
exchange_rate_type 'M'은 평균환율을 의미하며, B(매입), G(매도) 등이 있습니다. 회계 정책에 따라 결정합니다. @Consumption.valueHelpDefinition은 표준 뷰 I_Currency를 참조해 Fiori 필터바에 자동으로 드롭다운을 생성합니다. 이 어노테이션은 OData V2/V4 어느 쪽이든 게이트웨이 노출 시 인식됩니다.
실전 코드 3단계 - 프로덕션 패턴(파라미터화, 권한, 성능)
실제 운영에서는 환산 통화/환율 타입/기준일을 호출 측에서 지정할 수 있어야 재사용성이 올라갑니다. 파라미터화된 뷰로 발전시키고 액세스 컨트롤(DCL)까지 결합합니다.
@AbapCatalog.sqlViewName: 'ZVPO_PROD'
@AbapCatalog.compiler.compareFilter: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: '구매발주 환산 뷰 - 프로덕션'
@VDM.viewType: #COMPOSITE
@ClientHandling.algorithm: #SESSION_VARIABLE
define view Z_C_PurchaseOrder_FX
with parameters
p_target_currency : abap.cuky( 5 ),
p_rate_type : abap.char( 4 ),
p_rate_date : abap.dats
as select from zpo_header as po
association [0..1] to I_Currency as _Currency
on $projection.DocumentCurrency = _Currency.Currency
{
key po.po_id as PurchaseOrderId,
po.vendor_id as VendorId,
po.posting_date as PostingDate,
@Semantics.amount.currencyCode: 'DocumentCurrency'
po.total_amount as TotalAmount,
@Consumption.valueHelpDefinition: [{ entity: {
name: 'I_Currency', element: 'Currency' } }]
@Semantics.currencyCode: true
po.doc_currency as DocumentCurrency,
@Semantics.amount.currencyCode: 'TargetCurrency'
currency_conversion(
amount => po.total_amount,
source_currency => po.doc_currency,
target_currency => :p_target_currency,
exchange_rate_date => :p_rate_date,
exchange_rate_type => :p_rate_type,
error_handling => 'SET_TO_NULL',
round => 'X',
decimal_shift => 'X',
decimal_shift_back => 'X'
) as AmountConverted,
@Semantics.currencyCode: true
:p_target_currency as TargetCurrency,
_Currency
}
성능과 정확성에 영향을 주는 세 파라미터를 추가했습니다. round = 'X'는 결과를 통화 소수점 자리수에 맞춰 반올림합니다. decimal_shift와 decimal_shift_back은 TCURX에 등록된 통화별 소수점 보정을 자동 적용합니다. 일본 엔화(JPY)는 내부적으로 100단위로 저장되는 등의 특수성이 있어 이 옵션을 켜지 않으면 100배 차이가 나는 사고가 발생합니다.
@AccessControl.authorizationCheck: #CHECK와 함께 별도 DCL 파일을 만들면 행 수준 보안이 적용됩니다:
@MappingRole: true
define role Z_C_PurchaseOrder_FX {
grant select on Z_C_PurchaseOrder_FX
where (VendorId) = aspect pfcg_auth( Z_PO_AUTH, VENDOR, ACTVT = '03' );
}
호출 측 ABAP 코드는 다음과 같이 파라미터를 전달합니다:
SELECT po_id, total_amount, document_currency,
amount_converted, target_currency
FROM z_c_purchaseorder_fx(
p_target_currency = 'KRW',
p_rate_type = 'M',
p_rate_date = @sy-datum )
INTO TABLE @DATA(lt_po)
UP TO 100 ROWS.
테스트 시에는 ADT의 Data Preview에서 파라미터 입력창이 자동 생성되므로 수작업으로 다양한 통화 조합을 검증할 수 있습니다.
자주 발생하는 실수와 트러블슈팅
Q1. "Currency reference is missing" 같은 메시지로 액티베이션이 실패합니다.
A. @Semantics.amount.currencyCode가 가리키는 별칭이 실제 SELECT 리스트의 별칭과 정확히 일치하는지 확인하세요. 원본 컬럼명(doc_currency)이 아니라 별칭(DocumentCurrency)이어야 합니다. 또한 통화 필드 자체에 @Semantics.currencyCode: true가 빠지면 일부 도구가 인식하지 못합니다.
Q2. 환율 변환 결과가 모두 NULL로 나옵니다.
A. 가장 흔한 원인은 TCURR에 해당 날짜/통화쌍의 레코드가 없는 경우입니다. SE16N으로 TCURR을 열어 KURST(환율 타입), FCURR(원천 통화), TCURR(대상 통화), GDATU(역순 날짜)를 확인하세요. 또한 error_handling = 'SET_TO_NULL'로 설정되어 있으면 환율이 없을 때 NULL이 반환되므로, 원인 추적 시 일시적으로 'FAIL_ON_ERROR'로 바꿔 짧은 메시지를 받아보는 것이 디버깅에 유리합니다.
Q3. JPY 환산 결과가 예상보다 100배 큽니다(혹은 작습니다).
A. TCURX 테이블이 원인입니다. 일본 엔화처럼 일부 통화는 ABAP 내부에서 비정상 소수점으로 저장됩니다. currency_conversion 호출 시 decimal_shift = 'X', decimal_shift_back = 'X'를 명시하면 보정이 자동 적용됩니다. 표시 단계에서 보정하려 하면 다른 통화와 로직이 꼬이므로 변환 시점에 처리하는 것이 권장됩니다.
Q4. Fiori Elements에서 통화 드롭다운이 안 나타납니다.
A. @Consumption.valueHelpDefinition이 OData 메타데이터로 노출되는지 확인하세요. 일반적으로 RAP 서비스 정의에서 expose 또는 클래식 게이트웨이의 $metadata 응답에 ValueList 어노테이션이 포함되어야 합니다. 또한 참조하는 I_Currency 뷰가 해당 시스템에 존재하는지(S/4HANA 표준 뷰), 일부 구버전 NetWeaver에서는 별도 커스텀 뷰를 만들어 참조해야 합니다.
이어서 살펴볼 만한 주제
이 글에서 다룬 통화 변환은 시맨틱 어노테이션 패밀리의 한 축입니다. 같은 사고방식이 단위(Unit) 변환에도 동일하게 적용됩니다. @Semantics.quantity.unitOfMeasure와 UNIT_CONVERSION 빌트인 함수로 KG↔LB 변환이 가능합니다. 또한 RAP(ABAP RESTful Application Programming Model) BO에서 통화 필드를 다룰 때 Behavior Definition의 field ( readonly ) currency_code 같은 선언과 결합되며, Draft 처리 시 환산 값이 어떻게 갱신되는지도 학습 가치가 있습니다. ABAP Cloud(Steampunk)에서는 일부 클래식 어노테이션이 deprecated되고 RAP 중심으로 재편되었으므로 마이그레이션 가이드를 함께 살펴보면 좋습니다.
외부 자료 모음
- help.sap.com - CDS Annotations Reference
- help.sap.com - CURRENCY_CONVERSION Built-in Function
- help.sap.com - Semantics Annotations for Amount and Currency
- help.sap.com - Value Help in CDS Views
- SAP Community Blogs - ABAP CDS Views 태그
- help.sap.com - ABAP Cloud Development Guide
- help.sap.com - TCURR/TCURX Currency Configuration
댓글 0
아직 댓글이 없습니다.