ABAP Currency & Unit Conversion: Currency and Units of Measure

Category
ABAP-Statements
Published
Author
Johannes

Currency & Unit Conversion in ABAP requires special attention to decimal places, exchange rates, and conversion factors. Correct handling is crucial for financial and logistics applications.

Basic Concept

TopicDescription
CurrencyDecimal places depend on currency (EUR: 2, JPY: 0)
Exchange RateRates from table TCURR
Unit of MeasureConversion factors in T006
Internal vs. ExternalDifferent representation

Examples

1. Convert Currency Amount

DATA: lv_amount_eur TYPE bapicurr-bapicurr VALUE '1000.00',
lv_amount_usd TYPE bapicurr-bapicurr,
lv_rate TYPE bapi1093_0.
" Convert EUR to USD
CALL FUNCTION 'BAPI_CURRENCY_CONV_TO_EXTERNAL'
EXPORTING
currency = 'EUR'
amount_internal = lv_amount_eur
IMPORTING
amount_external = lv_amount_eur.
CALL FUNCTION 'BAPI_EXCHANGERATE_GETDETAIL'
EXPORTING
rate_type = 'M' " Rate type
from_curr = 'EUR'
to_currncy = 'USD'
date = sy-datum
IMPORTING
exch_rate = lv_rate
EXCEPTIONS
not_found = 1
OTHERS = 2.
IF sy-subrc = 0.
lv_amount_usd = lv_amount_eur * lv_rate-exch_rate.
WRITE: / '1000 EUR =', lv_amount_usd, 'USD'.
ENDIF.

2. Currency Conversion with CONVERT_TO_LOCAL_CURRENCY

DATA: lv_source_amount TYPE dmbtr VALUE '1000.00',
lv_target_amount TYPE dmbtr.
CALL FUNCTION 'CONVERT_TO_LOCAL_CURRENCY'
EXPORTING
date = sy-datum
foreign_amount = lv_source_amount
foreign_currency = 'USD'
local_currency = 'EUR'
type_of_rate = 'M'
IMPORTING
local_amount = lv_target_amount
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: / '1000 USD =', lv_target_amount, 'EUR'.
ELSE.
WRITE: / 'Exchange rate not found'.
ENDIF.

3. Decimal Places by Currency

DATA: lv_amount_eur TYPE netwr VALUE '123.45',
lv_amount_jpy TYPE netwr VALUE '12345',
lv_decimals TYPE i.
" Determine decimal places of a currency
SELECT SINGLE currdec FROM tcurx
WHERE currkey = 'EUR'
INTO @lv_decimals.
IF sy-subrc <> 0.
lv_decimals = 2. " Default
ENDIF.
WRITE: / 'EUR has', lv_decimals, 'decimal places'.
" For JPY (0 decimal places)
SELECT SINGLE currdec FROM tcurx
WHERE currkey = 'JPY'
INTO @lv_decimals.
" lv_decimals = 0
" Format amount according to currency
CLASS zcl_currency DEFINITION.
PUBLIC SECTION.
CLASS-METHODS: format_amount
IMPORTING iv_amount TYPE any
iv_currency TYPE waers
RETURNING VALUE(rv_formatted) TYPE string.
ENDCLASS.
CLASS zcl_currency IMPLEMENTATION.
METHOD format_amount.
DATA: lv_decimals TYPE i.
SELECT SINGLE currdec FROM tcurx
WHERE currkey = @iv_currency
INTO @lv_decimals.
IF sy-subrc <> 0.
lv_decimals = 2.
ENDIF.
rv_formatted = |{ iv_amount DECIMALS = lv_decimals }|.
ENDMETHOD.
ENDCLASS.

4. Internal/External Currency Representation

DATA: lv_internal TYPE bapicurr-bapicurr,
lv_external TYPE bapicurr-bapicurr.
" Internal (DB) -> External (display)
" For currencies with less than 2 decimal places
lv_internal = '12345'. " JPY in DB (without decimals)
CALL FUNCTION 'BAPI_CURRENCY_CONV_TO_EXTERNAL'
EXPORTING
currency = 'JPY'
amount_internal = lv_internal
IMPORTING
amount_external = lv_external.
" lv_external = '12345.00' for display
" External -> Internal (for storage)
lv_external = '12345.00'.
CALL FUNCTION 'BAPI_CURRENCY_CONV_TO_INTERNAL'
EXPORTING
currency = 'JPY'
amount_external = lv_external
max_number_of_digits = 15
IMPORTING
amount_internal = lv_internal.
" lv_internal = '12345' for DB

5. Convert Units of Measure

DATA: lv_quantity_kg TYPE menge VALUE '1000',
lv_quantity_to TYPE menge.
" Convert kg to t (tons)
CALL FUNCTION 'UNIT_CONVERSION_SIMPLE'
EXPORTING
input = lv_quantity_kg
unit_in = 'KG'
unit_out = 'TO'
IMPORTING
output = lv_quantity_to
EXCEPTIONS
conversion_not_found = 1
division_by_zero = 2
input_invalid = 3
output_invalid = 4
overflow = 5
type_invalid = 6
units_missing = 7
unit_in_not_found = 8
unit_out_not_found = 9
OTHERS = 10.
IF sy-subrc = 0.
WRITE: / '1000 KG =', lv_quantity_to, 'TO'. " 1 TO
ENDIF.

6. Material-Specific Unit Conversion

DATA: lv_quantity_st TYPE menge VALUE '100', " 100 pieces
lv_quantity_kg TYPE menge.
" Conversion with material master
CALL FUNCTION 'MD_CONVERT_MATERIAL_UNIT'
EXPORTING
i_matnr = '000000000012345678'
i_in_me = 'ST' " From pieces
i_out_me = 'KG' " To kilograms
i_menge = lv_quantity_st
IMPORTING
e_menge = lv_quantity_kg
EXCEPTIONS
error_in_application = 1
error = 2
OTHERS = 3.
IF sy-subrc = 0.
WRITE: / '100 ST =', lv_quantity_kg, 'KG'.
ENDIF.

7. Currency Helper Class

CLASS zcl_currency_helper DEFINITION.
PUBLIC SECTION.
TYPES: BEGIN OF ty_conversion_result,
amount TYPE dmbtr,
currency TYPE waers,
rate TYPE ukurs,
END OF ty_conversion_result.
CLASS-METHODS: convert
IMPORTING iv_amount TYPE dmbtr
iv_from_currency TYPE waers
iv_to_currency TYPE waers
iv_date TYPE sy-datum DEFAULT sy-datum
iv_rate_type TYPE kurst DEFAULT 'M'
RETURNING VALUE(rs_result) TYPE ty_conversion_result
RAISING zcx_currency_error.
CLASS-METHODS: get_decimals
IMPORTING iv_currency TYPE waers
RETURNING VALUE(rv_decimals) TYPE i.
CLASS-METHODS: round_amount
IMPORTING iv_amount TYPE dmbtr
iv_currency TYPE waers
RETURNING VALUE(rv_amount) TYPE dmbtr.
CLASS-METHODS: to_internal
IMPORTING iv_amount TYPE dmbtr
iv_currency TYPE waers
RETURNING VALUE(rv_amount) TYPE dmbtr.
CLASS-METHODS: to_external
IMPORTING iv_amount TYPE dmbtr
iv_currency TYPE waers
RETURNING VALUE(rv_amount) TYPE dmbtr.
ENDCLASS.
CLASS zcl_currency_helper IMPLEMENTATION.
METHOD convert.
IF iv_from_currency = iv_to_currency.
rs_result-amount = iv_amount.
rs_result-currency = iv_to_currency.
rs_result-rate = 1.
RETURN.
ENDIF.
CALL FUNCTION 'CONVERT_TO_LOCAL_CURRENCY'
EXPORTING
date = iv_date
foreign_amount = iv_amount
foreign_currency = iv_from_currency
local_currency = iv_to_currency
type_of_rate = iv_rate_type
IMPORTING
local_amount = rs_result-amount
exchange_rate = rs_result-rate
EXCEPTIONS
no_rate_found = 1
OTHERS = 2.
IF sy-subrc <> 0.
RAISE EXCEPTION TYPE zcx_currency_error
EXPORTING
textid = zcx_currency_error=>rate_not_found
from_currency = iv_from_currency
to_currency = iv_to_currency.
ENDIF.
rs_result-currency = iv_to_currency.
ENDMETHOD.
METHOD get_decimals.
SELECT SINGLE currdec FROM tcurx
WHERE currkey = @iv_currency
INTO @rv_decimals.
IF sy-subrc <> 0.
rv_decimals = 2. " Default
ENDIF.
ENDMETHOD.
METHOD round_amount.
DATA(lv_decimals) = get_decimals( iv_currency ).
rv_amount = round(
val = iv_amount
dec = lv_decimals
mode = cl_abap_math=>round_half_up
).
ENDMETHOD.
METHOD to_internal.
CALL FUNCTION 'BAPI_CURRENCY_CONV_TO_INTERNAL'
EXPORTING
currency = iv_currency
amount_external = iv_amount
max_number_of_digits = 23
IMPORTING
amount_internal = rv_amount.
ENDMETHOD.
METHOD to_external.
CALL FUNCTION 'BAPI_CURRENCY_CONV_TO_EXTERNAL'
EXPORTING
currency = iv_currency
amount_internal = iv_amount
IMPORTING
amount_external = rv_amount.
ENDMETHOD.
ENDCLASS.
" Usage
TRY.
DATA(ls_result) = zcl_currency_helper=>convert(
iv_amount = '1000.00'
iv_from_currency = 'USD'
iv_to_currency = 'EUR'
).
WRITE: / '1000 USD =', ls_result-amount, 'EUR'.
WRITE: / 'Rate:', ls_result-rate.
CATCH zcx_currency_error INTO DATA(lx_error).
WRITE: / lx_error->get_text( ).
ENDTRY.

8. Unit Helper Class

CLASS zcl_unit_helper DEFINITION.
PUBLIC SECTION.
CLASS-METHODS: convert
IMPORTING iv_quantity TYPE menge
iv_from_unit TYPE meins
iv_to_unit TYPE meins
iv_matnr TYPE matnr OPTIONAL
RETURNING VALUE(rv_quantity) TYPE menge
RAISING zcx_unit_error.
CLASS-METHODS: get_base_unit
IMPORTING iv_matnr TYPE matnr
RETURNING VALUE(rv_unit) TYPE meins.
CLASS-METHODS: get_conversion_factor
IMPORTING iv_from_unit TYPE meins
iv_to_unit TYPE meins
RETURNING VALUE(rv_factor) TYPE f.
ENDCLASS.
CLASS zcl_unit_helper IMPLEMENTATION.
METHOD convert.
IF iv_from_unit = iv_to_unit.
rv_quantity = iv_quantity.
RETURN.
ENDIF.
" Material-specific if material is provided
IF iv_matnr IS NOT INITIAL.
CALL FUNCTION 'MD_CONVERT_MATERIAL_UNIT'
EXPORTING
i_matnr = iv_matnr
i_in_me = iv_from_unit
i_out_me = iv_to_unit
i_menge = iv_quantity
IMPORTING
e_menge = rv_quantity
EXCEPTIONS
error_in_application = 1
OTHERS = 2.
IF sy-subrc = 0.
RETURN.
ENDIF.
ENDIF.
" General conversion
CALL FUNCTION 'UNIT_CONVERSION_SIMPLE'
EXPORTING
input = iv_quantity
unit_in = iv_from_unit
unit_out = iv_to_unit
IMPORTING
output = rv_quantity
EXCEPTIONS
OTHERS = 1.
IF sy-subrc <> 0.
RAISE EXCEPTION TYPE zcx_unit_error
EXPORTING
textid = zcx_unit_error=>conversion_failed
from_unit = iv_from_unit
to_unit = iv_to_unit.
ENDIF.
ENDMETHOD.
METHOD get_base_unit.
SELECT SINGLE meins FROM mara
WHERE matnr = @iv_matnr
INTO @rv_unit.
ENDMETHOD.
METHOD get_conversion_factor.
DATA: lv_output TYPE menge.
CALL FUNCTION 'UNIT_CONVERSION_SIMPLE'
EXPORTING
input = CONV menge( 1 )
unit_in = iv_from_unit
unit_out = iv_to_unit
IMPORTING
output = lv_output
EXCEPTIONS
OTHERS = 1.
IF sy-subrc = 0.
rv_factor = lv_output.
ELSE.
rv_factor = 1.
ENDIF.
ENDMETHOD.
ENDCLASS.

9. Price Conversion

" Convert price per unit of measure
DATA: lv_price TYPE netpr VALUE '10.00', " 10 EUR per 100 ST
lv_per_unit TYPE i VALUE 100, " Per 100
lv_quantity TYPE menge VALUE '250',
lv_total TYPE netwr.
" Calculate total price
lv_total = lv_price * lv_quantity / lv_per_unit.
" 10.00 * 250 / 100 = 25.00 EUR
" With unit conversion
DATA: lv_price_kg TYPE netpr VALUE '2.50', " 2.50 EUR per KG
lv_qty_to TYPE menge VALUE '500', " 500 KG ordered
lv_total_to TYPE netwr.
" First convert to tons
DATA(lv_qty_converted) = zcl_unit_helper=>convert(
iv_quantity = lv_qty_to
iv_from_unit = 'KG'
iv_to_unit = 'TO'
).
" Calculate price for 1 TO
DATA(lv_price_to) = lv_price_kg * 1000. " 2500 EUR per TO
lv_total_to = lv_price_to * lv_qty_converted.

10. Maintain Exchange Rates

" Read exchange rates (TCURR)
SELECT * FROM tcurr
WHERE kurst = 'M' " Rate type
AND fcurr = 'USD' " From currency
AND tcurr = 'EUR' " To currency
AND gdatu <= @sy-datum " Valid from
ORDER BY gdatu DESCENDING
INTO TABLE @DATA(lt_rates)
UP TO 1 ROWS.
IF lt_rates IS NOT INITIAL.
DATA(ls_rate) = lt_rates[ 1 ].
WRITE: / 'Rate:', ls_rate-ukurs.
WRITE: / 'Valid from:', ls_rate-gdatu.
ENDIF.
" Change rate via BAPI (customizing)
DATA: lt_return TYPE TABLE OF bapiret2.
CALL FUNCTION 'BAPI_EXCHRATE_CREATEMULTIPLE'
TABLES
exchrate_list = lt_rates_new
return = lt_return.
IF NOT line_exists( lt_return[ type = 'E' ] ).
CALL FUNCTION 'BAPI_TRANSACTION_COMMIT'.
ENDIF.

11. Commercial Rounding

DATA: lv_amount TYPE p DECIMALS 4 VALUE '123.4567',
lv_rounded TYPE p DECIMALS 2.
" Mathematical rounding
lv_rounded = round( val = lv_amount dec = 2 ).
" 123.46
" Commercial rounding (half up)
lv_rounded = round(
val = lv_amount
dec = 2
mode = cl_abap_math=>round_half_up
).
" Round down
lv_rounded = floor( lv_amount * 100 ) / 100.
" 123.45
" Round up
lv_rounded = ceil( lv_amount * 100 ) / 100.
" 123.46
" With currency
DATA(lv_currency_amount) = zcl_currency_helper=>round_amount(
iv_amount = lv_amount
iv_currency = 'EUR'
).

12. Amounts in SQL

" Currency conversion in CDS View
@AbapCatalog.viewEnhancementCategory: [#NONE]
define view entity ZI_SalesAmount as select from vbak
{
key vbeln,
netwr,
waerk,
// Amount with decimal correction
@Semantics.amount.currencyCode: 'waerk'
netwr as Amount,
// In local currency (simplified)
@Semantics.amount.currencyCode: 'EUR'
cast(netwr as abap.curr(15,2)) as AmountEUR
}
" In Open SQL
SELECT vbeln, netwr, waerk,
CURR_TO_DECFLOAT_AMOUNT( netwr, waerk ) AS amount_decimal
FROM vbak
INTO TABLE @DATA(lt_sales).

13. ISO Codes

" SAP internal codes vs. ISO codes
DATA: lv_sap_currency TYPE waers VALUE 'EUR',
lv_iso_currency TYPE isocd.
" SAP -> ISO
SELECT SINGLE isocd FROM tcurc
WHERE waersion = @lv_sap_currency
INTO @lv_iso_currency.
" ISO -> SAP
SELECT SINGLE waers FROM tcurc
WHERE isocd = 'EUR'
INTO @lv_sap_currency.
" Units of measure
DATA: lv_sap_unit TYPE meins VALUE 'KG',
lv_iso_unit TYPE isocd_unit.
SELECT SINGLE isocode FROM t006
WHERE msehi = @lv_sap_unit
INTO @lv_iso_unit.

Important Tables

TableContent
TCURRExchange rates
TCURCCurrencies
TCURXDecimal places
T006Units of measure
T006AUnit texts
MARMMaterial units of measure

Important Notes / Best Practice

  • Decimal places always handle depending on currency.
  • BAPI_CURRENCY_CONV for SAP-compliant conversion.
  • Internal representation for DB, external for display.
  • Rounding according to commercial rules.
  • Rate type ‘M’ for standard conversion.
  • Material-specific for accurate quantity conversion.
  • ISO codes for external interfaces.
  • Semantics in CDS for correct representation.
  • Error handling for rates not found.
  • Combine with BAPI Development for APIs.