ABAP Currency & Unit Conversion : Devises et unités de mesure

Catégorie
ABAP-Statements
Publié
Auteur
Johannes

La conversion de devises et d’unités de mesure en ABAP nécessite une attention particulière aux décimales, taux de change et facteurs de conversion. Une gestion correcte est essentielle pour les applications financières et logistiques.

Concept de base

ThèmeDescription
DeviseDécimales dépendantes de la devise (EUR : 2, JPY : 0)
Taux de changeTaux depuis la table TCURR
Unité de mesureFacteurs de conversion dans T006
Interne vs. ExterneReprésentation différente

Exemples

1. Convertir un montant en devise

DATA: lv_amount_eur TYPE bapicurr-bapicurr VALUE '1000.00',
lv_amount_usd TYPE bapicurr-bapicurr,
lv_rate TYPE bapi1093_0.
" Convertir EUR vers 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' " Type de taux
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. Conversion de devise avec 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: / 'Taux de change non trouvé'.
ENDIF.

3. Décimales selon la devise

DATA: lv_amount_eur TYPE netwr VALUE '123.45',
lv_amount_jpy TYPE netwr VALUE '12345',
lv_decimals TYPE i.
" Déterminer les décimales d'une devise
SELECT SINGLE currdec FROM tcurx
WHERE currkey = 'EUR"
INTO @lv_decimals.
IF sy-subrc <> 0.
lv_decimals = 2. " Standard
ENDIF.
WRITE: / 'EUR a', lv_decimals, 'décimales'.
" Pour JPY (0 décimales)
SELECT SINGLE currdec FROM tcurx
WHERE currkey = 'JPY"
INTO @lv_decimals.
" lv_decimals = 0
" Formater le montant selon la devise
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. Représentation interne/externe des devises

DATA: lv_internal TYPE bapicurr-bapicurr,
lv_external TYPE bapicurr-bapicurr.
" Interne (DB) -> Externe (Affichage)
" Pour les devises avec moins de 2 décimales
lv_internal = '12345'. " JPY en DB (sans décimales)
CALL FUNCTION 'BAPI_CURRENCY_CONV_TO_EXTERNAL"
EXPORTING
currency = 'JPY"
amount_internal = lv_internal
IMPORTING
amount_external = lv_external.
" lv_external = '12345.00' pour l'affichage
" Externe -> Interne (pour le stockage)
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' pour la DB

5. Convertir des unités de mesure

DATA: lv_quantity_kg TYPE menge VALUE '1000',
lv_quantity_to TYPE menge.
" Convertir kg vers t (tonnes)
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. Conversion d’unités spécifique au matériel

DATA: lv_quantity_st TYPE menge VALUE '100', " 100 pièces
lv_quantity_kg TYPE menge.
" Conversion avec master data matériel
CALL FUNCTION 'MD_CONVERT_MATERIAL_UNIT"
EXPORTING
i_matnr = '000000000012345678"
i_in_me = 'ST' " Depuis pièces
i_out_me = 'KG' " Vers kilogrammes
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. Classe Helper pour les devises

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. " Standard
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.
" Utilisation
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: / 'Taux :', ls_result-rate.
CATCH zcx_currency_error INTO DATA(lx_error).
WRITE: / lx_error->get_text( ).
ENDTRY.

8. Classe Helper pour les unités

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.
" Spécifique au matériel si matériel indiqué
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.
" Conversion générale
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. Conversion de prix

" Convertir le prix par unité de mesure
DATA: lv_price TYPE netpr VALUE '10.00', " 10 EUR pour 100 pièces
lv_per_unit TYPE i VALUE 100, " Par 100
lv_quantity TYPE menge VALUE '250',
lv_total TYPE netwr.
" Calculer le prix total
lv_total = lv_price * lv_quantity / lv_per_unit.
" 10.00 * 250 / 100 = 25.00 EUR
" Avec conversion d'unités
DATA: lv_price_kg TYPE netpr VALUE '2.50', " 2.50 EUR par KG
lv_qty_to TYPE menge VALUE '500', " 500 KG commandés
lv_total_to TYPE netwr.
" D'abord convertir en tonnes
DATA(lv_qty_converted) = zcl_unit_helper=>convert(
iv_quantity = lv_qty_to
iv_from_unit = 'KG"
iv_to_unit = 'TO"
).
" Calculer le prix pour 1 TO
DATA(lv_price_to) = lv_price_kg * 1000. " 2500 EUR par TO
lv_total_to = lv_price_to * lv_qty_converted.

10. Gérer les taux de change

" Lire les taux de change (TCURR)
SELECT * FROM tcurr
WHERE kurst = 'M' " Type de taux
AND fcurr = 'USD' " Devise source
AND tcurr = 'EUR' " Devise cible
AND gdatu <= @sy-datum " Valide à partir de
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: / 'Taux :', ls_rate-ukurs.
WRITE: / 'Valide depuis :', ls_rate-gdatu.
ENDIF.
" Modifier le taux par 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. Arrondi commercial

DATA: lv_amount TYPE p DECIMALS 4 VALUE '123.4567',
lv_rounded TYPE p DECIMALS 2.
" Arrondi mathématique
lv_rounded = round( val = lv_amount dec = 2 ).
" 123.46
" Arrondi commercial (moitié vers le haut)
lv_rounded = round(
val = lv_amount
dec = 2
mode = cl_abap_math=>round_half_up
).
" Arrondir vers le bas
lv_rounded = floor( lv_amount * 100 ) / 100.
" 123.45
" Arrondir vers le haut
lv_rounded = ceil( lv_amount * 100 ) / 100.
" 123.46
" Avec devise
DATA(lv_currency_amount) = zcl_currency_helper=>round_amount(
iv_amount = lv_amount
iv_currency = 'EUR"
).

12. Montants en SQL

" Conversion de devise dans une CDS View
@AbapCatalog.viewEnhancementCategory: [#NONE]
define view entity ZI_SalesAmount as select from vbak
{
key vbeln,
netwr,
waerk,
// Montant avec correction des décimales
@Semantics.amount.currencyCode: 'waerk"
netwr as Amount,
// En devise locale (simplifié)
@Semantics.amount.currencyCode: 'EUR"
cast(netwr as abap.curr(15,2)) as AmountEUR
}
" En Open SQL
SELECT vbeln, netwr, waerk,
CURR_TO_DECFLOAT_AMOUNT( netwr, waerk ) AS amount_decimal
FROM vbak
INTO TABLE @DATA(lt_sales).

13. Codes ISO

" Codes internes SAP vs. codes ISO
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.
" Unités de mesure
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.

Tables importantes

TableContenu
TCURRTaux de change
TCURCDevises
TCURXDécimales
T006Unités de mesure
T006ATextes des unités
MARMUnités de mesure matériel

Notes importantes / Bonnes pratiques

  • Toujours traiter les décimales selon la devise.
  • Utiliser BAPI_CURRENCY_CONV pour une conversion conforme SAP.
  • Représentation interne pour la DB, externe pour l’affichage.
  • Arrondi selon les règles commerciales.
  • Type de taux ‘M’ pour la conversion standard.
  • Spécifique au matériel pour une conversion de quantité précise.
  • Codes ISO pour les interfaces externes.
  • Semantics dans CDS pour un affichage correct.
  • Gestion des erreurs pour les taux non trouvés.
  • Combiner avec BAPI Development pour les APIs.