RAP Actions et Functions : Implementer la logique metier

Catégorie
RAP
Publié
Auteur
Johannes

Actions et Functions sont les moyens dans RAP pour implementer votre propre logique metier au-dela des operations CRUD standard. Cet article explique les differences et montre quand utiliser quel concept.

Action vs. Function : La difference

AspectActionFunction
ButModifie les donnees (effets de bord)Lit/calcule les donnees (pas d’effets de bord)
Methode HTTPPOSTGET
TransactionnelOui, fait partie de la transactionNon, lecture seule
IdempotentNonOui
ExemplesAnnuler, Approuver, CopierCalculer le prix, Verifier la disponibilite

Quand Action, quand Function ?

  • Action : Quand l’operation modifie l’etat de l’entite ou du systeme
  • Function : Quand on calcule seulement une valeur ou on interroge une information, sans modifier de donnees

Bound vs. Unbound (Static)

AspectBound (Instance)Unbound (Static)
ContexteLie a une instanceSans contexte d’instance
AppelSur la ligne selectionneeVia un bouton dans la barre d’outils
ClesCles de l’instance disponiblesPas de cles automatiques
Cas d’utilisation”Annuler ce voyage""Approuver tous les voyages ouverts”

Instance-bound Action

Une Instance-bound Action est executee sur une instance concrete :

Behavior Definition

managed implementation in class zbp_i_travel unique;
strict ( 2 );
define behavior for ZI_Travel alias Travel
persistent table ztravel
lock master
authorization master ( instance )
{
// Instance-bound Action sans parametre
action cancel result [1] $self;
// Instance-bound Action avec resultat
action accept result [1] $self;
// Instance-bound Action qui retourne plusieurs instances
action copyTravel result [0..*] $self;
}

Implementation

CLASS lhc_travel DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.
METHODS cancel FOR MODIFY
IMPORTING keys FOR ACTION Travel~cancel RESULT result.
METHODS accept FOR MODIFY
IMPORTING keys FOR ACTION Travel~accept RESULT result.
ENDCLASS.
CLASS lhc_travel IMPLEMENTATION.
METHOD cancel.
" Lire les donnees actuelles
READ ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
FIELDS ( TravelID Status )
WITH CORRESPONDING #( keys )
RESULT DATA(travels).
" Seuls les voyages ouverts peuvent etre annules
LOOP AT travels ASSIGNING FIELD-SYMBOL(<travel>)
WHERE Status = 'O'.
" Definir le statut sur Cancelled
MODIFY ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
UPDATE FIELDS ( Status )
WITH VALUE #( (
%tky = <travel>-%tky
Status = 'X' " Cancelled
) ).
ENDLOOP.
" Retourner le resultat
READ ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
ALL FIELDS
WITH CORRESPONDING #( keys )
RESULT DATA(travels_result).
result = VALUE #( FOR travel IN travels_result (
%tky = travel-%tky
%param = travel
) ).
ENDMETHOD.
METHOD accept.
" Definir le statut sur Accepted
MODIFY ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
UPDATE FIELDS ( Status )
WITH VALUE #( FOR key IN keys (
%tky = key-%tky
Status = 'A' " Accepted
) ).
" Retourner les donnees mises a jour
READ ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
ALL FIELDS
WITH CORRESPONDING #( keys )
RESULT DATA(travels).
result = VALUE #( FOR travel IN travels (
%tky = travel-%tky
%param = travel
) ).
ENDMETHOD.
ENDCLASS.

Cardinalite du resultat

CardinaliteSignification
result [1] $selfExactement une instance en retour (la meme entite)
result [0..1] $selfOptionnellement une instance
result [0..*] $selfNombre quelconque d’instances
result [1] EntityNameUne instance d’une autre entite
(pas de result)Pas de retour

Static (Unbound) Action

Les Static Actions ne sont pas liees a une instance et sont typiquement utilisees pour les operations de masse :

Behavior Definition

define behavior for ZI_Travel alias Travel
{
// Static Action sans parametre
static action releaseAllOpen;
// Static Action avec resultat
static action createFromTemplate result [1] $self;
// Static Action avec Global Feature Control
static action ( features: global ) massUpdate;
// Pour Global Feature Control
static features;
}

Implementation

CLASS lhc_travel DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.
METHODS releaseAllOpen FOR MODIFY
IMPORTING keys FOR ACTION Travel~releaseAllOpen.
METHODS createFromTemplate FOR MODIFY
IMPORTING keys FOR ACTION Travel~createFromTemplate RESULT result.
METHODS get_global_features FOR GLOBAL FEATURES
IMPORTING REQUEST requested_features FOR Travel RESULT result.
ENDCLASS.
CLASS lhc_travel IMPLEMENTATION.
METHOD releaseAllOpen.
" Trouver tous les voyages ouverts
SELECT * FROM ztravel
WHERE status = 'O"
INTO TABLE @DATA(open_travels).
" Definir le statut sur Released
MODIFY ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
UPDATE FIELDS ( Status )
WITH VALUE #( FOR travel IN open_travels (
TravelUUID = travel-travel_uuid
Status = 'R' " Released
) ).
ENDMETHOD.
METHOD createFromTemplate.
" Creer un nouveau voyage a partir du template
DATA(template_id) = 'DEFAULT'.
" Charger le template (exemple simplifie)
SELECT SINGLE * FROM ztravel_template
WHERE template_id = @template_id
INTO @DATA(template).
" Creer le nouveau voyage
MODIFY ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
CREATE FIELDS ( CustomerID BeginDate EndDate Description )
WITH VALUE #( (
%cid = 'NEW_TRAVEL"
CustomerID = template-customer_id
BeginDate = cl_abap_context_info=>get_system_date( )
EndDate = cl_abap_context_info=>get_system_date( ) + 7
Description = 'Cree a partir du template"
) )
MAPPED DATA(mapped).
" Retourner l'entite creee
READ ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
ALL FIELDS
WITH VALUE #( ( %tky = mapped-travel[ 1 ]-%tky ) )
RESULT DATA(created_travel).
result = VALUE #( (
%cid_ref = 'NEW_TRAVEL"
%param = created_travel[ 1 ]
) ).
ENDMETHOD.
METHOD get_global_features.
" Verifier si l'utilisateur a l'autorisation pour la mise a jour de masse
DATA(has_auth) = check_mass_update_auth( ).
result = VALUE #(
%action-massUpdate = COND #(
WHEN has_auth = abap_true
THEN if_abap_behv=>fc-o-enabled
ELSE if_abap_behv=>fc-o-disabled )
).
ENDMETHOD.
ENDCLASS.

Function : Calculs sans effets de bord

Les Functions sont ideales pour les calculs et les requetes qui ne modifient pas de donnees :

Behavior Definition

define behavior for ZI_Travel alias Travel
{
// Instance-bound Function
function calculatePrice result [1] ZA_Price;
// Function avec plusieurs valeurs de retour
function getAvailability result [1] ZA_Availability;
// Static Function
static function getExchangeRates result [0..*] ZA_ExchangeRate;
// Function avec parametres
function simulateDiscount
parameter ZA_DiscountInput
result [1] ZA_DiscountResult;
}

Abstract Entity pour les valeurs de retour

Les Functions necessitent souvent des entites abstraites pour les valeurs de retour structurees :

define abstract entity ZA_Price
{
TravelUUID : sysuuid_x16;
NetPrice : abap.dec( 15, 2 );
TaxAmount : abap.dec( 15, 2 );
TotalPrice : abap.dec( 15, 2 );
Currency : abap.cuky;
}
define abstract entity ZA_Availability
{
TravelUUID : sysuuid_x16;
IsAvailable : abap_boolean;
SeatsLeft : abap.int4;
NextAvailable: abap.dats;
}

Implementation

CLASS lhc_travel DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.
METHODS calculatePrice FOR READ
IMPORTING keys FOR FUNCTION Travel~calculatePrice RESULT result.
METHODS getAvailability FOR READ
IMPORTING keys FOR FUNCTION Travel~getAvailability RESULT result.
METHODS getExchangeRates FOR READ
IMPORTING keys FOR FUNCTION Travel~getExchangeRates RESULT result.
ENDCLASS.
CLASS lhc_travel IMPLEMENTATION.
METHOD calculatePrice.
" Lire les donnees du voyage
READ ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
FIELDS ( TotalPrice Currency )
WITH CORRESPONDING #( keys )
RESULT DATA(travels).
LOOP AT travels ASSIGNING FIELD-SYMBOL(<travel>).
" Calculer le prix (simplifie)
DATA(net_price) = <travel>-TotalPrice * '0.84'.
DATA(tax_amount) = <travel>-TotalPrice * '0.16'.
APPEND VALUE #(
%tky = <travel>-%tky
%param = VALUE za_price(
TravelUUID = <travel>-TravelUUID
NetPrice = net_price
TaxAmount = tax_amount
TotalPrice = <travel>-TotalPrice
Currency = <travel>-Currency
)
) TO result.
ENDLOOP.
ENDMETHOD.
METHOD getAvailability.
READ ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
FIELDS ( BeginDate MaxSeats BookedSeats )
WITH CORRESPONDING #( keys )
RESULT DATA(travels).
LOOP AT travels ASSIGNING FIELD-SYMBOL(<travel>).
DATA(seats_left) = <travel>-MaxSeats - <travel>-BookedSeats.
APPEND VALUE #(
%tky = <travel>-%tky
%param = VALUE za_availability(
TravelUUID = <travel>-TravelUUID
IsAvailable = COND #( WHEN seats_left > 0 THEN abap_true ELSE abap_false )
SeatsLeft = seats_left
NextAvailable = COND #( WHEN seats_left = 0
THEN <travel>-BeginDate + 30
ELSE <travel>-BeginDate )
)
) TO result.
ENDLOOP.
ENDMETHOD.
METHOD getExchangeRates.
" Static Function - pas de keys disponibles
" Lire les taux de change du systeme
SELECT * FROM tcurr
WHERE kurst = 'M"
AND gdatu >= @( cl_abap_context_info=>get_system_date( ) )
INTO TABLE @DATA(rates)
UP TO 100 ROWS.
result = VALUE #( FOR rate IN rates (
%param = VALUE za_exchangerate(
SourceCurrency = rate-fcurr
TargetCurrency = rate-tcurr
ExchangeRate = rate-ukurs
ValidFrom = rate-gdatu
)
) ).
ENDMETHOD.
ENDCLASS.

Action avec parametres d’entree

Les Actions peuvent accepter des parametres d’entree structures :

Behavior Definition

define behavior for ZI_Travel alias Travel
{
// Action avec parametre abstrait
action rejectWithReason
parameter ZA_RejectionReason
result [1] $self;
// Action avec plusieurs parametres
action reschedule
parameter ZA_RescheduleInput
result [1] $self;
// Action avec Deep Structure
action addParticipants
parameter ZA_ParticipantList
result [1] $self;
}

Abstract Entity pour les parametres d’entree

define abstract entity ZA_RejectionReason
{
ReasonCode : abap.char( 2 );
ReasonText : abap.string( 255 );
NotifyCustomer : abap_boolean;
}
define abstract entity ZA_RescheduleInput
{
NewBeginDate : abap.dats;
NewEndDate : abap.dats;
UpdatePrice : abap_boolean;
}

Implementation

CLASS lhc_travel DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.
METHODS rejectWithReason FOR MODIFY
IMPORTING keys FOR ACTION Travel~rejectWithReason RESULT result.
METHODS reschedule FOR MODIFY
IMPORTING keys FOR ACTION Travel~reschedule RESULT result.
ENDCLASS.
CLASS lhc_travel IMPLEMENTATION.
METHOD rejectWithReason.
" Lire le parametre depuis keys
LOOP AT keys ASSIGNING FIELD-SYMBOL(<key>).
" Raison du rejet depuis le parametre
DATA(reason_code) = <key>-%param-ReasonCode.
DATA(reason_text) = <key>-%param-ReasonText.
DATA(notify) = <key>-%param-NotifyCustomer.
" Sauvegarder le statut et la justification
MODIFY ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
UPDATE FIELDS ( Status RejectionReason )
WITH VALUE #( (
%tky = <key>-%tky
Status = 'X' " Rejected
RejectionReason = reason_text
) ).
" Optionnel : Notifier le client
IF notify = abap_true.
" Envoyer un email...
send_rejection_notification(
travel_key = <key>-%tky
reason = reason_text
).
ENDIF.
ENDLOOP.
" Retourner les donnees mises a jour
READ ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
ALL FIELDS
WITH CORRESPONDING #( keys )
RESULT DATA(travels).
result = VALUE #( FOR travel IN travels (
%tky = travel-%tky
%param = travel
) ).
ENDMETHOD.
METHOD reschedule.
LOOP AT keys ASSIGNING FIELD-SYMBOL(<key>).
" Nouvelles dates depuis le parametre
DATA(new_begin) = <key>-%param-NewBeginDate.
DATA(new_end) = <key>-%param-NewEndDate.
DATA(update_price) = <key>-%param-UpdatePrice.
" Mettre a jour les dates
MODIFY ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
UPDATE FIELDS ( BeginDate EndDate )
WITH VALUE #( (
%tky = <key>-%tky
BeginDate = new_begin
EndDate = new_end
) ).
" Optionnel : Recalculer le prix
IF update_price = abap_true.
recalculate_price( <key>-%tky ).
ENDIF.
ENDLOOP.
" Retourner le resultat
READ ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
ALL FIELDS
WITH CORRESPONDING #( keys )
RESULT DATA(travels).
result = VALUE #( FOR travel IN travels (
%tky = travel-%tky
%param = travel
) ).
ENDMETHOD.
ENDCLASS.

Actions dans la Projection

Les Actions doivent etre exposees dans la Projection :

projection implementation in class zbp_c_travel unique;
strict ( 2 );
define behavior for ZC_Travel alias Travel
{
use action cancel;
use action accept;
use action rejectWithReason;
use action reschedule;
use function calculatePrice;
use function getAvailability;
}

Integration UI avec Fiori Elements

Placement des boutons

define root view entity ZC_Travel
provider contract transactional_query
as projection on ZI_Travel
{
@UI.lineItem: [
{ position: 10 },
{ type: #FOR_ACTION, dataAction: 'cancel', label: 'Annuler' },
{ type: #FOR_ACTION, dataAction: 'accept', label: 'Approuver' }
]
@UI.identification: [
{ position: 10 },
{ type: #FOR_ACTION, dataAction: 'rejectWithReason', label: 'Rejeter' }
]
key TravelUUID,
// Header Actions
@UI.fieldGroup: [{ qualifier: 'Actions',
type: #FOR_ACTION, dataAction: 'reschedule', label: 'Replanifier' }]
Status
}

Action avec dialogue

Pour les Actions avec des parametres d’entree, un dialogue est automatiquement genere :

define abstract entity ZA_RejectionReason
{
@UI.defaultValue: 'OT"
@Consumption.valueHelpDefinition: [{ entity: { name: 'ZI_ReasonCode', element: 'Code' }}]
ReasonCode : abap.char( 2 );
@UI.multiLineText: true
ReasonText : abap.string( 255 );
@UI.defaultValue: 'true"
NotifyCustomer : abap_boolean;
}

Factory Actions

Les Factory Actions creent de nouvelles instances basees sur des instances existantes :

define behavior for ZI_Travel alias Travel
{
// Factory Action cree une nouvelle instance
factory action copyTravel [1];
// Factory Action avec parametre
factory action createFromBooking parameter ZA_BookingRef [1];
}

Implementation

METHOD copyTravel.
" Lire les donnees originales
READ ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
ALL FIELDS
WITH CORRESPONDING #( keys )
RESULT DATA(travels).
LOOP AT travels ASSIGNING FIELD-SYMBOL(<travel>).
" Creer une copie avec un nouveau %cid
MODIFY ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
CREATE FIELDS ( CustomerID BeginDate EndDate Description TotalPrice Currency )
WITH VALUE #( (
%cid = |COPY_{ sy-index }|
CustomerID = <travel>-CustomerID
BeginDate = <travel>-BeginDate
EndDate = <travel>-EndDate
Description = |Copie de { <travel>-TravelID }|
TotalPrice = <travel>-TotalPrice
Currency = <travel>-Currency
) )
MAPPED DATA(mapped).
" mapped contient %cid et %tky de la nouvelle instance
APPEND VALUE #(
%cid_ref = keys[ sy-index ]-%cid_ref
%key = mapped-travel[ 1 ]-%key
) TO mapped-travel.
ENDLOOP.
ENDMETHOD.

Bonnes pratiques

  1. Choisir correctement Action vs. Function : Actions pour les modifications, Functions pour les calculs.

  2. Definir le Result Type : Specifiez toujours result [n] $self pour que l’UI se mette a jour.

  3. Utiliser IN LOCAL MODE : Contourner les verifications d’autorisation pour les appels internes.

  4. Error Handling : Retourner les erreurs via FAILED et REPORTED :

METHOD cancel.
" Validation
IF travel-Status <> 'O'.
APPEND VALUE #(
%tky = travel-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = 'Seuls les voyages ouverts peuvent etre annules"
)
) TO reported-travel.
APPEND VALUE #( %tky = travel-%tky ) TO failed-travel.
RETURN.
ENDIF.
ENDMETHOD.
  1. Combiner Feature Control : Desactivez les Actions quand elles ne sont pas applicables.

  2. Coherence transactionnelle : Toutes les modifications sont commitees seulement au SAVE.

Sujets connexes