ABAP Function Modules : Creer et utiliser des modules de fonction

Catégorie
ABAP-Statements
Publié
Auteur
Johannes

Les Function Modules (modules de fonction) sont des routines ABAP reutilisables avec une interface definie. Ils sont organises en groupes de fonctions et peuvent etre compatibles RFC pour les appels distants.

Concept de base

ElementDescription
Groupe de fonctionsConteneur pour les Function Modules
IMPORTINGParametres d’entree
EXPORTINGParametres de sortie
CHANGINGParametres d’entree/sortie
TABLESParametres de table (obsolete)
EXCEPTIONSGestion des erreurs

Syntaxe

CALL FUNCTION 'FUNCTION_NAME"
EXPORTING
param1 = value1
IMPORTING
result = lv_result
CHANGING
data = ls_data
TABLES
itab = lt_table
EXCEPTIONS
error1 = 1
error2 = 2
OTHERS = 3.
IF sy-subrc <> 0.
" Gestion des erreurs
ENDIF.

Exemples

1. Appeler un Function Module

DATA: lv_date_out TYPE sy-datum.
" Calculer une date
CALL FUNCTION 'RP_CALC_DATE_IN_INTERVAL"
EXPORTING
date = sy-datum
days = 30
months = 0
signum = '+"
years = 0
IMPORTING
calc_date = lv_date_out.
WRITE: / 'Date + 30 jours:', lv_date_out.

2. Function Module avec Exceptions

DATA: lv_result TYPE p DECIMALS 2.
CALL FUNCTION 'CONVERSION_EXIT_ALPHA_INPUT"
EXPORTING
input = '123"
IMPORTING
output = lv_result
EXCEPTIONS
invalid_input = 1
OTHERS = 2.
CASE sy-subrc.
WHEN 0.
WRITE: / 'Resultat:', lv_result.
WHEN 1.
WRITE: / 'Entree invalide'.
WHEN OTHERS.
WRITE: / 'Erreur inconnue'.
ENDCASE.

3. Creer son propre Function Module

" Dans SE37 ou SE80:
" 1. Creer un groupe de fonctions : ZFGRP_CUSTOMER
" 2. Creer un module de fonction : Z_CUSTOMER_GET_BY_ID
FUNCTION z_customer_get_by_id.
*"----------------------------------------------------------------------
*"*"Interface locale:
*" IMPORTING
*" VALUE(IV_KUNNR) TYPE KUNNR
*" EXPORTING
*" VALUE(ES_CUSTOMER) TYPE KNA1
*" VALUE(EV_FOUND) TYPE ABAP_BOOL
*" EXCEPTIONS
*" CUSTOMER_NOT_FOUND
*" INVALID_INPUT
*"----------------------------------------------------------------------
" Verification de l'entree
IF iv_kunnr IS INITIAL.
RAISE invalid_input.
ENDIF.
" Lire le client
SELECT SINGLE * FROM kna1
WHERE kunnr = @iv_kunnr
INTO @es_customer.
IF sy-subrc = 0.
ev_found = abap_true.
ELSE.
ev_found = abap_false.
RAISE customer_not_found.
ENDIF.
ENDFUNCTION.
" Appel
DATA: ls_customer TYPE kna1,
lv_found TYPE abap_bool.
CALL FUNCTION 'Z_CUSTOMER_GET_BY_ID"
EXPORTING
iv_kunnr = '0000001000"
IMPORTING
es_customer = ls_customer
ev_found = lv_found
EXCEPTIONS
customer_not_found = 1
invalid_input = 2
OTHERS = 3.
IF sy-subrc = 0.
WRITE: / ls_customer-name1.
ENDIF.

4. Function Module compatible RFC

" Parametre dans SE37:
" Attributes -> Processing Type: Remote-Enabled Module
FUNCTION z_rfc_get_sales_data.
*"----------------------------------------------------------------------
*"*"Interface locale:
*" IMPORTING
*" VALUE(IV_VKORG) TYPE VKORG
*" VALUE(IV_DATE_FROM) TYPE SY-DATUM
*" VALUE(IV_DATE_TO) TYPE SY-DATUM
*" EXPORTING
*" VALUE(ET_ORDERS) TYPE ZTT_ORDERS
*" EXCEPTIONS
*" NO_DATA_FOUND
*" INVALID_DATE_RANGE
*"----------------------------------------------------------------------
" Verifier la plage de dates
IF iv_date_from > iv_date_to.
RAISE invalid_date_range.
ENDIF.
" Lire les donnees
SELECT vbeln, erdat, kunnr, netwr
FROM vbak
WHERE vkorg = @iv_vkorg
AND erdat BETWEEN @iv_date_from AND @iv_date_to
INTO CORRESPONDING FIELDS OF TABLE @et_orders.
IF sy-subrc <> 0.
RAISE no_data_found.
ENDIF.
ENDFUNCTION.
" Appel distant
CALL FUNCTION 'Z_RFC_GET_SALES_DATA"
DESTINATION 'PRD_100' " Destination RFC
EXPORTING
iv_vkorg = '1000"
iv_date_from = '20240101"
iv_date_to = '20241231"
IMPORTING
et_orders = lt_orders
EXCEPTIONS
no_data_found = 1
invalid_date_range = 2
communication_failure = 3 MESSAGE lv_msg
system_failure = 4 MESSAGE lv_msg
OTHERS = 5.
IF sy-subrc >= 3.
WRITE: / 'Erreur RFC:', lv_msg.
ENDIF.

5. Function Module avec parametre TABLES

" TABLES est obsolete mais encore souvent utilise
FUNCTION z_process_items.
*"----------------------------------------------------------------------
*"*"Interface locale:
*" TABLES
*" IT_ITEMS STRUCTURE ZITEM
*" ET_RESULTS STRUCTURE ZRESULT
*"----------------------------------------------------------------------
LOOP AT it_items.
" Traitement
et_results-id = it_items-id.
et_results-status = 'OK'.
APPEND et_results.
ENDLOOP.
ENDFUNCTION.
" Alternative moderne : TYPE pour les parametres de table
FUNCTION z_process_items_new.
*"----------------------------------------------------------------------
*" IMPORTING
*" VALUE(IT_ITEMS) TYPE ZTT_ITEMS
*" EXPORTING
*" VALUE(ET_RESULTS) TYPE ZTT_RESULTS
*"----------------------------------------------------------------------
LOOP AT it_items INTO DATA(ls_item).
APPEND VALUE #( id = ls_item-id status = 'OK' ) TO et_results.
ENDLOOP.
ENDFUNCTION.

6. Update Task Function Module

" Pour les modifications de base de donnees asynchrones
FUNCTION z_update_customer IN UPDATE TASK.
*"----------------------------------------------------------------------
*" IMPORTING
*" VALUE(IS_CUSTOMER) TYPE KNA1
*"----------------------------------------------------------------------
MODIFY kna1 FROM is_customer.
ENDFUNCTION.
" Appel
CALL FUNCTION 'Z_UPDATE_CUSTOMER' IN UPDATE TASK
EXPORTING
is_customer = ls_customer.
" Le commit execute la mise a jour
COMMIT WORK AND WAIT.

7. Function Module avec exceptions basees sur des classes

FUNCTION z_divide_numbers.
*"----------------------------------------------------------------------
*" IMPORTING
*" VALUE(IV_DIVIDEND) TYPE I
*" VALUE(IV_DIVISOR) TYPE I
*" EXPORTING
*" VALUE(EV_RESULT) TYPE P
*" RAISING
*" ZCX_DIVISION_BY_ZERO
*"----------------------------------------------------------------------
IF iv_divisor = 0.
RAISE EXCEPTION TYPE zcx_division_by_zero.
ENDIF.
ev_result = iv_dividend / iv_divisor.
ENDFUNCTION.
" Appel avec TRY-CATCH
TRY.
CALL FUNCTION 'Z_DIVIDE_NUMBERS"
EXPORTING
iv_dividend = 100
iv_divisor = 0
IMPORTING
ev_result = lv_result.
CATCH zcx_division_by_zero INTO DATA(lx_error).
WRITE: / lx_error->get_text( ).
ENDTRY.

8. Groupe de fonctions avec donnees globales

" Include TOP du groupe de fonctions (LZFGRP_CUSTOMERTOP)
FUNCTION-POOL zfgrp_customer.
DATA: gt_cache TYPE HASHED TABLE OF kna1 WITH UNIQUE KEY kunnr.
" Le Function Module utilise les donnees globales
FUNCTION z_customer_get_cached.
*"----------------------------------------------------------------------
*" IMPORTING
*" VALUE(IV_KUNNR) TYPE KUNNR
*" EXPORTING
*" VALUE(ES_CUSTOMER) TYPE KNA1
*"----------------------------------------------------------------------
" D'abord chercher dans le cache
READ TABLE gt_cache INTO es_customer
WITH KEY kunnr = iv_kunnr.
IF sy-subrc <> 0.
" Charger depuis la DB et mettre en cache
SELECT SINGLE * FROM kna1
WHERE kunnr = @iv_kunnr
INTO @es_customer.
IF sy-subrc = 0.
INSERT es_customer INTO TABLE gt_cache.
ENDIF.
ENDIF.
ENDFUNCTION.
" Vider le cache
FUNCTION z_customer_clear_cache.
CLEAR gt_cache.
ENDFUNCTION.

9. Appel dynamique de Function Module

DATA: lv_funcname TYPE funcname VALUE 'Z_CUSTOMER_GET_BY_ID',
lt_params TYPE abap_func_parmbind_tab,
lt_excps TYPE abap_func_excpbind_tab,
ls_customer TYPE kna1,
lv_kunnr TYPE kunnr VALUE '0000001000'.
" Construire les parametres
lt_params = VALUE #(
( name = 'IV_KUNNR"
kind = abap_func_exporting
value = REF #( lv_kunnr ) )
( name = 'ES_CUSTOMER"
kind = abap_func_importing
value = REF #( ls_customer ) )
).
lt_excps = VALUE #(
( name = 'CUSTOMER_NOT_FOUND' value = 1 )
( name = 'OTHERS' value = 99 )
).
" Appel dynamique
CALL FUNCTION lv_funcname
PARAMETER-TABLE lt_params
EXCEPTION-TABLE lt_excps.
IF sy-subrc = 0.
WRITE: / ls_customer-name1.
ENDIF.

10. Tester un Function Module (SE37)

" Dans SE37:
" 1. Ouvrir le module de fonction
" 2. Menu: Module de fonction -> Tester -> Test individuel
" 3. Saisir les parametres
" 4. F8 pour executer
" Ou par programmation avec Unit Test
CLASS ltcl_function_test DEFINITION FOR TESTING
DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
METHODS: test_customer_get FOR TESTING.
ENDCLASS.
CLASS ltcl_function_test IMPLEMENTATION.
METHOD test_customer_get.
DATA: ls_customer TYPE kna1,
lv_found TYPE abap_bool.
CALL FUNCTION 'Z_CUSTOMER_GET_BY_ID"
EXPORTING
iv_kunnr = '0000001000"
IMPORTING
es_customer = ls_customer
ev_found = lv_found
EXCEPTIONS
OTHERS = 1.
cl_abap_unit_assert=>assert_equals(
exp = 0
act = sy-subrc
msg = 'La fonction devrait reussir"
).
cl_abap_unit_assert=>assert_true(
act = lv_found
msg = 'Le client devrait etre trouve"
).
ENDMETHOD.
ENDCLASS.

11. Documentation des Function Modules

" Dans SE37:
" Menu: Saut -> Documentation
" Documentation structuree:
" - Description courte
" - Documentation des parametres
" - Exemple d'appel
" - Explications des exceptions
" Documentation dans le code (pour les developpeurs)
FUNCTION z_well_documented.
*"----------------------------------------------------------------------
*" Description:
*" Calcule le prix total TTC
*"
*" Parametres:
*" IV_AMOUNT - Montant net
*" IV_TAX_RATE - Taux de taxe en pourcentage (ex. 19)
*" EV_TOTAL - Montant brut
*"
*" Exceptions:
*" INVALID_AMOUNT - Le montant est negatif
*" INVALID_TAX_RATE - Le taux de taxe est hors de 0-100
*"
*" Exemple:
*" CALL FUNCTION 'Z_WELL_DOCUMENTED"
*" EXPORTING iv_amount = 100 iv_tax_rate = 19
*" IMPORTING ev_total = lv_total.
*" " lv_total = 119.00
*"----------------------------------------------------------------------
" Implementation...
ENDFUNCTION.

12. Conversion Exits

" Function Modules speciaux pour le formatage
" Convention de nommage: CONVERSION_EXIT_<NAME>_INPUT/OUTPUT
FUNCTION conversion_exit_alpha_input.
*" Ajouter des zeros en tete
*" IMPORTING VALUE(INPUT)
*" EXPORTING VALUE(OUTPUT)
CALL FUNCTION 'CONVERSION_EXIT_ALPHA_INPUT"
EXPORTING input = '1000"
IMPORTING output = lv_kunnr.
" lv_kunnr = '0000001000"
ENDFUNCTION.
FUNCTION conversion_exit_alpha_output.
*" Supprimer les zeros en tete
CALL FUNCTION 'CONVERSION_EXIT_ALPHA_OUTPUT"
EXPORTING input = '0000001000"
IMPORTING output = lv_display.
" lv_display = '1000"
ENDFUNCTION.

13. Popup Function Modules

" Popup Oui/Non
DATA: lv_answer TYPE c LENGTH 1.
CALL FUNCTION 'POPUP_TO_CONFIRM"
EXPORTING
titlebar = 'Confirmation"
text_question = 'Voulez-vous continuer ?"
text_button_1 = 'Oui"
text_button_2 = 'Non"
default_button = '2"
display_cancel_button = abap_false
IMPORTING
answer = lv_answer.
IF lv_answer = '1'.
" Oui choisi
ENDIF.
" Saisie de texte
DATA: lv_input TYPE string.
CALL FUNCTION 'POPUP_GET_VALUES"
EXPORTING
popup_title = 'Saisie"
TABLES
fields = lt_fields.
" Afficher un message
CALL FUNCTION 'POPUP_TO_DISPLAY_TEXT"
EXPORTING
titel = 'Information"
textline1 = 'Traitement termine."
textline2 = '100 enregistrements traites.'.

14. Traitement en arriere-plan

" Function Module pour job d'arriere-plan
FUNCTION z_batch_process.
*"----------------------------------------------------------------------
*" IMPORTING
*" VALUE(IV_DATE) TYPE SY-DATUM
*"----------------------------------------------------------------------
" Peut etre execute en job d'arriere-plan
" Pas d'appels de dialogue !
SELECT * FROM vbak
WHERE erdat = @iv_date
INTO TABLE @DATA(lt_orders).
LOOP AT lt_orders INTO DATA(ls_order).
" Traitement sans interaction utilisateur
ENDLOOP.
ENDFUNCTION.
" Planifier comme job
SUBMIT zreport_calling_function
VIA JOB lv_jobname NUMBER lv_jobcount
AND RETURN.

15. Classe wrapper pour Function Modules

CLASS zcl_function_wrapper DEFINITION.
PUBLIC SECTION.
" Wrappers type-safe pour les FuBas frequemment utilises
CLASS-METHODS: convert_to_internal
IMPORTING iv_external TYPE clike
RETURNING VALUE(rv_internal) TYPE kunnr.
CLASS-METHODS: convert_to_external
IMPORTING iv_internal TYPE kunnr
RETURNING VALUE(rv_external) TYPE string.
CLASS-METHODS: calculate_date
IMPORTING iv_date TYPE sy-datum
iv_days TYPE i DEFAULT 0
iv_months TYPE i DEFAULT 0
iv_years TYPE i DEFAULT 0
RETURNING VALUE(rv_date) TYPE sy-datum.
ENDCLASS.
CLASS zcl_function_wrapper IMPLEMENTATION.
METHOD convert_to_internal.
CALL FUNCTION 'CONVERSION_EXIT_ALPHA_INPUT"
EXPORTING input = iv_external
IMPORTING output = rv_internal.
ENDMETHOD.
METHOD convert_to_external.
CALL FUNCTION 'CONVERSION_EXIT_ALPHA_OUTPUT"
EXPORTING input = iv_internal
IMPORTING output = rv_external.
ENDMETHOD.
METHOD calculate_date.
DATA: lv_sign TYPE c LENGTH 1.
" Positif ou negatif
IF iv_days >= 0 AND iv_months >= 0 AND iv_years >= 0.
lv_sign = '+'.
ELSE.
lv_sign = '-'.
ENDIF.
CALL FUNCTION 'RP_CALC_DATE_IN_INTERVAL"
EXPORTING
date = iv_date
days = abs( iv_days )
months = abs( iv_months )
signum = lv_sign
years = abs( iv_years )
IMPORTING
calc_date = rv_date.
ENDMETHOD.
ENDCLASS.
" Utilisation
DATA(lv_kunnr) = zcl_function_wrapper=>convert_to_internal( '1000' ).
DATA(lv_next_month) = zcl_function_wrapper=>calculate_date(
iv_date = sy-datum
iv_months = 1
).

Transactions importantes

TransactionDescription
SE37Function Builder
SE80Repository Browser
SM59Destinations RFC

Conseils importants / Bonnes pratiques

  • Organiser les groupes de fonctions logiquement par theme.
  • VALUE() pour les parametres IMPORTING (par valeur).
  • Compatible RFC uniquement si vraiment necessaire (performance).
  • Eviter TABLES - utiliser TYPE pour les tables.
  • Definir des exceptions pour tous les cas d’erreur.
  • Maintenir la documentation dans SE37.
  • Utiliser les donnees globales dans le groupe de fonctions pour le cache.
  • Exceptions basees sur des classes pour l’integration OO.
  • Pas de dialogues dans les modules compatibles RFC.
  • Combiner avec BAPI Development pour les APIs.