Cross Business Object in RAP

Treatment in progress…
You're done! You are in the list.

When we develop a new RAP business object or an extension to an existing RAP object (Actions/Validations/Determinations among others), we sometimes need and want to interact with other existing RAP business objects.

These interactions are :

  • Read entity data from an existing Business Object, from another Business Object
  • Create/modify an entity in an existing Business Object, from another Business Object
  • Delete an entity from an existing Business Object, from another Business Object

To perform these actions within our Business Object (the one that calls the other Business Object with which we wish to interact), we use EML, the language used to interact with a RAP Business Object.

In my opinion, there are 3 ways to create a Cross Business Object (BO):

  • By developing an action in the RAP object

So, when you call the action, the EML code enabling cross BO will be executed in the Behavior Implementation code.

  • Via development of a determination in the RAP object

If we don’t want to call any particular action other than the standard CRUD operation, and we want to cross BO when an entity is created, for example, the solution may be to create a determination within which we develop an EML call that enables Cross BO.

In this case, during the save phase, make all calls to other RAP objects via bgPF. We won’t go into this point here.

We’ll now look at an example in detail.

All the code is available on our Gitlab, here.

I. Case studies

To analyze this phenomenon, we will create a specific business object representing a soccer team.

@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;

}

Each player in the team will be linked, on creation, to a customer number linked to the flight booking demo scenario provided by SAP, available in table /DMO/I_CUSTOMER. In practice, all the user has to do is enter the id of the customer to which the user wishes to link the player, and the first and last names will be retrieved automatically.

Also, when a player is created, he will be scheduled for a mandatory meeting with the team manager (using a Determination), and a flight will be automatically booked on his behalf from the training camp to the manager’s offices. All this…via cross BO! In fact, via our ZR_FOOTBALL_TEAM business object, we’ll call the /DMO/I_TRAVEL_M business object to make the reservation.

Finally, when matches are scheduled, we want to be able to book flights for all team members from our business object. So we’re going to create an action that will perform a Cross BO to create flights for each player on the team.

II. Development

To create the business object base, I used the automatic generation of the BO, via the RAP generator.

Then I modified the Behavior Definition to add :

  • Automatic UUID generation
  • Read only on fields that will be automatically retrieved from /DMO/I_CUSTOMER (first and last name)
  • Add determinations
    • getCustomer: to retrieve the player’s first and last name via the customer id entered by the user
    • visitToBoss: to perform the cross BO from travel creation to the manager when the new player is created.
  • The addition of the createBooking action to enable cross BO of travel creation for the entire team.
  • In other articles, we’ll look in detail at side effects and determine action Prepare, but it’s not necessary here to understand the logic of 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. Retrieve player’s first and last name

To retrieve the player’s first and last name, the user enters the player’s customer id:

By entering the id, as the determination is configured to be executed each time the “Customer ID” field is modified ( determination getCustomer on modify { field CustomerId; } ), the getCustomer method is called

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.

Within this method, we can see that we’re making a SELECT on /dmo/customer to retrieve the information. We could have used an EML call via READ ENTITY if a behavior implementation had been developed by SAP for this example, but this isn’t the case. So we’re using a simple select here, which works just as well, even though a true cross BO would be performed via a READ ENTITY.

Once the id has been entered, the first and last names are retrieved.

2. Creating the player and creating the journey to the manager

Once the player information has been entered, the user will create the player.2 Creating the player and creating the travel to the manager

Clicking on create will call the visitToBoss determination

The following code will be executed:

  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.

Within this method, the EML call MODIFY ENTITIES OF /DMO/I_Travel_M (in bold) is made. It is during this call that the Cross BO to /DMO/I_Travel_M is performed.

NOTE: As long as the save phase has not been reached, all the determinations and validations that must be performed on the Business Object /DMO/I_Travel_M during the save phase have not yet been carried out.

Once the save phase has been reached (see debug below), our ZR_FOOTBALL_TEAM business object and /DMO/I_Travel_M will be validated and determined.

debug :

Clicking on player creation in the Fiori application triggers the CREATE operation, which determines where the cross BO takes place:

then create the travel via Cross BO :

Here, outward and return flights are created via EML (but not yet persisted in the database – remember, validations haven’t yet taken place, as we’re not yet in the saving phase!)

  • Save phase

As we are using a fiori application that calls our RAP BO via HTTP calls, the backup phase is automatically performed via the RAP framework.

This brings us to the save phase, where we place the validations linked to the creation of our Cross BO for the journey to the manager:

Here we can see that validation validate_customer of the BO /DMO/I_TRAVEL_M is called.

Also, the checkCustomerId validation of our own BO has been called.

NOTE: If one of our BO or Cross BO validations fails, the 2 BOs are cancelled: the RAP framework performs an ENTITIES ROLLBACK.

We can see that our new player has been created correctly:

And you can also check out the travel:

3. Create a travel for all players via an action

Finally, another way to perform a cross BO is via an action.

Here, I’ve created a static action: static action createBooking parameter ZP_BOOKING_PARAM;

This will create a return flight to the destination of the user’s choice via a cross BO to /DMO/I_TRAVEL_M for all players.

  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.

In the same way, by clicking on the “Create Booking” action, the createBooking method will be called up and, via EML and Cross BO, the routes for the players will be created:

Then the BO travel call for each player:

Conclusion

In this article, we explain what Cross BO is and what it can be used for.

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