ABAP

아직도 TCURC JOIN? I_Currency 씁니다 #shorts #SAP #ABAP

개요와 이 글에서 얻어갈 것

SAP 시스템에서 금액을 다루는 모든 화면, 모든 보고서, 모든 인터페이스에는 반드시 통화 코드가 따라붙습니다. KRW, USD, EUR, JPY 같은 ISO 코드는 단순한 문자열이 아니라 소수점 자릿수, 환율 변환 규칙, 다국어 명칭이라는 부가 정보를 동반합니다. ABAP CDS에서 이 정보를 표준화된 방식으로 노출하는 가상 데이터 모델(VDM)이 바로 I_Currency입니다. 이 글에서는 TCURC 테이블 위에 어떻게 마스터 뷰가 구축되어 있는지, 이를 어떻게 자체 CDS 뷰에서 association으로 연결하는지를 단계별로 풀어봅니다.

  • TCURC, TCURT, TCURX와 I_Currency의 매핑 관계 이해
  • CDS 뷰에서 통화 코드를 외래키로 연결하는 표준 패턴 익히기
  • RAP 기반 서비스에서 currency code semantic을 부여하는 방법 확인
  • 실무에서 자주 마주치는 소수점/환율/텍스트 처리 트러블슈팅 대비

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

ABAP CDS 뷰 정의 문법(@AbapCatalog.sqlViewName, define view, association)에 익숙하고, ADT(Eclipse for ABAP) 환경에서 DDL 소스를 생성해본 경험이 있다면 충분히 따라올 수 있습니다. SAP의 가상 데이터 모델(Virtual Data Model) 계층 구조(I_, C_, P_ prefix)에 대한 기본 인식이 있으면 더 좋고, 통화·금액 필드를 다뤄본 적이 있다면 CURR/CUKY 타입의 의미도 자연스럽게 받아들일 수 있습니다.

실습 환경과 사전 준비물

아래 예제는 S/4HANA 2022 온프레미스 또는 그에 준하는 ABAP 7.57 이상 환경, 그리고 SAP BTP ABAP Environment(스팀러너 릴리스 기준)에서 동일하게 동작합니다. RAP 부분은 ABAP for Cloud Development 모델을 가정합니다.

  • SAP S/4HANA 2022 FPS01 이상 또는 BTP ABAP Environment
  • Eclipse 2024-03 + ADT(ABAP Development Tools) 3.40 이상
  • DDL/DDLS 생성 권한(S_DEVELOP), 패키지 할당 가능
  • 표준 VDM 뷰 사용을 위해 I_Currency, I_CurrencyText 활성 상태 확인
  • 테스트 데이터: 트랜잭션 OY03 또는 SE16N으로 TCURC 확인

BTP ABAP Environment에서는 SE16N 대신 ADT의 Data Preview를 사용하고, 트랜잭션 호출은 불가하므로 Fiori 앱 "Currencies" 또는 자체 RAP 서비스를 통해 데이터를 조회합니다.

핵심 개념: TCURC, TCURT, TCURX, 그리고 I_Currency

통화 코드는 겉으로는 세 글자짜리 문자열이지만, 시스템 내부에서는 네 개의 테이블이 협력하여 의미를 완성합니다. 마치 사람의 신분증을 만들 때 이름(영문)·이름(현지어)·생년월일·예외사항이 분리된 카드 묶음으로 관리되는 것과 비슷합니다.

  • TCURC — 통화 코드 마스터. WAERS(내부 키)와 ISOCD(ISO 코드), ALTWR(대체 키)를 보관합니다.
  • TCURT — 다국어 텍스트. SPRAS(언어키) + WAERS + KTEXT(짧은 명칭) + LTEXT(긴 명칭)로 구성됩니다.
  • TCURX — 소수점 자릿수 예외. JPY처럼 ISO 표준 소수점과 다른 처리가 필요한 통화를 정의합니다.
  • TCURR/TCURF/TCURN — 환율, 환산비율, 반올림 규칙. 통화 그 자체보다 환율 계산을 책임집니다.

I_Currency는 이 중 TCURC를 기반으로 정규화된 키 컬럼(Currency), ISO 코드, 소수점 자릿수, 그리고 텍스트 association을 노출합니다. ABAP 7.55부터는 @Semantics.unitOfMeasure: true 대신 @Semantics.currencyCode: true 어노테이션이 핵심 컬럼에 부여되어, 이 뷰에 association으로 연결된 모든 금액 필드는 자동으로 통화 의미를 인식합니다. Fiori Elements는 이 메타데이터를 보고 통화 기호와 소수점을 화면에 정확하게 렌더링합니다.

비유하자면 I_Currency는 "통화에 대한 위키피디아 표제어"이고, 우리가 만드는 매출 뷰는 그 표제어로 향하는 하이퍼링크입니다. 링크가 유효하면 Fiori는 자동으로 환율 변환, 소수점 처리, value help까지 제공합니다.

실전 예제 1단계 — 기본 association 연결

먼저 가장 단순한 매출 헤더 CDS 뷰를 만들고, 통화 컬럼에 I_Currency를 association으로 붙여보겠습니다. ZSALES_HEADER라는 가상의 테이블을 가정합니다(컬럼: SALES_ID, CUSTOMER_ID, NET_AMOUNT, CURRENCY_CODE, ORDER_DATE).

@AbapCatalog.sqlViewName: 'ZVSALESHDR'
@AbapCatalog.compiler.compareFilter: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Sales Header with Currency'
define view ZI_SalesHeader
  as select from zsales_header as Header
  association [0..1] to I_Currency as _Currency
    on $projection.TransactionCurrency = _Currency.Currency
{
  key Header.sales_id           as SalesId,
      Header.customer_id        as CustomerId,
      Header.order_date         as OrderDate,

      @Semantics.amount.currencyCode: 'TransactionCurrency'
      Header.net_amount         as NetAmount,

      @Semantics.currencyCode: true
      Header.currency_code      as TransactionCurrency,

      _Currency
}

핵심은 세 줄입니다. 첫째, association [0..1] to I_Currency로 마스터 뷰를 끌어옵니다. 둘째, @Semantics.currencyCode: true가 키 역할 컬럼에 부여됩니다. 셋째, 금액 컬럼인 NetAmount에는 @Semantics.amount.currencyCode 어노테이션으로 어떤 컬럼이 통화인지 명시합니다. 이 세 가지가 갖춰지면 ADT Data Preview에서 NetAmount가 통화별로 올바른 소수점으로 표시됩니다.

실전 예제 2단계 — 텍스트, 소수점, 환율을 결합한 실무 뷰

실무에서는 단순히 통화 코드뿐 아니라 통화의 한글 명칭, 소수점 자릿수, 그리고 원화 환산 금액까지 함께 보고 싶은 경우가 많습니다. I_Currency는 내부적으로 _Text association을 제공하므로 SystemLanguage 기준 텍스트를 손쉽게 가져올 수 있습니다.

@AbapCatalog.sqlViewName: 'ZVSALESCURR'
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Sales with Currency Detail'
@VDM.viewType: #COMPOSITE
define view ZC_SalesWithCurrencyInfo
  as select from ZI_SalesHeader as Sales
  association [0..1] to I_CurrencyText as _CurrencyText
    on  $projection.TransactionCurrency = _CurrencyText.Currency
    and _CurrencyText.Language           = $session.system_language
{
  key Sales.SalesId,
      Sales.CustomerId,
      Sales.OrderDate,

      @Semantics.amount.currencyCode: 'TransactionCurrency'
      Sales.NetAmount,

      @Semantics.currencyCode: true
      Sales.TransactionCurrency,

      // 원화 환산 — currency_conversion 빌트인 사용
      @Semantics.amount.currencyCode: 'TargetCurrency'
      currency_conversion(
        amount             => Sales.NetAmount,
        source_currency    => Sales.TransactionCurrency,
        round              => 'X',
        target_currency    => cast( 'KRW' as abap.cuky ),
        exchange_rate_date => Sales.OrderDate,
        exchange_rate_type => 'M',
        error_handling     => 'SET_TO_NULL'
      )                                   as NetAmountInKRW,

      @Semantics.currencyCode: true
      cast( 'KRW' as abap.cuky )          as TargetCurrency,

      _CurrencyText.CurrencyName          as CurrencyDescription,
      Sales._Currency.Decimals            as CurrencyDecimals
}

여기서 주의할 점은 error_handling 파라미터입니다. FAIL_ON_ERROR로 두면 환율이 없는 날짜에 대해 런타임 예외가 발생해 전체 쿼리가 실패합니다. 운영 환경에서는 SET_TO_NULL 또는 KEEP_UNCONVERTED를 권장합니다. 환율 변환 자체는 TCURR에서 OrderDate 이하의 가장 최근 환율을 찾아오는 방식으로 동작하므로, 환율 데이터의 적재 주기를 사전에 확인해야 합니다.

실전 예제 3단계 — RAP 서비스에 노출하고 value help 자동화

마지막으로 위 CDS 뷰를 RAP managed 서비스로 노출하고, Fiori Elements에서 통화 코드 입력 시 자동 value help가 뜨도록 구성합니다. 핵심은 @Consumption.valueHelpDefinition이 별도로 필요 없다는 점입니다. I_Currency가 이미 @ObjectModel.representativeKey와 텍스트 association을 갖추고 있어, association만 노출하면 OData 메타데이터에 자동 반영됩니다.

@EndUserText.label: 'Sales Order Projection for UI'
@AccessControl.authorizationCheck: #CHECK
@Metadata.allowExtensions: true
define root view entity ZC_SalesOrderUI
  provider contract transactional_query
  as projection on ZC_SalesWithCurrencyInfo
{
  key SalesId,
      CustomerId,
      OrderDate,
      NetAmount,
      TransactionCurrency,
      NetAmountInKRW,
      TargetCurrency,
      CurrencyDescription,
      CurrencyDecimals,
      /* expose association so Fiori picks up VH */
      _Currency,
      _CurrencyText
}

서비스 정의(Service Definition)와 서비스 바인딩(Service Binding)을 추가한 뒤 SAP Fiori Elements preview를 띄우면, TransactionCurrency 필드 옆에 돋보기 아이콘이 자동 생성되고 클릭 시 통화 코드/명칭 목록이 표시됩니다. NetAmount 컬럼은 통화별로 다른 소수점이 적용되어 JPY는 정수, KRW는 정수, USD는 소수점 2자리로 출력됩니다.

테스트는 다음과 같이 ABAP Unit으로 작성할 수 있습니다.

CLASS ltc_currency_lookup DEFINITION FOR TESTING
  DURATION SHORT RISK LEVEL HARMLESS.
  PRIVATE SECTION.
    METHODS jpy_has_zero_decimals FOR TESTING.
ENDCLASS.

CLASS ltc_currency_lookup IMPLEMENTATION.
  METHOD jpy_has_zero_decimals.
    SELECT SINGLE Decimals
      FROM I_Currency
      WHERE Currency = 'JPY'
      INTO @DATA(lv_decimals).
    cl_abap_unit_assert=>assert_equals(
      exp = 0
      act = lv_decimals
      msg = 'JPY should have zero decimals via TCURX exception' ).
  ENDMETHOD.
ENDCLASS.

이 테스트는 TCURX에 JPY가 0자리로 등록되어 있다는 사실을 검증합니다. 만약 어떤 시스템에서 이 테스트가 실패한다면 TCURX 커스터마이징이 누락된 상태이므로 BC-set FI_CURR_TCURX를 활성화해야 합니다.

현장에서 자주 부딪히는 함정과 해결법

Q1. NetAmount의 소수점이 항상 2자리로 나옵니다. 통화별로 달라야 하지 않나요?
A. @Semantics.amount.currencyCode 어노테이션이 가리키는 컬럼이 같은 SELECT 절에 존재해야 하고, 해당 컬럼에 @Semantics.currencyCode: true가 있어야 합니다. 둘 중 하나라도 빠지면 OData layer는 기본 2자리로 떨어집니다. ADT Data Preview는 어노테이션을 100% 따르지 않을 때도 있으므로 실제 검증은 Fiori 또는 OData $metadata에서 하시기 바랍니다.

Q2. currency_conversion이 0을 반환하거나 NULL이 됩니다.
A. 첫째, TCURR에 해당 일자/환율 타입(M, B, G 등) 조합이 존재하는지 확인합니다. 둘째, exchange_rate_date가 미래 날짜이면 환율이 없을 수 있습니다. 셋째, source와 target이 동일하면 1:1로 변환되지만, source 컬럼이 공백이면 함수가 NULL을 돌려줍니다. error_handling 옵션을 KEEP_UNCONVERTED로 두면 디버깅이 한결 수월합니다.

Q3. I_Currency에 없는 사내 가상 통화 코드를 쓰고 싶습니다.
A. TCURC에 먼저 등록해야 합니다(트랜잭션 OY03 또는 SM30의 V_TCURC). 등록 없이 association을 만들면 outer join이라 데이터는 조회되지만, value help와 텍스트가 비어 사용자 경험이 크게 떨어집니다. ISO 표준이 아닌 코드는 ALTWR 필드에 별도 표기하여 추적성을 확보하시는 것을 권장합니다.

그 밖에 자주 보이는 실수는 $session.system_language 대신 하드코딩으로 'E''1'을 넣어버리는 경우, 그리고 association을 select 절에 노출하지 않아 OData $expand가 동작하지 않는 경우입니다. 두 가지 모두 코드 리뷰 단계에서 잡아내는 것이 좋습니다.

이 글 이후에 살펴볼 만한 주제들

통화의 짝꿍은 단위(Unit of Measure)입니다. I_UnitOfMeasure는 T006 기반으로 동일한 패턴을 따르므로 이 글의 구조를 그대로 적용할 수 있습니다. 환율 변환을 본격적으로 다루려면 I_ExchangeRate(TCURR 기반)와 currency_conversion 외에 I_FixedExchangeRate, 그리고 RAP에서 환율 입력을 받는 패턴인 cl_exchange_rates API를 추적해보길 권장합니다. 분석계 쪽에서는 SAP Analytics Cloud의 currency translation 모델이 어떻게 동일한 TCURR을 활용하는지 확인하면 OLTP↔OLAP 일관성을 이해할 수 있습니다.

더 깊이 파고들 때 도움이 되는 자료들

댓글 0

아직 댓글이 없습니다.