Cross Business Object dans RAP

Traitement en cours…
Terminé ! Vous figurez dans la liste.

Lorsque nous développons un nouveau business objet RAP ou une extension à un objet RAP existant (Actions/Validations/Determinations entre autres), nous avons parfois le besoin et l’envie d’interagir avec d’autres business objects RAP déjà existants.

Ces interactions sont :

  • Lire les données d’une entité d’un Business Object existant, depuis un autre business object
  • Créer/modifier une entité d’un Business Object existant, depuis un autre business object
  • Supprimer une entité d’un Business Object existant, depuis un autre business object

Pour effectuer ces actions au sein de notre Business Object (celui qui appelle l’autre Bussiness Object avec lequel nous souhaitons interagir), nous utilisons l’EML, langage utilisé pour interagir avec un Business Object RAP.

Pour effectuer un Cross Business Object (BO) il y a 3 méthodes selon moi :

  • Via le développement d’une action dans l’objet RAP

Ainsi, en appelant l’action, le code EML permettant le cross BO va être exécuté dans le code du Behavior Implementation

  • Via le développement d’une détermination dans l’objet RAP

Si nous ne souhaitons pas appeler d’action particulière en dehors des opération CRUD standard, et que nous souhaitons faire un cross BO à la création d’une entité par exemple, la solution peut être de créer une détermination au sein de laquelle nous développerons un appel EML qui permettra le Cross BO

Dans ce cas, lors de la phase de sauvegarde, venir faire tous les appels aux autres objets RAP via bgPF. Nous n’aborderons pas ce point ici.

Nous allons désormais voir un exemple en détails.

Tout le code est disponible sur notre Gitlab, ici.

I. Cas d’étude

Pour analyser ce phénomène, nous allons créer un business object spécifique représentant une équipe de football.

@EndUserText.label : 'Football team'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table zfootball_team {

  key client         : abap.clnt not null;
  key uuid           : sysuuid_x16 not null;
  name               : abap.char(50);
  surname            : abap.char(50);
  field_position     : abap.char(50);
  customer_id        : /dmo/customer_id;
  local_last_changed : abp_locinst_lastchange_tstmpl;
  created_by         : abp_creation_user;
  created_at         : abp_creation_tstmpl;
  change_by          : abp_lastchange_user;
  change_at          : abp_lastchange_tstmpl;

}

Chaque joueur de l’équipe sera relié, à sa création, à un numéro client en lien avec le scénario de démo de réservation de vol mis à disposition par SAP disponible dans la table /DMO/I_CUSTOMER. En pratique, l’utilisateur n’a donc qu’à saisir l’id du client auquel le user souhaite relier le joueur, et les noms et prénoms seront récupérés automatiquement.

Également, lors de la création du joueur, celui-ci se verra planifier une rencontre obligatoire avec le manager de l’équipe (via l’utilisation d’une Determination), et un vol sera automatiquement réservé en son nom depuis le camps d’entraînement, vers les bureaux du manager. Tout cela…via cross BO ! En effet, via notre business object ZR_FOOTBALL_TEAM nous allons appeler le Business Object /DMO/I_TRAVEL_M pour effectuer la réservation.

Enfin, lorsqu’il y aura des matchs, nous souhaitons pouvoir réserver un vol à tous les membres de l’équipe depuis notre business object. Nous allons donc créer une action qui viendra effectuer un Cross BO pour venir créer les vols pour chacun des joueurs de l’équipe.

II. Développement

Pour créer la base du business object, j’ai utilisé la génération automatique du BO, via le générateur RAP.

Puis je suis venu modifier le Behavior Definition pour y ajouter :

  • La génération automatique du UUID
  • Le read only sur les champs qui vont être récupérés en automatique depuis /DMO/I_CUSTOMER (le nom et prénom)
  • l’ajout des déterminations
    • getCustomer : Pour venir récupérer les nom et prénom du joueur via l’id customer saisi par le user
    • visitToBoss : pour effectuer le cross BO de création du voyage vers le manager à la création du nouveau joueur.
  • l’ajout de l’action createBooking qui permettra le cross BO de création de voyage pour toute l’équipe.
  • Dans d’autres articles nous verrons également en détails les sujets autours du side effect et du determine action Prepare mais ce n’est pas nécessaire ici pour comprendre la logique du Cross BO.
managed implementation in class ZBP_R_FOOTBALL_TEAM unique;
strict ( 2 );
with draft;
define behavior for ZR_FOOTBALL_TEAM alias ZrFootballTeam
persistent table ZFOOTBALL_TEAM
with additional save
draft table ZFOOTBALL_TEAM_D
etag master LocalLastChanged
lock master total etag ChangeAt
authorization master( global )

{

  field ( mandatory )
  CustomerId;

  field ( readonly, numbering : managed )
   Uuid;

  field ( readonly )
  name,
  surname,
   LocalLastChanged,
   CreatedBy,
   CreatedAt,
   ChangeBy,
   ChangeAt;

  determination getCustomer on modify { field CustomerId; }
  determination visitToBoss on save { create; }
  validation checkCustomerId on save { create; }
  static action createBooking parameter ZP_BOOKING_PARAM;
  create;
  update;
  delete;

  draft action Activate optimized;
  draft action Discard;
  draft action Edit;
  draft action Resume;
  draft determine action Prepare{
   validation checkCustomerId;
  }

  side effects {
  field CustomerId affects field name, field surname; }

  mapping for ZFOOTBALL_TEAM
  {
    Uuid = uuid;
    Name = name;
    Surname = surname;
    FieldPosition = field_position;
    CustomerId = customer_id;
    LocalLastChanged = local_last_changed;
    CreatedBy = created_by;
    CreatedAt = created_at;
    ChangeBy = change_by;
    ChangeAt = change_at;
  }
}

1. Récupérer les nom et prénom du joueur

Pour récupérer les nom et prénom du joueur, l’utilisateur saisi l’id customer du joueur :

En saisissant l’id, comme la determination est configurée pour être executée à chaque modification du champs « Customer ID » ( determination getCustomer on modify { field CustomerId; } ), alors, la méthode getCustomer est appelée

METHOD getCustomer.

    READ ENTITIES OF zr_football_team IN LOCAL MODE ENTITY zrfootballteam
    ALL FIELDS
    WITH CORRESPONDING #( keys )
    RESULT DATA(customers)
    FAILED DATA(failed).

    LOOP AT customers INTO DATA(customer).

      SELECT SINGLE FROM /dmo/customer
      FIELDS first_name, last_name
      WHERE customer_id = @customer-CustomerId
      INTO (  @DATA(name) , @DATA(surname) ).

      DATA player_info TYPE TABLE FOR UPDATE zr_football_team.

      player_info = VALUE #( ( %tky = customer-%tky
                               name = name
                               surname = surname
                               %control = VALUE #( name = if_abap_behv=>mk-on
                                                   surname = if_abap_behv=>mk-on
                                                 ) ) ).
      MODIFY ENTITIES OF zr_football_team IN LOCAL MODE ENTITY zrfootballteam
      UPDATE FROM player_info
      FAILED DATA(failed_u)
      REPORTED DATA(reported_u).

    ENDLOOP.

  ENDMETHOD.

Au sein de cette méthode, nous voyons que nous venons faire un SELECT sur /dmo/customer pour récupérer les informations. Nous aurions pu utiliser un appel EML via READ ENTITY si un behavior implementation avait été développé par SAP pour ce cas d’exemple, mais ce n’est pas le cas. Nous utilisons donc un simple select ici qui fonctionne tout aussi bien, même si effectivement, un vrai cross BO serait effectué via un READ ENTITY.

Une fois l’id saisi, les nom et prénom sont récupérés

2. Création du joueur et création du voyage vers le manager

Une fois les informations saisies pour le joueur, l’utilisateur va effectuer la création de ce dernier.

En cliquant sur créer, la determination visitToBoss va être appelée

Le code suivant va être exécuté :

  METHOD visitToBoss.

    READ ENTITIES OF zr_football_team IN LOCAL MODE ENTITY zrfootballteam
    ALL FIELDS
    WITH CORRESPONDING #( keys )
    RESULT DATA(customers)
    FAILED DATA(failed).

    LOOP AT customers INTO DATA(customer).


      DATA : travels      TYPE TABLE FOR CREATE /DMO/I_Travel_M\\travel,
             bookings_cba TYPE TABLE FOR CREATE /DMO/I_Travel_M\\travel\_booking.

      travels = VALUE #( ( %cid = '1'
                                      agency_id = '070049' "the agency used by the team to travel
                                      customer_id = customer-CustomerId
                                      begin_date = cl_abap_context_info=>get_system_date( ) + 10
                                      end_date = cl_abap_context_info=>get_system_date( ) + 11
                                      overall_status = 'O'
                                      %control = VALUE #( agency_id = if_abap_behv=>mk-on
                                                          customer_id = if_abap_behv=>mk-on
                                                          begin_date = if_abap_behv=>mk-on
                                                          end_date = if_abap_behv=>mk-on
                                                          overall_status = if_abap_behv=>mk-on )
                                   ) ).
      bookings_cba =  VALUE #( (  %cid_ref  = '1'      "refers to the root (travel instance)
                           %target   = VALUE #( (  %cid = '1_1' " Preliminary ID for new booking instance
                                                             carrier_id = 'AA'
                                                             connection_id = '2678'
                                                             Customer_ID = customer-CustomerId
                                                             Flight_Date = cl_abap_context_info=>get_system_date( ) + 10
                                                             booking_date = cl_abap_context_info=>get_system_date( )
                                                             booking_status = 'B' ) ) )
                               (  %cid_ref  = '1'      "refers to the root (travel instance)
                           %target   = VALUE #( (  %cid = '1_2' " Preliminary ID for new booking instance
                                                             carrier_id = 'AA'
                                                             connection_id = '0017'
                                                             Customer_ID = customer-CustomerId
                                                             Flight_Date = cl_abap_context_info=>get_system_date( ) + 11
                                                             booking_date = cl_abap_context_info=>get_system_date( )
                                                             booking_status = 'B' ) ) )
                                                             ).

      MODIFY ENTITIES OF /DMO/I_Travel_M
        ENTITY travel
          CREATE FIELDS ( agency_id customer_id begin_date end_date booking_fee total_price currency_code overall_status description )
            WITH travels
          CREATE BY \_Booking FIELDS ( booking_date customer_id carrier_id connection_id flight_date booking_status )
            WITH bookings_cba
            MAPPED DATA(mapped_bookings)
            FAILED DATA(failed_bookings)
            REPORTED DATA(reported_bookings).

      IF failed_bookings IS NOT INITIAL.
        APPEND VALUE #(  %tky = customer-%tky ) TO failed-zrfootballteam.
        LOOP AT failed_bookings-travel INTO DATA(travel).
          APPEND VALUE #(  %tky = customer-%tky
  %msg = me->new_message( id = 'ZMESSAGE_TEAM'
  number = '00'
  severity = if_abap_behv_message=>severity-error ) ) TO reported-zrfootballteam.
        ENDLOOP.
      ENDIF.

      IF mapped_bookings IS NOT INITIAL.
        LOOP AT mapped_bookings-travel INTO DATA(success_travel).
          APPEND VALUE #( travel_id = success_travel-travel_id customer_id = customer-CustomerId ) TO travel_temp.
        ENDLOOP.
      ENDIF.

    ENDLOOP.

  ENDMETHOD.

Au sein de cette méthode, l’appel EML MODIFY ENTITIES OF /DMO/I_Travel_M (en gras) est appelé. C’est lors de cet appel que le Cross BO vers /DMO/I_Travel_M est effectué.

A NOTER : Tant que la phase de sauvegarde n’est pas atteinte, toutes les déterminations et validations qui doivent être effectuées sur le Business Object /DMO/I_Travel_M lors de la phase de sauvegarde ne sont pas encore effectuées.

Lorsque la phase de sauvegarde sera atteinte (voir debug plus bàs), les validations et déterminations de notre business object ZR_FOOTBALL_TEAM ainsi que celles de /DMO/I_Travel_M seront effectuées.

debug :

  • En cliquant sur la création du joueur dans l’application Fiori, on trigger l’opération CREATE pour laquelle la détermination où à lieu le cross BO est effectuée :

puis on effectue la création du voyage via Cross BO :

Ici les vols aller et retour sont créés via EML (mais pas encore persisté dans la base de données, n’oubliez pas, les validations n’ont pas encore eu lieux comme nous ne sommes pas encore dans la phase de sauvegarde !)

  • Phase de sauvegarde

Comme nous utilisons une application fiori qui appel notre RAP BO via appels HTTP, la phase de sauvegarde est automatiquement effectuée via le framework RAP

Nous arrivons donc dans la phase de sauvegarde et c’est là qu’on lieux les validations en lien avec la création de notre Cross BO pour le trajet vers le manager :

Ici nous pouvons voir que la validation validate_customer du BO /DMO/I_TRAVEL_M est appelée.

Également, la validation checkCustomerId de notre propre BO a été appelée.

A NOTER : Si une des validation de notre BO ou du Cross BO échoue, alors la sauvegarde des 2 BO est annulée : le framework RAP effectue un ROLLBACK ENTITIES.

On voit donc que notre nouveau joueur a été créé correctement :

Et on peut également vérifier le voyage :

3. Création d’un voyage pour tous les joueurs via une action

Finalement, une autre façon d’effectuer un cross BO est via une action.

Ici, j’ai créé une action statique : static action createBooking parameter ZP_BOOKING_PARAM;

Cette dernière va permettre de créer un vol aller retour vers la destination du choix du user via un cross BO vers /DMO/I_TRAVEL_M pour tous les joueurs.

  METHOD createBooking.

    SELECT FROM zr_football_team
    FIELDS CustomerId
    INTO TABLE @DATA(team).

    LOOP AT keys INTO DATA(key).

      LOOP AT team INTO DATA(player).

        DATA : travels      TYPE TABLE FOR CREATE /DMO/I_Travel_M\\travel,
               bookings_cba TYPE TABLE FOR CREATE /DMO/I_Travel_M\\travel\_booking.

        travels = VALUE #( ( %cid = '1'
                                        agency_id = '070049' "the agency used by the team to travel
                                        customer_id = player-CustomerId
                                        begin_date = key-%param-start_date
                                        end_date = key-%param-end_date
                                        overall_status = 'O'
                                        %control = VALUE #( agency_id = if_abap_behv=>mk-on
                                                            customer_id = if_abap_behv=>mk-on
                                                            begin_date = if_abap_behv=>mk-on
                                                            end_date = if_abap_behv=>mk-on
                                                            overall_status = if_abap_behv=>mk-on )
                                     ) ).
        bookings_cba =  VALUE #( (  %cid_ref  = '1'      "refers to the root (travel instance)
                             %target   = VALUE #( (  %cid = '1_1' " Preliminary ID for new booking instance
                                                               carrier_id = key-%param-start_carrier_id
                                                               connection_id = key-%param-start_connection_id
                                                               Customer_ID = player-CustomerId
                                                               Flight_Date = key-%param-start_date
                                                               booking_date = cl_abap_context_info=>get_system_date( )
                                                               booking_status = 'B' ) ) )
                                 (  %cid_ref  = '1'      "refers to the root (travel instance)
                             %target   = VALUE #( (  %cid = '1_2' " Preliminary ID for new booking instance
                                                               carrier_id = key-%param-end_carrier_id
                                                               connection_id = key-%param-end_connection_id
                                                               Customer_ID = player-CustomerId
                                                               Flight_Date = key-%param-end_date
                                                               booking_date = cl_abap_context_info=>get_system_date( )
                                                               booking_status = 'B' ) ) )
                                                               ).

        MODIFY ENTITIES OF /DMO/I_Travel_M
          ENTITY travel
            CREATE FIELDS ( agency_id customer_id begin_date end_date booking_fee total_price currency_code overall_status description )
              WITH travels
            CREATE BY \_Booking FIELDS ( booking_date customer_id carrier_id connection_id flight_date booking_status )
              WITH bookings_cba
              MAPPED DATA(mapped_bookings)
              FAILED DATA(failed_bookings)
              REPORTED DATA(reported_bookings).

        IF failed_bookings IS NOT INITIAL.
          APPEND VALUE #(  %cid = key-%cid ) TO failed-zrfootballteam.
          LOOP AT mapped_bookings-travel INTO DATA(travel).
            APPEND VALUE #(  %cid = key-%cid
  %msg = me->new_message( id = 'ZMESSAGE_TEAM'
  number = '002'
  severity = if_abap_behv_message=>severity-error ) ) TO reported-zrfootballteam.
          ENDLOOP.
        ENDIF.

        IF mapped_bookings IS NOT INITIAL.
          APPEND VALUE #(  %cid = key-%cid ) TO mapped-zrfootballteam.
          LOOP AT mapped_bookings-travel INTO travel.
            APPEND VALUE #(  %cid = key-%cid
%msg = me->new_message( id = 'ZMESSAGE_TEAM'
number = '001'
severity = if_abap_behv_message=>severity-success ) ) TO reported-zrfootballteam.
          ENDLOOP.
        ENDIF.

      ENDLOOP.

    ENDLOOP.

  ENDMETHOD.

De la même facon, en cliquant sur l’action « Create Booking » la méthode createBooking va être appelée et, via EML et Cross BO, les trajets pour les joueurs vont être créés :

Puis l’appel au BO travel pour chaque joueur :

Conclusion

Au sein de cet article, nous avons pu expliquer la notion de Cross BO et à quoi elle peut servir.

Également, nous avons vu comment effectuer ces cross BO via Determination ou via Action.