TCURR 직접 조회의 함정 — 환율 변환이 망가지는 순간
실무에서 가장 흔하게 발견되는 ABAP 안티 패턴 중 하나가 TCURR 테이블을 직접 SELECT 하는 코드입니다. 환율은 단순히 "1 USD = 1,350 KRW" 같은 숫자 한 개가 아닙니다. SAP의 환율 데이터는 EXCHANGE_RATE, FROM_FACTOR, TO_FACTOR, VALID_FROM, EXCHANGE_RATE_TYPE가 결합된 복합 구조이며, 통화 페어(예: KRW → JPY)에 따라 직접 환율이 없으면 USD 같은 기축통화를 거쳐 간접 계산(referenced quotation)해야 합니다.
예를 들어 영업오더 금액을 USD에서 KRW로 변환할 때 TCURR-UKURS 만 가져와 곱하면 factor가 100, 1000인 통화(JPY, IDR 등)에서 결과가 100배 어긋납니다. 또한 VALID_FROM 필드는 "YYYYMMDD를 99999999에서 뺀 값"으로 저장되어 있어 단순 WHERE 조건으로 최신 환율을 찾기도 까다롭습니다. 이런 세부 사항을 매번 코드로 재구현하면 버그가 누적되고 결산 시 환차손 계산이 어긋나는 사고로 이어집니다.
이 글은 SAP S/4HANA(2022 / 2023 릴리스 기준, NetWeaver 7.5x 이상)에서 표준 펑션 모듈 CONVERT_TO_LOCAL_CURRENCY를 활용해 안전한 환율 변환을 구현하는 실전 패턴을 다룹니다. 영업오더(SalesOrder), 구매오더(PurchaseOrder) 시나리오로 변수명을 재구성했고, 예외 처리·정밀도·환율 유형 선택까지 다섯 단계로 깊이 들어갑니다.
- TCURR / TCURF / TCURV / TCURX 테이블 구조와 factor의 의미
- CONVERT_TO_LOCAL_CURRENCY 파라미터 9종 전체 활용
- 환율 유형 M / B / G의 회계 정책상 차이
- ROUND_OFF·소수점·통화별 decimal places 처리
- no_rate_found / overflow / no_factors_found 분기 대응
읽기 전 갖춰야 할 배경 지식
이 글은 ABAP Open SQL 기본 문법과 CALL FUNCTION 구문에 익숙하다고 가정합니다. TCURR, BKPF, VBAK, EKKO 같은 표준 테이블의 통화 필드(WAERS, WAERK)를 본 경험이 있으면 좋습니다. 또한 WAERS(통화 키)와 WERTV8(금액 도메인)의 차이, 그리고 ABAP에서 P 타입의 소수점 처리 방식을 알고 있어야 5섹션 이후의 정밀도 논의를 따라올 수 있습니다.
실습 환경과 사전 점검 항목
예제는 다음 환경에서 검증했습니다.
- SAP S/4HANA 2023 FPS01 (백엔드 NetWeaver AS ABAP 7.58)
- SE37 / ADT(Eclipse ABAP Development Tools 3.40 이상) 둘 다 호환
- SU01 권한:
S_TABU_DIS(TCURR 조회),F_TCURMNT(환율 유지) - 트랜잭션 OB08(환율 입력), OB07(환율 유형), OB22(통화 평가)
본격 코딩 전에 OB08에서 변환 대상 통화 페어에 환율이 등록되어 있는지, OB07에서 환율 유형(M, B, G)의 Reference Currency 설정과 Inversion 허용 여부를 확인하세요. 이 두 설정이 비어 있으면 펑션은 no_rate_found 예외를 반환합니다.
환율 데이터 모델과 표준 API의 동작 원리
SAP의 환율 처리는 네 개의 테이블이 협력해서 동작합니다. 비유하자면 TCURR은 "환율 장부", TCURF는 "단위 환산표", TCURV는 "환율 유형 정책서", TCURX는 "통화별 소수점 규칙"입니다.
- TCURR — 환율 유형 + From 통화 + To 통화 + 유효일자 별로 환율(UKURS)과 factor(FFACT/TFACT) 저장
- TCURF — 통화 페어별 환산 factor 별도 관리(예: JPY는 100단위)
- TCURV — 환율 유형(M=평균, B=Bank Buying, G=Bank Selling 등)과 기준 통화 정의
- TCURX — 통화별 실제 표시 소수점(JPY=0, KRW=0, BHD=3)
표준 펑션 CONVERT_TO_LOCAL_CURRENCY는 내부적으로 READ_EXCHANGE_RATE를 호출하고, factor를 자동 적용한 뒤 TCURX의 decimal places까지 정렬해 최종 금액을 돌려줍니다. 직접 SELECT로는 이 세 가지 보정 단계를 일일이 재구현해야 합니다.
또 하나 중요한 점은 inversion(역환율)입니다. KRW → USD 환율만 등록되어 있고 USD → KRW를 변환하려고 하면, 펑션은 TCURV의 inversion 설정을 보고 1/rate를 자동 계산합니다. 직접 SQL로는 이 fallback 로직이 누락되기 쉽습니다.
1단계 — CONVERT_TO_LOCAL_CURRENCY 기본 호출
가장 단순한 시나리오부터 시작합니다. 영업오더(SalesOrder) 헤더의 금액을 USD에서 회사 코드 통화(KRW)로 변환합니다.
REPORT z_so_currency_basic.
DATA: lv_so_amount_foreign TYPE p DECIMALS 2 VALUE '15000.00',
lv_so_amount_local TYPE p DECIMALS 2,
lv_so_currency_from TYPE waers VALUE 'USD',
lv_so_currency_to TYPE waers VALUE 'KRW',
lv_so_pricing_date TYPE sy-datum VALUE '20260525'.
CALL FUNCTION 'CONVERT_TO_LOCAL_CURRENCY'
EXPORTING
date = lv_so_pricing_date
foreign_amount = lv_so_amount_foreign
foreign_currency = lv_so_currency_from
local_currency = lv_so_currency_to
IMPORTING
local_amount = lv_so_amount_local
EXCEPTIONS
no_rate_found = 1
overflow = 2
no_factors_found = 3
no_spread_found = 4
derived_2_times = 5
OTHERS = 6.
IF sy-subrc = 0.
WRITE: / 'SalesOrder 환산금액:', lv_so_amount_local, lv_so_currency_to.
ELSE.
WRITE: / '환산 실패 sy-subrc=', sy-subrc.
ENDIF.
핵심은 date 파라미터입니다. 일반적으로 영업에서는 가격결정일(VBKD-PRSDT), 회계에서는 전기일(BKPF-BUDAT)을 사용합니다. 날짜를 잘못 주면 결산 환율이 아닌 일별 환율이 적용되어 차이가 발생합니다.
2단계 — LOCAL_AMOUNT와 부가 IMPORTING 활용
두 번째 단계에서는 단순 변환 결과 외에 적용된 환율 자체와 factor 정보를 함께 받아 로깅합니다. 회계 감사 대응이나 결산 차이 분석 시 "어떤 환율을 썼는가"를 추적하려면 필수입니다.
REPORT z_po_currency_audit.
DATA: lv_po_amount_doc TYPE bapicurr-bapicurr VALUE '2500000.00',
lv_po_amount_local TYPE bapicurr-bapicurr,
lv_po_curr_doc TYPE waers VALUE 'JPY',
lv_po_curr_local TYPE waers VALUE 'KRW',
lv_po_posting_date TYPE sy-datum,
lv_exchange_rate_used TYPE kurst_curr,
lv_factor_from TYPE i,
lv_factor_to TYPE i,
lv_audit_log TYPE string.
lv_po_posting_date = sy-datum.
CALL FUNCTION 'CONVERT_TO_LOCAL_CURRENCY'
EXPORTING
date = lv_po_posting_date
foreign_amount = lv_po_amount_doc
foreign_currency = lv_po_curr_doc
local_currency = lv_po_curr_local
IMPORTING
exchange_rate = lv_exchange_rate_used
foreign_factor = lv_factor_from
local_factor = lv_factor_to
local_amount = lv_po_amount_local
EXCEPTIONS
no_rate_found = 1
overflow = 2
no_factors_found = 3
OTHERS = 4.
CASE sy-subrc.
WHEN 0.
CONCATENATE 'PO 환산 OK rate='
lv_exchange_rate_used
'fromFactor=' lv_factor_from
'toFactor=' lv_factor_to
INTO lv_audit_log SEPARATED BY space.
MESSAGE lv_audit_log TYPE 'S'.
WHEN 1.
MESSAGE |환율 미등록: { lv_po_curr_doc } / { lv_po_curr_local } @ { lv_po_posting_date }| TYPE 'E'.
WHEN 2.
MESSAGE 'overflow: foreign_amount이 너무 큽니다 (BAPICURR 한계 초과)' TYPE 'E'.
WHEN 3.
MESSAGE 'TCURF에 factor 미등록' TYPE 'E'.
ENDCASE.
JPY는 TCURF의 factor가 100인 대표적 통화입니다. foreign_factor를 받아 로그에 남기면 "왜 JPY 2,500,000원이 KRW로 25,000원이 아닌 22,500,000원이 됐는가" 같은 질문에 즉시 답할 수 있습니다.
3단계 — TYPE / RATE / ROUND_OFF로 정밀도와 정책 제어
실무 결산 코드는 환율 유형을 명시적으로 지정합니다. M(평균환율)은 일반 거래, B(매입)는 외화 매출채권 평가, G(매도)는 외화 매입채무 평가에 사용하는 것이 일반적입니다. 추가로 분기/연결 결산에서는 회사 내부 환율(EURX, P001 등 커스텀 유형)을 쓰기도 합니다.
FUNCTION z_convert_so_with_policy.
*"----------------------------------------------------------------------
*" IMPORTING
*" VALUE(IV_AMOUNT) TYPE WERTV8
*" VALUE(IV_FROM_CURR) TYPE WAERS
*" VALUE(IV_TO_CURR) TYPE WAERS
*" VALUE(IV_DATE) TYPE SY-DATUM
*" VALUE(IV_RATE_TYPE) TYPE KURST DEFAULT 'M'
*" VALUE(IV_FIXED_RATE) TYPE KURST_CURR OPTIONAL
*" VALUE(IV_ROUND) TYPE ABAP_BOOL DEFAULT 'X'
*" EXPORTING
*" VALUE(EV_LOCAL_AMOUNT) TYPE WERTV8
*" VALUE(EV_RATE_USED) TYPE KURST_CURR
*" EXCEPTIONS
*" RATE_MISSING
*" FACTOR_MISSING
*" OVERFLOW_ERR
*"----------------------------------------------------------------------
CALL FUNCTION 'CONVERT_TO_LOCAL_CURRENCY'
EXPORTING
date = iv_date
foreign_amount = iv_amount
foreign_currency = iv_from_curr
local_currency = iv_to_curr
rate = iv_fixed_rate " 0이면 TCURR 조회, 0 아니면 강제 적용
type_of_rate = iv_rate_type
round_off = iv_round
IMPORTING
exchange_rate = ev_rate_used
local_amount = ev_local_amount
EXCEPTIONS
no_rate_found = 1
overflow = 2
no_factors_found = 3
no_spread_found = 4
derived_2_times = 5
OTHERS = 6.
CASE sy-subrc.
WHEN 1. RAISE rate_missing.
WHEN 2. RAISE overflow_err.
WHEN 3. RAISE factor_missing.
WHEN 4 OR 5 OR 6.
RAISE rate_missing.
ENDCASE.
ENDFUNCTION.
round_off = 'X'를 지정하면 펑션이 TCURX의 decimal places를 보고 자동 반올림합니다. JPY/KRW처럼 소수점이 없는 통화로 변환할 때 특히 유용합니다. 다만 여러 라인 아이템을 합산하는 회계 시나리오에서는 라인 단위 반올림 누적 오차가 발생할 수 있으므로, 결산 코드는 round_off = space로 호출한 뒤 합계에서 한 번만 반올림하는 패턴을 권장합니다.
rate 파라미터에 값을 직접 넣으면 TCURR 조회를 건너뛰고 강제 환율을 적용합니다. 시뮬레이션(예: "환율이 1,400원이면 손익이 어떻게 되나?") 리포트에 사용합니다.
예외 처리 — no_rate_found 외에 놓치기 쉬운 분기
CONVERT_TO_LOCAL_CURRENCY는 다섯 개 예외를 던지지만 실무에서 가장 흔히 만나는 함정은 다음과 같습니다.
- no_rate_found: TCURR에 해당 날짜·통화 페어·환율 유형 환율이 없음. inversion·reference currency를 OB07에서 활성화하면 상당수 해결됩니다.
- overflow:
foreign_amount가WERTV8(15자리, 소수점 2) 범위를 초과. 연결 결산에서 보고통화 합산 시 자주 발생하므로WERTV12또는 직접 분할 처리 필요. - no_factors_found: TCURF에 factor가 비어 있음. 신규 통화 추가 시 OBBS 누락이 원인.
- derived_2_times: reference currency(예: USD)를 거치는 간접 환산에서 한 번에 두 단계 derivation이 필요해 모호한 경우. 직접 환율을 OB08에 추가하는 것이 안전합니다.
자주 묻는 질문 세 가지
Q1. CDS View나 RAP에서는 어떻게 환산하나요?
S/4HANA 환경에서는 CDS에서 CURRENCY_CONVERSION( amount => ..., source_currency => ..., target_currency => ..., exchange_rate_date => ..., exchange_rate_type => 'M' ) 같은 SQL 함수를 사용합니다. 내부적으로 같은 환율 모델을 사용하지만 HANA 계산 엔진에서 실행되어 성능이 훨씬 좋습니다. ABAP 단건 처리는 펑션, 대량 집계는 CDS를 권장합니다.
Q2. 펑션 호출 비용이 부담스러운데 캐시해도 되나요?
같은 날짜·환율 유형·통화 페어에 대해서는 결과 환율을 ABAP 내부 테이블에 캐시해도 안전합니다. 다만 rate를 직접 지정한 경우는 캐시 키에 포함해야 하며, 장기 실행 배치에서는 환율 마스터 변경 가능성을 고려해 캐시 TTL을 짧게 두세요.
Q3. 단위 테스트는 어떻게 작성하나요?
ABAP Unit에서 CONVERT_TO_LOCAL_CURRENCY를 직접 호출하면 TCURR 의존성 때문에 테스트가 깨지기 쉽습니다. 펑션 호출을 래핑한 클래스를 인터페이스로 추상화하고, 테스트에서는 더블(Test Double)을 주입하세요. SAP에서 제공하는 CL_EXCHANGERATE_CONV_FACTORY 패턴도 참고할 만합니다.
TCURR 직접 조회 vs 표준 펑션 — 의사결정 체크리스트
| 비교 항목 | TCURR 직접 SELECT | CONVERT_TO_LOCAL_CURRENCY |
|---|---|---|
| factor(TCURF) 자동 적용 | 수동 구현 | 자동 |
| inversion(역환율) 처리 | 수동 1/rate | TCURV 정책 자동 적용 |
| reference currency 경유 | 구현 누락 빈발 | 자동 derivation |
| decimal places(TCURX) 반영 | 수동 | round_off 파라미터 |
| VALID_FROM 인버트 처리 | 실수 가능성 큼 | 내부 처리 |
| 유지보수성 / 회귀 안전성 | 낮음 | 높음(SAP 표준 업데이트 자동 반영) |
새 코드는 거의 모든 경우 표준 펑션 또는 CDS CURRENCY_CONVERSION을 선택해야 합니다. TCURR 직접 조회는 환율 마스터 조회 화면을 만드는 경우 등 "변환이 목적이 아닌 표시가 목적"인 시나리오에 한정하세요.
이어서 살펴볼 주제
이 글에서 다룬 펑션 모듈 기반 접근을 익혔다면, 다음 단계로 CDS의 CURRENCY_CONVERSION built-in 함수와 RAP(ABAP RESTful Application Programming Model)에서 통화 변환을 어떻게 선언적으로 처리하는지 살펴보길 권장합니다. 또한 그룹 결산 환경에서는 FAGL_CURRENCY_CONVERSION 계열 BAdI와 OB22의 통화 평가 설정을 함께 학습해야 실무 시나리오를 완성할 수 있습니다.
더 깊이 파볼 만한 자료
- SAP S/4HANA Help Portal — Cross Application Functions / Currency
- SAP ABAP Platform Documentation — Currency Conversion in CDS
- SAP NetWeaver 7.5 — General Ledger Accounting / Exchange Rates
- SAP Note 91481 — Conversion of foreign currency, factor maintenance
- SAP Note 783877 — Customizing of exchange rate types (OB07)
- SE37 → CONVERT_TO_LOCAL_CURRENCY / READ_EXCHANGE_RATE Documentation
- 트랜잭션 OB08 / OB07 / OBBS / OY03 — 환율 관련 커스터마이징 진입점
댓글 0
아직 댓글이 없습니다.