ABAP RESTful Application Programming Model - ERP Q&A https://www.erpqna.com/tag/abap-restful-application-programming-model/ Trending SAP Career News and Guidelines Tue, 07 Oct 2025 10:19:36 +0000 en-US hourly 1 https://wordpress.org/?v=6.9.4 https://www.erpqna.com/wp-content/uploads/2021/11/cropped-erpqna-32x32.png ABAP RESTful Application Programming Model - ERP Q&A https://www.erpqna.com/tag/abap-restful-application-programming-model/ 32 32 ABAP RAP: Excel upload through custom action popup (No UI5 Extension, No Object Page workaround) https://www.erpqna.com/abap-rap-excel-upload-through-custom-action-popup-no-ui5-extension-no-object-page-workaround/?utm_source=rss&utm_medium=rss&utm_campaign=abap-rap-excel-upload-through-custom-action-popup-no-ui5-extension-no-object-page-workaround Tue, 07 Oct 2025 10:19:31 +0000 https://www.erpqna.com/?p=93806 A frequent business requirement involves enabling mass changes to business objects via Excel uploads executed through a custom action popup. Historically, achieving this functionality has necessitated various workarounds, often involving UI5 extensions, third-party solutions, or Object Page manipulations, all of which present specific implementation challenges. The existing workaround approaches present several drawbacks: However, SAP has […]

The post ABAP RAP: Excel upload through custom action popup (No UI5 Extension, No Object Page workaround) appeared first on ERP Q&A.

]]>
A frequent business requirement involves enabling mass changes to business objects via Excel uploads executed through a custom action popup. Historically, achieving this functionality has necessitated various workarounds, often involving UI5 extensions, third-party solutions, or Object Page manipulations, all of which present specific implementation challenges.

The existing workaround approaches present several drawbacks:

  • Custom UI Extensions: Require specialized UI5 development expertise.
  • Third-Party Solutions: Introduce risks related to licensing compliance and potential security vulnerabilities.
  • Object Page Manipulations: Involve complex, multi-step processes, such as creating a dummy object page, facilitating file upload, temporarily storing the file data in a table field, and requiring a final user action (a button press) to initiate processing. This temporary data storage is often unnecessary, complicating the data model.

However, SAP has recently introduced ABAP / CAP annotations that offer a cloud-ready solution, potentially eliminating approximately 95% of the development effort typically associated with integrating an Excel upload into the backend. This innovation allows developers to prioritize implementing core business logic over developing reusable technical artifacts.

I will now detail the implementation steps.

A business requirement to manage mass processing listings for a library was selected to demonstrate this use case. The implementation requires several steps, with steps 3 through 6 being the special or additional configurations needed, while all others are considered routine.

Implementation Steps

1. A database table for the listing entity is created. This involves fields such as Id, Title, Type, and Author.

@EndUserText.label : 'Library Listings'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table zrk_lib_listings {

  key client            : abap.clnt not null;
  key listing_uuid      : sysuuid_x16 not null;
  id                    : abap.numc(10);
  title                 : abap.char(40);
  type                  : abap.char(5);
  author                : abap.char(40);
  publisher_studio      : abap.char(40);
  isbn_ean              : abap.char(40);
  language_code         : abap.lang;
  publication_year      : abap.numc(4);
  description           : abap.char(40);
  totalcopies           : abap.int2;
  available_copies      : abap.int2;
  location_shelf_id     : abap.char(40);
  lending_duration_days : abap.int2;
  status                : abap.char(40);
  cover_image_url       : abap.char(100);
  local_created_by      : abp_creation_user;
  local_created_at      : abp_creation_tstmpl;
  local_last_changed_by : abp_locinst_lastchange_user;
  local_last_changed_at : abp_locinst_lastchange_tstmpl;
  last_changed_at       : abp_lastchange_tstmpl;

}

2. A RAP Business Object (BO) is generated, followed by the requisite UI artifacts. The specific RAP BO scenario (Managed, Unmanaged, Draft, or Non-Draft) is noted as not influencing the core Excel upload use case. The RAP Generator is used to simplify the demonstration.

3. A root abstract entity is created for the file to be uploaded. (This entity is highly reusable and can be applied across different RAP BOs).

@EndUserText.label: 'Abs. Entity For Attachment'
define root abstract entity ZRK_D_FILE_STREAM
{
  @Semantics.largeObject.mimeType: 'MimeType'
  @Semantics.largeObject.fileName: 'FileName'
  @Semantics.largeObject.contentDispositionPreference: #INLINE
  @EndUserText.label: 'Select Excel file'
  StreamProperty : abap.rawstring(0);
  
  .hidden: true
  MimeType : abap.char(128);
  
  .hidden: true
  FileName : abap.char(128);   
}

4. The abstract behavior definition for the file entity is implemented.

abstract;
strict(2);
with hierarchy;
define behavior for ZRK_D_FILE_STREAM {
}

5. A second abstract entity is created to serve as an action parameter. This entity includes an association to the file abstract entity (from Step 3).

@EndUserText.label: 'Action Param for Uploading Excel'
define root abstract entity ZRK_D_UPLOAD_EXCEL
{
// Dummy is a dummy field
@UI.hidden: true
dummy : abap_boolean;
     _StreamProperties : association [1] to ZRK_D_FILE_STREAM on 1 = 1;
    
}

6. The abstract behavior definition for the action parameter is implemented, including the association to the earlier entity.

abstract;
strict ( 2 );
with hierarchy;
define behavior for ZRK_D_UPLOAD_EXCEL //alias <alias_name>
{
association _StreamProperties with hierarchy;
}

7. An action is defined on the RAP BO Behavior definition, with the parameter specified in Step 5.

static action ExcelUpload deep parameter ZRK_D_UPLOAD_EXCEL ;
managed implementation in class ZRK_BP_R_LIB_LISTINGS unique;
strict ( 2 );
with draft;
extensible;
define behavior for ZRK_R_LIB_LISTINGS alias Listings
persistent table ZRK_LIB_LISTINGS
extensible
draft table ZRK_LIB_LSTNGS_D
etag master LocalLastChangedAt
lock master total etag LastChangedAt
authorization master( global )
{
  field ( readonly )
   ListingUUID,
   LocalCreatedBy,
   LocalCreatedAt,
   LocalLastChangedBy,
   LocalLastChangedAt,
   LastChangedAt;

  field ( numbering : managed )
   ListingUUID;


  create;
  update;
  delete;

  draft action Activate optimized;
  draft action Discard;
  draft action Edit;
  draft action Resume;
  draft determine action Prepare;

  static action ExcelUpload deep parameter ZRK_D_UPLOAD_EXCEL ;

  mapping for ZRK_LIB_LISTINGS corresponding extensible
  {
    ListingUUID = listing_uuid;
    ID = id;
    Title = title;
    Type = type;
    Author = author;
    PublisherStudio = publisher_studio;
    IsbnEan = isbn_ean;
    LanguageCode = language_code;
    PublicationYear = publication_year;
    Description = description;
    Totalcopies = totalcopies;
    AvailableCopies = available_copies;
    LocationShelfID = location_shelf_id;
    LendingDurationDays = lending_duration_days;
    Status = status;
    CoverImageUrl = cover_image_url;
    LocalCreatedBy = local_created_by;
    LocalCreatedAt = local_created_at;
    LocalLastChangedBy = local_last_changed_by;
    LocalLastChangedAt = local_last_changed_at;
    LastChangedAt = last_changed_at;
  }

}

8. The business logic is implemented to read the Excel content. A released API, XCO_CP_XLSX , is used for this demonstration.

METHOD ExcelUpload.
    TYPES : BEGIN OF ty_sheet_data,
              id                  TYPE zrk_r_lib_listings-id,
              title               TYPE zrk_r_lib_listings-title,
              type                TYPE zrk_r_lib_listings-Type,
              author              TYPE zrk_r_lib_listings-author,
              PublisherStudio     TYPE zrk_r_lib_listings-PublisherStudio,
              IsbnEan             TYPE zrk_r_lib_listings-IsbnEan,
              LanguageCode        TYPE zrk_r_lib_listings-LanguageCode,
              PublicationYear     TYPE zrk_r_lib_listings-PublicationYear,
              description         TYPE zrk_r_lib_listings-Description,
              Totalcopies         TYPE zrk_r_lib_listings-Totalcopies,
              AvailableCopies     TYPE zrk_r_lib_listings-AvailableCopies,
              LocationShelfID     TYPE zrk_r_lib_listings-LocationShelfID,
              LendingDurationDays TYPE zrk_r_lib_listings-LendingDurationDays,
              status              TYPE zrk_r_lib_listings-Status,
            END OF ty_sheet_data.

    DATA lv_file_content   TYPE xstring.
    DATA lt_sheet_data     TYPE STANDARD TABLE OF ty_sheet_data.
    DATA lt_listing_create TYPE TABLE FOR CREATE zrk_r_lib_listings.

    lv_file_content = VALUE #( keys[ 1 ]-%param-_streamproperties-StreamProperty OPTIONAL ).

    " Error handling in case file content is initial

    DATA(lo_document) = xco_cp_xlsx=>document->for_file_content( lv_file_content )->read_access( ).

    DATA(lo_worksheet) = lo_document->get_workbook( )->worksheet->at_position( 1 ).

    DATA(o_sel_pattern) = xco_cp_xlsx_selection=>pattern_builder->simple_from_to(
      )->from_column( xco_cp_xlsx=>coordinate->for_alphabetic_value( 'A' )  " Start reading from Column A
      )->to_column( xco_cp_xlsx=>coordinate->for_alphabetic_value( 'N' )   " End reading at Column N
      )->from_row( xco_cp_xlsx=>coordinate->for_numeric_value( 2 )    " *** Start reading from ROW 2 to skip the header ***
      )->get_pattern( ).

    lo_worksheet->select( o_sel_pattern
                                     )->row_stream(
                                     )->operation->write_to( REF #( lt_sheet_data )
                                     )->set_value_transformation(
                                         xco_cp_xlsx_read_access=>value_transformation->string_value
                                     )->execute( ).

    lt_listing_create = CORRESPONDING #( lt_sheet_data ).

    MODIFY ENTITIES OF zrk_r_lib_listings IN LOCAL MODE
           ENTITY Listings
           CREATE AUTO FILL CID FIELDS ( Id Title Type author PublisherStudio IsbnEan LanguageCode PublicationYear description Totalcopies AvailableCopies LocationShelfID LendingDurationDays status )
           WITH lt_listing_create
           " TODO: variable is assigned but never used (ABAP cleaner)
           MAPPED DATA(lt_mapped)
           " TODO: variable is assigned but never used (ABAP cleaner)
           REPORTED DATA(lt_reported)
           " TODO: variable is assigned but never used (ABAP cleaner)
           FAILED DATA(lt_failed).

    " Communicate the messages to UI - not in scope of this demo
    IF lt_failed IS INITIAL.
      APPEND VALUE #( %msg = new_message_with_text( severity = if_abap_behv_message=>severity-success
                                                    text     = 'Listings have been uploaded - please refresh the list!!' ) )
             TO reported-listings.
    ENDIF.
  ENDMETHOD.

9. The action is utilized on the projection behavior and subsequently exposed in the metadata extension.

use action ExcelUpload;
projection implementation in class ZRK_BP_C_LIB_LISTINGS unique;
strict ( 2 );
extensible;
use draft;
use side effects;
define behavior for ZRK_C_LIB_LISTINGS alias Listings
extensible
use etag
{
  use create;
  use update;
  use delete;

  use action Edit;
  use action Activate;
  use action Discard;
  use action Resume;
  use action Prepare;

  use action ExcelUpload;

}
.lineItem: [{ type:#FOR_ACTION , dataAction: 'ExcelUpload' , label: 'Upload Excel' }]

10. The service binding is published, and the application is then ready for execution.

Note:

This feature is currently functional on the BTP ABAP Environment. However, an issue appears to exist with metadata generation on S/4HANA 2023 On-Premise deployments, even though the objects are syntactically correct. It is anticipated that this constraint will be addressed in the S/4HANA 2025 release, making the full feature set available on the S/4HANA On-Premise version following a brief waiting period.

Rating: 5 / 5 (3 votes)

The post ABAP RAP: Excel upload through custom action popup (No UI5 Extension, No Object Page workaround) appeared first on ERP Q&A.

]]>
Handle Asynchronous task in background job from a stateless UI application using RAP business object https://www.erpqna.com/handle-asynchronous-task-in-background-job-from-a-stateless-ui-application-using-rap-business-object/?utm_source=rss&utm_medium=rss&utm_campaign=handle-asynchronous-task-in-background-job-from-a-stateless-ui-application-using-rap-business-object Tue, 18 Mar 2025 12:09:01 +0000 https://www.erpqna.com/?p=91078 Requirement: An application built using BAS on BTP for an RAP business object has a requirement of triggering an asynchronous task in the background when a button is clicked. Example: ( this example will be used to explain the implementation ) when the button is clicked, the user uploads an excel file with data and […]

The post Handle Asynchronous task in background job from a stateless UI application using RAP business object appeared first on ERP Q&A.

]]>
Requirement: An application built using BAS on BTP for an RAP business object has a requirement of triggering an asynchronous task in the background when a button is clicked. Example: ( this example will be used to explain the implementation ) when the button is clicked, the user uploads an excel file with data and on click of OK, the data needs to be saved but as it is a large amount of data it will take some time, so the UI shouldn’t be waiting for a response from the backend.

Challenge: background processing framework has been introduced by SAP from Cloud 2311 version. Without this framework, it is not possible to trigger an asynchronous task from a stateless UI.

Solution: To simulate similar functionality we can use existing ABAP artifacts like RFC and submitting program in background job.

Overview: In this example, I have implemented the following:

  • a new action with parameters is defined in the behavior definition. The key of the RAP object will hold the keys of the table that need to be changed. The new values to be updated are passed by the UI as parameters to the action
action (features : instance) upload_file  parameter ZSD_ABS_CONTRACTUAL_INDATA;
  • an RFC function module is created that accepts the data from the file in the form of an internal table
  • Above RFC function module is called from the RAP action implementation ( with the addition destination ‘NONE’ as otherwise commit cannot be done from the FM to schedule the job ).
METHOD upload_file.

    DATA: gt_return   TYPE STANDARD TABLE OF zsds_soitem_message,
          lt_sch_data TYPE zsdt_soschdata.

    IF NOT keys[] IS INITIAL.
      LOOP AT keys INTO DATA(ls_keys).
        APPEND INITIAL LINE TO lt_sch_data ASSIGNING FIELD-SYMBOL(<ls_sch_data>).
        <ls_sch_data>-vbeln = ls_keys-salesdocument.
        <ls_sch_data>-posnr = ls_keys-salesdocumentitem.
        <ls_sch_data>-del_dt = COND #( WHEN ls_keys-%param-newcontractualdt IS NOT INITIAL
                                       THEN ls_keys-%param-newcontractualdt ).
        <ls_sch_data>-email = cond #( WHEN ls_keys-%param-email IS NOT INITIAL
                                       THEN ls_keys-%param-email ).

      ENDLOOP.

      CALL FUNCTION 'ZSD_DEL_DATE_UPD_FILE' DESTINATION 'NONE'
        EXPORTING
          it_sch_data   = lt_sch_data
        EXCEPTIONS
          error_message = 99.

    ENDIF.

  ENDMETHOD.
  • The function module calls the ‘JOB_OPEN’ and ‘JOB_CLOSE’ function modules to submit another program via a background job.
  • The program runs asynchronously as a job and updates the data and also sends an email with the results to the email-id ( the parameter email in the previous screenshot )
  • Email functionality has been implemented so the user is informed of the success or failure of the task as the UI doesn’t wait for a response and simply displays a message that the data will be updated in background.
  • The button and the popup that allows the user to upload an excel file:

Conclusion: The above approach can be used to call an asynchronous task from an RAP object for OP SAP versions. With SAP Cloud 2311, it will also be possible to update the UI once the asynchronous task is completed using event driven actions.

Rating: 5 / 5 (1 votes)

The post Handle Asynchronous task in background job from a stateless UI application using RAP business object appeared first on ERP Q&A.

]]>
CDS Abstract Entity and ABAP RESTful Application Programming Model: Input parameter modelling https://www.erpqna.com/cds-abstract-entity-and-abap-restful-application-programming-model-input-parameter-modelling/?utm_source=rss&utm_medium=rss&utm_campaign=cds-abstract-entity-and-abap-restful-application-programming-model-input-parameter-modelling Mon, 28 Oct 2024 08:14:10 +0000 https://www.erpqna.com/?p=88510 1. Using Abstract Entities for Non-Standard RAP BO Operations. This short overview of abstract entities concept in the context of non-standard RAP business object operations. It outlines their purpose, advantages, and implementation strategies, emphasizing their role in enhancing modularity and flexibility in data modeling. Purpose Abstract entities are Core Data Services (CDS) constructs specifically designed […]

The post CDS Abstract Entity and ABAP RESTful Application Programming Model: Input parameter modelling appeared first on ERP Q&A.

]]>
1. Using Abstract Entities for Non-Standard RAP BO Operations.

This short overview of abstract entities concept in the context of non-standard RAP business object operations. It outlines their purpose, advantages, and implementation strategies, emphasizing their role in enhancing modularity and flexibility in data modeling.

Purpose

Abstract entities are Core Data Services (CDS) constructs specifically designed to model complex input parameters for non-standard RAP BO operations(actions and functions).

Database Independence

One of the key features of abstract entities is their independence from database persistence. They are particularly suited for parameter modeling and give possibility to redefine parameters on next modelling level.

Reusability

Abstract entities promote reusability across multiple operations. This characteristic enables developers to adopt a more modular approach, allowing the same abstract entity to be utilized in different contexts without the need for redundant definitions.

Parameter Flexibility

These entities support complex structures, including multi-level nested components. This flexibility allows for more sophisticated data representations and enhances the capability to handle intricate business logic.

Binding

Unlike traditional entities, abstract entities are not bound to specific BO nodes. They provides greater adaptability in how they are integrated into various operations.

Improved Separation of Concerns

By decoupling input parameter modeling from the actual business logic, abstract entities facilitate a clearer separation of concerns. This simplification in design leads to more maintainable and understandable code, as the focus can be placed on each aspect of the application independently.

In conclusion, abstract entities serve as a powerful tool for modeling complex input parameters in non-standard RAP BO operations.

ABAP RESTful Application Programming Model, SAP S/4HANA Cloud, ABAP Development, SAP NetWeaver Application Server for ABAP, SAP S/4HANA

2. Implementation details.

I want to highlight how to effectively use abstract entities in ABAP development. One of their key applications is for typing, particularly for action parameters in RAP actions. Let’s begin with a straightforward example.

We make a simple abstract entity with four fields.

@EndUserText.label: 'ABSTRACT ENTITY'
define root abstract entity ZPRU_ABS_ENTITY
{
    ABSTRACTENTITYNAME : char40;
    SURNAME : char40;
    AGE : int4;
    EMAIL : char40;
    
    CHILD : composition [ * ] of ZPRU_ABS_CHILD;
    CHILD_2: composition [ * ] of ZPRU_ABS_CHILD_2;
}

Next, I created a RAP business object with a root entity view (the specifics of which aren’t important for this example) and defined the ‘sendEntity’ action with an input parameter of type ZPRU_ABS_ENTITY.

Let’s have a look at action definition:

managed implementation in class zbp_pru_root_entity unique;
strict ( 2 );

define behavior for ZPRU_ROOT_ENTITY alias ROOT
persistent table zpru_dn
lock master
authorization master ( instance )
{
  create;
  update;
  delete;
  field ( readonly ) dn_no, freq, prod;

  //Flat
  action sendEntity parameter ZPRU_ABS_ENTITY;
  // Deep
  action sendEntity2 deep parameter ZPRU_ABS_ENTITY;
  // Deep Table
  action sendEntity3 deep table parameter ZPRU_ABS_ENTITY;

}

Right now, let’s check RAP business object implementation class.

CLASS lhc_root DEFINITION INHERITING FROM cl_abap_behavior_handler.
  PRIVATE SECTION.

    METHODS get_instance_authorizations FOR INSTANCE AUTHORIZATION
      IMPORTING keys REQUEST requested_authorizations FOR root RESULT result.

    METHODS sendentity2 FOR MODIFY
      IMPORTING keys FOR ACTION root~sendentity2.

    METHODS sendentity FOR MODIFY
      IMPORTING keys FOR ACTION root~sendentity.

    METHODS sendentity3 FOR MODIFY
      IMPORTING keys FOR ACTION root~sendentity3.

ENDCLASS.

CLASS lhc_root IMPLEMENTATION.

  METHOD get_instance_authorizations.
  ENDMETHOD.

  METHOD sendentity2.
    DATA(lv_deep_field_from_abs_entity) = keys[ 1 ]-%param-child[ 1 ]-abstractchildname.
  ENDMETHOD.

  METHOD sendentity.
    DATA(lv_field_from_abs_entity) = keys[ 1 ]-%param-abstractentityname.
  ENDMETHOD.

  METHOD sendentity3.
    DATA(lv_deep_table_field) = keys[ 1 ]-%param[ 1 ]-child[ 1 ]-abstractchildname.
  ENDMETHOD.

ENDCLASS.

Derived type:

ABAP RESTful Application Programming Model, SAP S/4HANA Cloud, ABAP Development, SAP NetWeaver Application Server for ABAP, SAP S/4HANA

As a result, you’ll see the KEYS table, where each row contains a %PARAM component. This component is typed as the structure ZPRU_ABS_ENTITY.

The next step is to demonstrate the use of the ‘deep parameter AbstractBDEF’ and the ‘deep table parameter AbstractBDEF’ in defining a BDEF action parameter.

To do this, we need to extend the abstract entity by adding a BDEF of type Abstract with a hierarchy.

First, I added the ‘root’ keyword to the abstract entity:

ABAP RESTful Application Programming Model, SAP S/4HANA Cloud, ABAP Development, SAP NetWeaver Application Server for ABAP, SAP S/4HANA

One important note: when adding a BDEF to an abstract entity, we initiate the creation of an abstract business object. As a result, we need to construct this business object in a way that’s quite similar to how we build standard RAP business objects. This is why we use keywords like ‘root’, ‘composition’, and ‘association to parent.’

I also created a new abstract entity, ZPRU_ABS_CHILD. Then, I added mutual associations between ZPRU_ABS_ENTITY as the root and ZPRU_ABS_CHILD as the child.

ABAP RESTful Application Programming Model, SAP S/4HANA Cloud, ABAP Development, SAP NetWeaver Application Server for ABAP, SAP S/4HANA

ABAP RESTful Application Programming Model, SAP S/4HANA Cloud, ABAP Development, SAP NetWeaver Application Server for ABAP, SAP S/4HANA

Then, I’ve created BDEF with ZPRU_ABS_ENITY as root entity and Abstract implementation type:

ABAP RESTful Application Programming Model, SAP S/4HANA Cloud, ABAP Development, SAP NetWeaver Application Server for ABAP, SAP S/4HANA

Let’s overview new abstract BDEF:

abstract;
strict ( 2 );
with hierarchy;

define behavior for ZPRU_ABS_ENTITY alias ABS
{
  association CHILD;
  association CHILD_2;
}

define behavior for ZPRU_ABS_CHILD alias CHILD
{

  association ROOT;

}

define behavior for zpru_abs_child_2 {

association third_level;

}

There are 3 main points:

  1. add keyword ‘with hierarchy’ to make BDEF opt to deep expanding.
  2. recreate RAP BO composition tree, add root entity and child entity.
  3. explicitly mark association to ZPRU_ABS_CHILD.

Finally, I’ve added addition keyword ‘deep’ to action definition to expand action parameter type.

ABAP RESTful Application Programming Model, SAP S/4HANA Cloud, ABAP Development, SAP NetWeaver Application Server for ABAP, SAP S/4HANA

Let’s have a look into typing:

ABAP RESTful Application Programming Model, SAP S/4HANA Cloud, ABAP Development, SAP NetWeaver Application Server for ABAP, SAP S/4HANA

%PARAM typing:

ABAP RESTful Application Programming Model, SAP S/4HANA Cloud, ABAP Development, SAP NetWeaver Application Server for ABAP, SAP S/4HANA

You can notice that to component %PARAM a new nested table with the name CHILD has been added. It’s an effect of keyword ‘deep’ in action parameter definition. Component CHILD has type of table due to cardinality [ * ] in definition of composition in abstract root entity ZPRU_ABS_ENTITY.

Last topic is about addition ‘deep table’ to action parameter definition.

Let’s add it:

ABAP RESTful Application Programming Model, SAP S/4HANA Cloud, ABAP Development, SAP NetWeaver Application Server for ABAP, SAP S/4HANA

Hence, let’s check what has been changed in typing:

ABAP RESTful Application Programming Model, SAP S/4HANA Cloud, ABAP Development, SAP NetWeaver Application Server for ABAP, SAP S/4HANA

As you can see component %PARAM became table, before it was a structure. This is the effect of keyword ‘table’ in action parameter definition.

Lastly, the same principles apply to typing action output parameters. However, one key difference is that we can’t use the ‘deep’ addition when defining output parameters. As a result, the %PARAM component will be incorporated into the output’s derived type as a structure.

A table with summarizing:

ABAP RESTful Application Programming Model, SAP S/4HANA Cloud, ABAP Development, SAP NetWeaver Application Server for ABAP, SAP S/4HANA
Rating: 5 / 5 (1 votes)

The post CDS Abstract Entity and ABAP RESTful Application Programming Model: Input parameter modelling appeared first on ERP Q&A.

]]>
SAP RAP Unmanaged scenario example-Simplified https://www.erpqna.com/sap-rap-unmanaged-scenario-example-simplified/?utm_source=rss&utm_medium=rss&utm_campaign=sap-rap-unmanaged-scenario-example-simplified Thu, 04 Jul 2024 11:24:06 +0000 https://www.erpqna.com/?p=86138 SAP RAP (ABAP RESTful Application Programming Model) has two main flavors: managed and unmanaged. Let’s focus on the unmanaged version. Unmanaged SAP RAP refers to a development approach where developers have more control over the data persistence and business logic compared to the managed approach. Here are some key aspects Overall, unmanaged SAP RAP provides […]

The post SAP RAP Unmanaged scenario example-Simplified appeared first on ERP Q&A.

]]>
SAP RAP (ABAP RESTful Application Programming Model) has two main flavors: managed and unmanaged. Let’s focus on the unmanaged version.

Unmanaged SAP RAP refers to a development approach where developers have more control over the data persistence and business logic compared to the managed approach. Here are some key aspects

  1. Custom Logic: In unmanaged RAP, developers write their own custom logic for handling data retrieval, manipulation, and persistence. This gives more flexibility in how data is processed and stored.
  2. Direct Database Access: Developers can directly access the database tables and define their own data models using Core Data Services (CDS) views or ABAP classes.
  3. Explicit Service Definition: Unlike managed RAP, where service definitions are automatically generated based on annotations, unmanaged RAP requires developers to explicitly define service implementations and behaviors.
  4. Manual CRUD Operations: CRUD (Create, Read, Update, Delete) operations need to be implemented explicitly in unmanaged RAP, giving full control over how data is managed.
  5. Integration with Existing Systems: Unmanaged RAP is often used when integrating with existing systems or when there is a need for complex business logic that cannot be easily handled by the managed approach.
  6. Flexibility: Developers have more freedom to implement complex validation rules, authorization checks, and other custom requirements directly in the application logic.

Overall, unmanaged SAP RAP provides a more hands-on approach to application development compared to the managed approach, allowing developers to leverage their expertise in ABAP programming and database handling while building modern RESTful APIs.

Top of Form

In this example, we will show a simple application for Employee build with RAP Unmanaged flavors.

Development steps.

To be summarized below object will be created for Unmanaged scenario.

Table ZT01_EMPLOYEE

Base CDS View Z_I_EMPLOYEES_U

Consumption CDS view Z_C_EMPLOYEES_U

Behavior Definition

Bottom of Form

Behavior definition

Implement the Create method

Implement Update Method

Implement Delete Method

Implement Adjust_Numbers method.

Implement Save method.

Test

1. Open the Application.

2. Click on Create. Give Input value and Create.

3. New Record got created.

4. Select any Row , click on Edit.

5. Change the value and Save.

6. Record will be updated.

7. Select the Rows and click on Delete.

8. Records will be deleted.

9. In the Database table also you can see the records.

So, all the CRUD operation is successful using RAP Unmanaged flavors.

Rating: 0 / 5 (0 votes)

The post SAP RAP Unmanaged scenario example-Simplified appeared first on ERP Q&A.

]]>
ABAP RESTful Application Programming Model (RAP) https://www.erpqna.com/abap-restful-application-programming-model-rap/?utm_source=rss&utm_medium=rss&utm_campaign=abap-restful-application-programming-model-rap Sat, 29 Jun 2024 10:52:44 +0000 https://www.erpqna.com/?p=85983 Introduction The SAP landscape has evolved significantly, with businesses seeking simpler, more efficient solutions that offer excellent user experiences. Many organizations remain deeply embedded in the SAP ecosystem, primarily focusing on ABAP over other languages. So, is it possible to develop feature-rich applications without other frontend languages? Yes, leveraging ABAP with RAP (ABAP Restful Application […]

The post ABAP RESTful Application Programming Model (RAP) appeared first on ERP Q&A.

]]>
Introduction

The SAP landscape has evolved significantly, with businesses seeking simpler, more efficient solutions that offer excellent user experiences. Many organizations remain deeply embedded in the SAP ecosystem, primarily focusing on ABAP over other languages. So, is it possible to develop feature-rich applications without other frontend languages? Yes, leveraging ABAP with RAP (ABAP Restful Application Programming) makes it possible.

Restful Application Programming is an ABAP programming model for creating business applications and services in an AS ABAP or BTP ABAP environment. RAP offers a standardized way of developing applications using Core Data Services (CDS), the modernized extended ABAP language, OData protocol, and the concept of business objects and services. RAP applications can only be created through ABAP development tools (ADT) and it’s available in SAP BTP ABAP Environment, SAP S/4 HANA Cloud, and AS ABAP >=7.56.

Before digging deeper into RAP, let’s explore CDS, annotations, and business services. To illustrate these concepts, let’s create a simple read-only list report application.

Developing an OData Service for simple list reporting

An OData service follows the best practices for developing and consuming RESTful APIs. This service can be used in SAP Fiori applications and can also be exposed as Web APIs. Below are the steps for creating a simple list report application:

Let’s explore each step in detail by creating the application.

Sample requirement: Create a read-only list report application which shows purchase order information.

  • Create an interface CDS view which takes data from Purchase Order Header (EKKO) and Item (EKPO).

  • Create two interface CDS views for showing master data of purchase order type and material details.

  • Make an association between the purchase order type CDS view and material details CDS view from the purchase order header/item CDS view. The associated views will act as Search Help in the list report after applying the annotations.

  • Create a consumption view on top of the Purchase Order Header/Item interface view (ZI_PURCHASE_ORDER_RVN).

The UI annotations needed for the application are written in the consumption CDS View or Metadata Extensions.

Now, we have the data model and the required annotations to manifest semantics for it. The next step is to create the OData service and binding the service.

To define a service, we first need to create a service definition. In service definition, we specify the CDS entities that need to be exposed. In this example, the gateway client is replaced by the service definition and service binding.

As a last step, create the service binding for service definition.

Set the binding type as OData V2 – UI, since this is an OData V2 service.

After publishing the service, the exposed entity and associated entities will be visible. Click on the entity and click the preview button to see the preview of the application.

Purchasing Doc Type Search Help

Material Search Help

Conclusion

This blog serves as an introduction to developing OData services for simple list reporting using the ABAP Restful Application Programming (RAP) model. By following the steps outlined, you can create a read-only list report application that showcases purchase order information. We have covered the basics of creating CDS views, defining and binding OData services, and incorporating annotations for enhanced functionality.

Rating: 0 / 5 (0 votes)

The post ABAP RESTful Application Programming Model (RAP) appeared first on ERP Q&A.

]]>
Streams in RAP: Uploading PDF, Excel and Other Files in RAP Application https://www.erpqna.com/streams-in-rap-uploading-pdf-excel-and-other-files-in-rap-application/?utm_source=rss&utm_medium=rss&utm_campaign=streams-in-rap-uploading-pdf-excel-and-other-files-in-rap-application Mon, 04 Mar 2024 10:56:50 +0000 https://www.erpqna.com/?p=82070 Uploading Large Object and media such as Excel or Image through your application is a common business requirement and the only way to do it through a RAP application was by extending the application and using UI5 tooling to upload the file. With the latest SAP BTP ABAP 2208 release the RAP framework now supports […]

The post Streams in RAP: Uploading PDF, Excel and Other Files in RAP Application appeared first on ERP Q&A.

]]>
Uploading Large Object and media such as Excel or Image through your application is a common business requirement and the only way to do it through a RAP application was by extending the application and using UI5 tooling to upload the file.

With the latest SAP BTP ABAP 2208 release the RAP framework now supports OData streams. It is now possible to enable your RAP application to maintain and handle Large Objects(LOBs).This feature provides end users an option to upload external files of different file formats such as PDF, XLSX, binary file format and other types hence allowing media handling.

In this Blog we will explore how to upload and handle Large Object such as PDF or Binary files without the need to extend the RAP application in BAS.

Large objects are modeled by means of the following fields:

  • Attachment
  • Mimetype
  • Filename

The field Attachment contains the LOB itself in a RAWSTRING format and is technically bound to the field Mimetype and Filename using semantics annotation.

Mimetype represents the content type of the attachment uploaded and the values for the fields Mimetype and Filename are derived from the field Attachment by the RAP framework based on the maintained CDS annotations. No attachment can exist without its mimetype and vice versa.

For example, when a PDF is uploaded the Mimetype field will be derived and populated with ‘APPLICATION/PDF’.

PDF File Uploaded from RAP application

To try this feature out I have built a simple RAP application to upload files directly using RAP Framework.

Database Table

A Database table was built as per code snippet below.

The field attachment has a data type of RAWSTRING. In BTP ABAP environment you cannot use RAWSTRING domain directly so create a custom domain with data type as RAWSTRING and Length as ‘0’ . This is important as length being ‘0’ would indicate that the RAWSTRING has No length restriction and can accommodate file of larger size. ZMIMETYPE and ZFILENAME are both of type Character and length 128.

@EndUserText.label : 'Invoice Table'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table zinvoicetable {
  key client            : abap.clnt not null;
  key invoice           : ebeln not null;
  comments              : char30;
  attachment            : zattachment;
  mimetype              : zmimetype;
  filename              : zfilename;
  local_created_by      : abp_creation_user;
  local_created_at      : abp_creation_tstmpl;
  local_last_changed_by : abp_locinst_lastchange_user;
  local_last_changed_at : abp_locinst_lastchange_tstmpl;
  last_changed_at       : abp_lastchange_tstmpl;

}

Interface View

CDS annotation @Semantics.largeObject technically binds the MimeType and Filename to the Attachment.

The annotation contentDispositionPreference can be used to define whether, depending on the browser settings, the file attachment is either displayed in the browser (setting #INLINE) or downloaded when selected (option #ATTACHMENT).

Annotation @Semantics.largeObject.acceptableMimeTypes can be used to restrict the Media types which can be uploaded. The validation and Error handling on upload of unsupported media type is handled by the RAP framework.

CDS annotation @Semantics.mimeType: true was used to define the field MimeType as such.

@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Invoice Table'
define root view entity ZI_INVOICETABLE
  as select from zinvoicetable
{
  key invoice               as Invoice,
      comments              as Comments,
      @Semantics.largeObject:
      { mimeType: 'MimeType',
      fileName: 'Filename',
      contentDispositionPreference: #INLINE }
      attachment            as Attachment,
      @Semantics.mimeType: true
      mimetype              as MimeType,
      filename              as Filename,
      @Semantics.user.createdBy: true
      local_created_by      as LocalCreatedBy,
      @Semantics.systemDateTime.createdAt: true
      local_created_at      as LocalCreatedAt,
      @Semantics.user.lastChangedBy: true
      local_last_changed_by as LocalLastChangedBy,
      //local ETag field --> OData ETag
      @Semantics.systemDateTime.localInstanceLastChangedAt: true
      local_last_changed_at as LocalLastChangedAt,

      //total ETag field
      @Semantics.systemDateTime.lastChangedAt: true
      last_changed_at       as LastChangedAt
}

Consumption View

@EndUserText.label: 'Invvoice Table'
@AccessControl.authorizationCheck: #NOT_REQUIRED
@Metadata.allowExtensions: true
define root view entity ZC_INVOICE_TABLE
  provider contract transactional_query
  as projection on ZI_INVOICETABLE
{
  key Invoice,
      Comments,
      Attachment,
      MimeType,
      Filename,
      LocalLastChangedAt
}

Metadata Extension

From an UI perspective the User only needs to interact with the Attachment and hence Mimetype and Filename is hidden.

@Metadata.layer: #CORE
@UI: { headerInfo: {
typeName: 'Invoice',
typeNamePlural: 'Invoices',
title: { type: #STANDARD, value: 'Invoice' },
         description: { type: #STANDARD, value: 'Invoice' } },
         presentationVariant: [{
         sortOrder: [{ by: 'Invoice', direction: #ASC }],
         visualizations: [{type: #AS_LINEITEM}] }] }
annotate entity ZC_INVOICE_TABLE with
{
  @UI.facet: [    {
                label: 'General Information',
                id: 'GeneralInfo',
                type: #COLLECTION,
                position: 10
                },
                     { id:            'Invoicedet',
                    purpose:       #STANDARD,
                    type:          #IDENTIFICATION_REFERENCE,
                    label:         'Invoice Details',
                    parentId: 'GeneralInfo',
                    position:      10 },
                  {
                      id: 'Upload',
                      purpose: #STANDARD,
                      type: #FIELDGROUP_REFERENCE,
                      parentId: 'GeneralInfo',
                      label: 'Upload Invoice',
                      position: 20,
                      targetQualifier: 'Upload'
                  } ]

  @UI: { lineItem:       [ { position: 10, importance: #HIGH , label: 'Invoice Number'} ] ,
          identification: [ { position: 10 , label: 'Invoice Number' } ] }
  Invoice;
  @UI: { lineItem:       [ { position: 20, importance: #HIGH , label: 'Comments'} ] ,
           identification: [ { position: 20 , label: 'Comments' } ] }
  Comments;
  @UI:
  { fieldGroup:     [ { position: 50, qualifier: 'Upload' , label: 'Attachment'} ]}
  Attachment;

  @UI.hidden: true
  MimeType;

  @UI.hidden: true
  Filename;

}

I have created a managed Behavior definition with Draft and Created a Service definition and Service binding to expose this as a V4 UI .

managed implementation in class zbp_i_invoicetable unique;
strict ( 2 );
with draft;

define behavior for ZI_INVOICETABLE alias Invoice
persistent table ZINVOICETABLE
draft table zinvoicetdraft
lock master
total etag LocalLastChangedAt
authorization master ( instance )
etag master LastChangedAt
{

 // administrative fields: read only
  field ( readonly ) LastChangedAt, LocalLastChangedBy, LocalLastChangedAt , LocalCreatedBy ,
                      LocalCreatedAt;

  create;
  update;
  delete;

  draft action Edit ;
  draft action Activate;
  draft action Discard;
  draft action Resume;

  draft determine action Prepare ;
}

Once the OData is published through service binding, we can preview the application.

List Page

You can click on create to Create a new Instance.

Object page With Upload Option

On “Upload File” the File Open Dialog comes up to select the file from Presentation Server .

File Selection Dialog

Once the File is uploaded the Hyperlink can be used to access the file and based on annotation contentDispositionPreference the file would either open in a new window or downloaded when selected.

After File Upload

Once the Instance is saved we can see the new file encoded in RAWSTRING format along with its Mimetype and Name saved in the database.

Database table after Upload

In Conclusion, with the Support of OData streams, we can now handle LOBs directly using RAP framework, this really caters to a lot of missing features for which extensions were needed before. The above application is a very simple example of how this feature can be used.

Rating: 0 / 5 (0 votes)

The post Streams in RAP: Uploading PDF, Excel and Other Files in RAP Application appeared first on ERP Q&A.

]]>
How to create a Fiori Elements App for a Time-Dependent RAP BO https://www.erpqna.com/how-to-create-a-fiori-elements-app-for-a-time-dependent-rap-bo/?utm_source=rss&utm_medium=rss&utm_campaign=how-to-create-a-fiori-elements-app-for-a-time-dependent-rap-bo Sat, 02 Dec 2023 09:21:33 +0000 https://www.erpqna.com/?p=79857 Introduction In this blog post, you will learn how to create a Fiori Elements app for a Time-Dependent RAP Business Object. Time dependency means that the underlying table has a date that represents the start or end of the record validity as a key field. Our goal is to create a Fiori Elements app where […]

The post How to create a Fiori Elements App for a Time-Dependent RAP BO appeared first on ERP Q&A.

]]>
Introduction

In this blog post, you will learn how to create a Fiori Elements app for a Time-Dependent RAP Business Object.

Time dependency means that the underlying table has a date that represents the start or end of the record validity as a key field.

Our goal is to create a Fiori Elements app where

  • The user can filter the data based on record validity to display current, past, or future valid entries
  • The user can use a delimit action to split an existing time slice
  • Validations ensure that only one time slice is valid at any time and that there are no gaps between time slices

To follow this blog, you should be familiar with

  • ABAP RESTful Programming Model
  • Fiori Elements

This blog is relevant for:

  • SAP S/4HANA On-Premises 2023 or higher
  • SAP S/4HANA Cloud, Public Edition
  • SAP S/4HANA Cloud, Private Edition
  • SAP BTP, ABAP Environment

Generate RAP BO for time-dependent customizing table

The time-dependent table has the following definition:

@EndUserText.label : 'Time depend.'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #C
@AbapCatalog.dataMaintenance : #ALLOWED
define table ztimedep {
  key client              : abap.clnt not null;
  key numc1               : abap.numc(1) not null;
  key validity_begin_date : abap.dats not null;
  validity_end_date       : abap.dats;
  content                 : abap.char(30);
  last_changed_at         : abp_lastchange_tstmpl;
  local_last_changed_at   : abp_locinst_lastchange_tstmpl;
}

Time slice validation

Add the following validation to the table entity in the behavior definition. By using the validity date fields as trigger fields, you can ensure that all changes to the time slice are validated. Also add this validation to the preparation action because a draft-enabled BO has been generated and this validation should be executed for the draft instance.

validation ValidateTimeSlice on save { field ValidityBeginDate, ValidityEndDate; }

draft determine action Prepare {
  validation TimeDepend ~ ValidateTimeSlice;
}

Note the comments in the code for a detailed explanation:

METHOD ValidateTimeSlice.
    DATA check_date TYPE d.
    CONSTANTS c_state_area TYPE string VALUE `TimeValidity`.
    "Entities can only be read if the key is specified in full
    "Validation requires not only the modified entity, but all others with the same initial key
    "Therefore, we use the parent entity to retrieve all sibling entities and additionally read ValidityEndDate
    READ ENTITIES OF zi_timedepend_s IN LOCAL MODE
      ENTITY timedependall BY \_timedepend
      FROM VALUE #( ( %tky-singletonid = 1
                      %tky-%is_draft = keys[ 1 ]-%is_draft ) )
      RESULT FINAL(all_keys).
    READ ENTITY IN LOCAL MODE zi_timedepend
      FIELDS ( ValidityEndDate ) WITH CORRESPONDING #( all_keys )
      RESULT DATA(all_entities).
    "Sort by ValidityBeginDate
    SORT all_entities BY numc1 ValidityBeginDate ASCENDING.

    LOOP AT keys ASSIGNING FIELD-SYMBOL(<key>).
      "We are using state messages: https://help.sap.com/docs/abap-cloud/abap-rap/state-messages?version=sap_btp
      "Therefore, invalidate existing messages first
      INSERT VALUE #( %tky        = <key>-%tky
                      %state_area = c_state_area ) INTO TABLE reported-timedepend.
      READ TABLE all_entities WITH KEY %tky = <key>-%tky BINARY SEARCH INTO DATA(entity).
      DATA(tabix) = sy-tabix.
      "Check1: If ValidityEndDate is set, it must be before ValidityBeginDate
      IF entity-ValidityEndDate IS NOT INITIAL AND entity-ValidityEndDate < entity-ValidityBeginDate.
        INSERT VALUE #( %tky = <key>-%tky ) INTO TABLE failed-timedepend.
        INSERT VALUE #( %tky = <key>-%tky
                        %state_area = c_state_area
                        %path-timedependall-singletonid = 1
                        %path-timedependall-%is_draft = <key>-%is_draft
                        %element-ValidityEndDate = if_abap_behv=>mk-on "to highlight the affected cell
                        %msg = new_message_with_text( text = `End date is before Begin date` ) ) INTO TABLE reported-timedepend.
        CONTINUE.
      ENDIF.
      "Check2: Since we sorted by ValidityBeginDate, we can read the preceding chronological entity and check for gaps or overlaps.
      READ TABLE all_entities INDEX tabix - 1 ASSIGNING FIELD-SYMBOL(<prev_entity>).
      IF sy-subrc = 0 AND <prev_entity>-numc1 = <key>-numc1.
        check_date = entity-ValidityBeginDate - 1.
        IF <prev_entity>-ValidityEndDate <> check_date.
          IF <prev_entity>-ValidityEndDate > check_date.
            DATA(text) = `Time slices overlap`.
          ELSE.
            text = `Gap between time slices`.
          ENDIF.
          INSERT VALUE #( %tky = <key>-%tky ) INTO TABLE failed-timedepend.
          INSERT VALUE #( %tky = <key>-%tky
                          %state_area = c_state_area
                          %path-timedependall-singletonid = 1
                          %path-timedependall-%is_draft = <key>-%is_draft
                          %element-ValidityBeginDate = if_abap_behv=>mk-on
                          %msg = new_message_with_text( text = text ) ) INTO TABLE reported-timedepend.
        ENDIF.
      ENDIF.
      "Check3: check the following chronological entity
      READ TABLE all_entities INDEX tabix + 1 ASSIGNING FIELD-SYMBOL(<next_entity>).
      IF sy-subrc = 0 AND <next_entity>-numc1 = <key>-numc1.
        check_date = entity-ValidityEndDate + 1.
        IF <next_entity>-ValidityBeginDate <> check_date.
          IF <next_entity>-ValidityBeginDate < check_date.
            text = `Time slices overlap`.
          ELSE.
            text = `Gap between time slices`.
          ENDIF.
          INSERT VALUE #( %tky = <key>-%tky ) INTO TABLE failed-timedepend.
          INSERT VALUE #( %tky = <key>-%tky
                          %state_area = c_state_area
                          %path-timedependall-singletonid = 1
                          %path-timedependall-%is_draft = <key>-%is_draft
                          %element-ValidityEndDate = if_abap_behv=>mk-on
                          %msg = new_message_with_text( text = text ) ) INTO TABLE reported-timedepend.
        ENDIF.
      ENDIF.
    ENDLOOP.
  ENDMETHOD.

In the following example, a new record is added, but its time slice overlaps with the existing records. Note that the validation is executed when you save.

Validation example

Delimit action

A typical task is to split or delimit an existing time-dependent record. We provide the user with an action where a record is selected and a new ValididyEndDate is provided by the user. The existing data record is adjusted with the specified date and a new data record is also created.

Create a new abstract entity for parameterizing the action:

@EndUserText.label: 'Delimit'
define abstract entity ZD_DELIMITTP
{
  @EndUserText.label: 'New Validity End Date'
  ValidityEndDate : abap.dats;
}

Add the following action to the table entity in the behavior definition and, if applicable, the behavior projection. Also, add a side effect so that the table is refreshed when the action is applied to multiple rows.

"behavior definition
factory action ( features: instance ) Delimit parameter ZD_DELIMITTP [1];
side effects
  { action Delimit affects entity _TimeDependAll; }
"behavior projection
projection;
strict;
use draft;
use side effects;

use action Delimit;

Add the action to the metadata extension:

@UI.identification: [ {
    position: 1 , 
    label: 'Numc1'
  } ]
  @UI.lineItem: [ {
    position: 1 , 
    label: 'Numc1'
  },
  {
    type: #FOR_ACTION, 
    dataAction: 'Delimit', 
    label: 'Delimit Selected Entry'
  } ]
  @UI.facet: [ {
    id: 'ZI_TimeDepend', 
    purpose: #STANDARD, 
    type: #IDENTIFICATION_REFERENCE, 
    label: 'Time depend.', 
    position: 1 
  } ]
  Numc1;

Note the comments in the code for a detailed explanation:

METHOD get_global_authorizations.
    AUTHORITY-CHECK OBJECT 'S_TABU_NAM' ID 'TABLE' FIELD 'ZI_TIMEDEPEND' ID 'ACTVT' FIELD '02'.
    DATA(is_authorized) = COND #( WHEN sy-subrc = 0 THEN if_abap_behv=>auth-allowed
                                  ELSE if_abap_behv=>auth-unauthorized ).
    result-%action-delimit = is_authorized.
  ENDMETHOD.

  METHOD get_instance_features.
    "The delimitation action shall only be possible for draft entities as the transport selection logic requires a draft entity
    result = VALUE #( FOR <key> IN keys (
               %tky = <key>-%tky
               %action-delimit = COND #( WHEN <key>-%is_draft = if_abap_behv=>mk-on
                                         THEN if_abap_behv=>fc-o-enabled
                                         ELSE if_abap_behv=>fc-o-disabled ) ) ).
  ENDMETHOD.

  METHOD delimit.
    DATA new_timedepend TYPE TABLE FOR CREATE zi_timedepend_s\_timedepend.
    DATA modify_timedepend TYPE TABLE FOR UPDATE zi_timedepend.

    CHECK lines( keys ) > 0.
    READ ENTITIES OF zi_timedepend_s IN LOCAL MODE
      ENTITY timedepend
        ALL FIELDS WITH CORRESPONDING #( keys )
        RESULT FINAL(ref_timedepends).
    APPEND VALUE #( %is_draft = keys[ 1 ]-%is_draft
                    singletonid = 1 )
      TO new_timedepend ASSIGNING FIELD-SYMBOL(<new_timedepend>).

    LOOP AT keys ASSIGNING FIELD-SYMBOL(<key>).
      READ TABLE ref_timedepends WITH TABLE KEY draft COMPONENTS %tky = <key>-%tky INTO DATA(ref_timedepend).
      "The new ValidityEndDate must be between the current ValidityBeginDate and ValidityEndDate.
      IF <key>-%param-ValidityEndDate < ref_timedepend-ValidityBeginDate OR <key>-%param-ValidityEndDate >= ref_timedepend-ValidityEndDate.
        INSERT VALUE #( %tky = <key>-%tky ) INTO TABLE failed-timedepend.
        INSERT VALUE #( %tky = <key>-%tky
                        %path-timedependall-singletonid = 1
                        %path-timedependall-%is_draft = <key>-%is_draft
                        %msg = new_message_with_text( text = |{ <key>-%param-ValidityEndDate DATE = USER } is not a valid date for delimit action| ) ) INTO TABLE reported-timedepend.
        CONTINUE.
      ENDIF.
      "new record-ValidityBeginDate = user selected date + 1 day
      "new record-ValidityEndDate = reference record-ValidityEndDate
      ref_timedepend-ValidityBeginDate = <key>-%param-ValidityEndDate + 1.
      INSERT VALUE #( %cid = <key>-%cid
                      %is_draft = <key>-%is_draft
                      %data = CORRESPONDING #( ref_timedepend EXCEPT lastChangedAt localLastChangedAt singletonid ) "don't copy technical fields
       ) INTO TABLE <new_timedepend>-%target.
      "reference record-ValidityEndDate = user selected date
      INSERT VALUE #( %tky = <key>-%tky
                      ValidityEndDate = <key>-%param-ValidityEndDate
                      %control-ValidityEndDate = if_abap_behv=>mk-on ) INTO TABLE modify_timedepend.
    ENDLOOP.
    IF new_timedepend[ 1 ]-%target IS NOT INITIAL.
      MODIFY ENTITIES OF zi_timedepend_s IN LOCAL MODE
        ENTITY timedependall CREATE BY \_timedepend
        FIELDS ( numc1
                 ValidityBeginDate
                 ValidityEndDate
                 content ) WITH new_timedepend
        ENTITY  timedepend  UPDATE FIELDS  ( ValidityEndDate ) WITH modify_timedepend
          MAPPED FINAL(mapped_create).
      mapped-timedepend = mapped_create-timedepend.
    ENDIF.
  ENDMETHOD.

In the following example, the record valid for October is delimited:

Delimit Selected Entry
Delimit Action Result

Validity View Variants

The user should be able to filter the entries by their validity regarding a key date:

  • Currently valid
  • Valid in the past
  • Valid in the future

There are two alternative solutions:

  • View settings variants
  • Calculated or Virtual validity field

View settings variants

The user can use the table view settings to create the required filter conditions.

Filter Conditions for Effective Date Today

For this approach, you must ensure that both ValidityBeginDate and ValidityEndDate are within a reasonable period of time so that Today +/- can be applied. You can change the time slice validation by changing the first check:

"Check1: ValidityBeginDate and ValidityEndDate must be within a reasonable timeframe
      DATA(max_date) = CONV d( cl_abap_context_info=>get_system_date( ) + 99999 ).
      DATA(min_date) = CONV d( cl_abap_context_info=>get_system_date( ) - 99999 ).
      IF entity-ValidityEndDate < entity-ValidityBeginDate
        OR entity-ValidityEndDate > max_date.
        IF entity-ValidityEndDate < entity-ValidityBeginDate.
          DATA(err_text) = `End date must be greather than Begin date`.
        ELSE.
          err_text = |End date must not be greather than {  max_date DATE = USER }|.
        ENDIF.
        INSERT VALUE #( %tky = <key>-%tky ) INTO TABLE failed-timedepend.
        INSERT VALUE #( %tky = <key>-%tky
                        %state_area = c_state_area
                        %path-timedependall-singletonid = 1
                        %path-timedependall-%is_draft = <key>-%is_draft
                        %element-ValidityEndDate = if_abap_behv=>mk-on "to highlight the affected cell
                        %msg = new_message_with_text( text = err_text ) ) INTO TABLE reported-timedepend.
        CONTINUE.
      ENDIF.
      IF entity-ValidityBeginDate < min_date.
        INSERT VALUE #( %tky = <key>-%tky ) INTO TABLE failed-timedepend.
        INSERT VALUE #( %tky = <key>-%tky
                        %state_area = c_state_area
                        %path-timedependall-singletonid = 1
                        %path-timedependall-%is_draft = <key>-%is_draft
                        %element-ValidityBeginDate = if_abap_behv=>mk-on
                        %msg = new_message_with_text( text = |Begin date must be greather than { min_date DATE = USER }| ) ) INTO TABLE reported-timedepend.
        CONTINUE.
      ENDIF.

The user now has the option to set this view as the default view for all and create other view variants, for example, for Valid in the future.

Default view

Calculated or virtual validity field

A field “Validity” is added to the data model that calculates the validity of each record with the system date as the key date. The user can use this field in the filter conditions.

Create a custom domain that represents the different validity values:

Validity domain

Create a custom value help entity for the domain:

"for use in SAP BTP, ABAP Environment or S/4HANA Cloud Public Edition
@ObjectModel.dataCategory: #VALUE_HELP
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Validity'
@Metadata.ignorePropagatedAnnotations: true
@ObjectModel.resultSet.sizeCategory: #XS
define view entity ZI_ValidityVH
  as select from DDCDS_CUSTOMER_DOMAIN_VALUE( p_domain_name : 'ZVALIDITY' ) as Id
  association [0..1] to DDCDS_CUSTOMER_DOMAIN_VALUE_T as _Text on  _Text.value_low   = $projection.Validity
                                                               and _Text.language    = $session.system_language
                                                               and _Text.domain_name = Id.domain_name
                                                               and _Text.value_position = Id.value_position
{
         @ObjectModel.text.element: ['Description']
  key    value_low                                 as Validity,
         @Semantics.text: true
         _Text( p_domain_name : 'ZVALIDITY' ).text as Description
}

"for use in S/4HANA Cloud Private Edition and S/4HANA On-Premises
@ObjectModel.dataCategory: #VALUE_HELP
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Validity'
@Metadata.ignorePropagatedAnnotations: true
@ObjectModel.resultSet.sizeCategory: #XS
define view entity ZI_ValidityVH
  as select from dd07l as id
  association [0..1] to dd07t as _text on  _text.domname    = id.domname
                                       and _text.ddlanguage = $session.system_language
                                       and _text.as4local   = id.as4local
                                       and _text.valpos     = id.valpos
                                       and _text.as4vers    = id.as4vers
{
      @ObjectModel.text.element: ['Description']
  key domvalue_l   as Validity,
      @Semantics.text: true
      _text.ddtext as Description
}
where
      id.domname  = 'ZVALIDITY'
  and id.as4local = 'A'
  and id.as4vers  = '0000'

Add a calculated field to the base CDS Entity of the table. It calculates the validity value based on the system date. Use the custom value help entity for value help definition. If you want to add an additional visual indicator for the validity, you can use the criticality annotation.

If you have a projection layer, also add the new field to the projection CDS entity.

In this case, you also have the option to define a virtual element in the projection view instead of the calculated field in the base view. The advantage is that you don’t need to define a determination with the same logic as in the CDS view, because the calculation is executed for both the active instance and the draft instance via the annotated ABAP class.

@EndUserText.label: 'Time depend.'
@AccessControl.authorizationCheck: #CHECK
define view entity ZI_TimeDepend
  as select from ztimedep
  association to parent ZI_TimeDepend_S as _TimeDependAll on $projection.SingletonID = _TimeDependAll.SingletonID
{
  key numc1                 as Numc1,
  key validity_begin_date   as ValidityBeginDate,
      content               as Content,
      validity_end_date     as ValidityEndDate,
      @Semantics.systemDateTime.lastChangedAt: true
      last_changed_at       as LastChangedAt,
      @Semantics.systemDateTime.localInstanceLastChangedAt: true
      local_last_changed_at as LocalLastChangedAt,
      1                     as SingletonID,
      @Consumption.valueHelpDefinition: [{  entity:
      {name: 'ZI_ValidityVH' , element: 'Validity' }
      }]
      case
         when validity_begin_date <= $session.system_date and ( validity_end_date >= $session.system_date or validity_end_date is initial ) then 'C'
         when validity_begin_date > $session.system_date then 'F'
      else 'P'
      end                   as Validity,
      _TimeDependAll
}

Adapt the draft table by adding the calculated field:

@EndUserText.label : 'ZI_TimeDepend - Draft'
@AbapCatalog.enhancement.category : #EXTENSIBLE_ANY
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table ztimedep_d {
  key mandt             : mandt not null;
  key numc1             : abap.numc(1) not null;
  key validitybegindate : abap.dats not null;
  content               : abap.char(30);
  validityenddate       : abap.dats;
  lastchangedat         : abp_lastchange_tstmpl;
  locallastchangedat    : abp_locinst_lastchange_tstmpl;
  singletonid           : abap.int1;
  validity              : abap.char(1);
  "%admin"              : include sych_bdl_draft_admin_inc;
}

Add the following determination to the table entity in the behavior definition to update the validity value when the validity dates are changed. A side effect is used so that the validity change is reflected on the UI. If you have a projection layer, reuse the side effect in the behavior projection. If you already have a side effect for the delimit action, simply extend the side effect list.

"behavior definition  
  determination setValidity on modify { field ValidityEndDate; }

  side effects
  { field ValidityEndDate affects field Validity; }

"behavior projection
  use side effects;

Implement the determination:

METHOD setvalidity.
    DATA modify_timedepend TYPE TABLE FOR UPDATE zi_timedepend.
    CHECK lines( keys ) > 0.
    READ ENTITIES OF zi_timedepend_s IN LOCAL MODE
      ENTITY timedepend
        ALL FIELDS WITH CORRESPONDING #( keys )
        RESULT FINAL(ref_timedepends).
    LOOP AT ref_timedepends ASSIGNING FIELD-SYMBOL(<timedepends>).
      INSERT VALUE #( %tky = <timedepends>-%tky
                      validity = COND #( WHEN <timedepends>-ValidityBeginDate <= cl_abap_context_info=>get_system_date( )
                                              AND ( <timedepends>-ValidityEndDate >= cl_abap_context_info=>get_system_date( ) OR <timedepends>-ValidityEndDate IS INITIAL ) THEN 'C'
                                         WHEN <timedepends>-ValidityBeginDate > cl_abap_context_info=>get_system_date( ) THEN 'F'
                                         ELSE 'P' )
                      %control-validity = if_abap_behv=>mk-on ) INTO TABLE modify_timedepend.
    ENDLOOP.
    MODIFY ENTITIES OF zi_timedepend_s IN LOCAL MODE
      ENTITY timedepend
      UPDATE FIELDS  ( validity )
      WITH modify_timedepend.
  ENDMETHOD.

Set the new field Validity as readonly in the behavior definition:

field ( readonly )
   Validity,
   SingletonID,
   LastChangedAt,
   LocalLastChangedAt;

The user can now filter the records in the view settings based on the validity value:

Validity Filter

The user now has the option to set this view as the default view for all:

Default view

If the user expands the ValidityEndDate for an outdated entry, the validity column value is updated once the focus is moved from the ValidityEndDate cell.

Rating: 0 / 5 (0 votes)

The post How to create a Fiori Elements App for a Time-Dependent RAP BO appeared first on ERP Q&A.

]]>
Service Consumption Model 2 for OData Client Proxy https://www.erpqna.com/service-consumption-model-2-for-odata-client-proxy/?utm_source=rss&utm_medium=rss&utm_campaign=service-consumption-model-2-for-odata-client-proxy Tue, 07 Nov 2023 10:19:24 +0000 https://www.erpqna.com/?p=79270 Introduction This blog post will describe the Service Consumption Model 2 for OData. I will describe its benefits, provide a description of the model, take a look at the OData Client Proxy at runtime and also dive into the ABAP cross trace integration as well. If you want to receive or send data to an […]

The post Service Consumption Model 2 for OData Client Proxy appeared first on ERP Q&A.

]]>
Introduction

This blog post will describe the Service Consumption Model 2 for OData. I will describe its benefits, provide a description of the model, take a look at the OData Client Proxy at runtime and also dive into the ABAP cross trace integration as well.

If you want to receive or send data to an OData V2 or V4 service within SAP BTP, ABAP Environment or SAP S/4HANA ABAP Environment, you can use the “Service Consumption Model 2” for OData. As of 2311, it is also possible to consume also Complex Types, Complex Collection, Action (bound) and Functions (bound). Of course, the consumption of EntityTypes and EntitySets is also possible.

In addition you get fewer generated artifacts. The persistence of the underlying model has changed completely. In the first version you get an abstract CDS View for each EntityType. For large services with multiple EntityTypes, you could end up with a lot of artifacts. Now only one class with type definitions is created.

Many artifacts in the first version of the Service Consumption Model for OData

Use case

If you have an SAP BTP, ABAP Environment or SAP S/4HANA ABAP Environment system you can use the Service Consumption Model for OData. From here you can connect to a Cloud or an On premise system.

Scenario

In my scenario I am consuming the /dmo/travel service from another Cloud system. A call to the EntitySet Travel returns the following data:

GET /sap/opu/odata4/dmo/api_travel_u_v4/srvd_a2x/dmo/travel_u/0001/Travel
{
    "@odata.context": "$metadata#Travel",
    "@odata.metadataEtag": "W/\"20230919122803\"",
    "@odata.nextLink": "/sap/opu/odata4/dmo/api_travel_u_v4/srvd_a2x/dmo/travel_u/0001/Travel?$skiptoken=100",
    "value": [
        {
            "@odata.etag": "W/\"SADL-202305191948080000000C~20230519194808.0000000\"",
            "AgencyID": "70041",
            "AgencyName": "Maxitrip",
            "BeginDate": "2023-06-03",
            "BookingFee": 40.0,
            "CurrencyCode": "USD",
            "CustomerID": "594",
            "CustomerName": "Ryan",
            "EndDate": "2024-03-31",
            "LastChangedAt": "2023-05-19T19:48:08Z",
            "Memo": "Vacation",
            "SAP__Messages": [
            ],
            "Status": "P",
            "StatusText": "Planned",
            "TotalPrice": 1889.0,
            "TravelID": "1"
        },
        ...

Service Consumption Model 2 for OData

As of 1808, you can use the Service Consumption Model for OData. From 2311 on, the wizard in the ADT will automatically uses the new version.

Wizard

Select File -> New -> Service Consumption Model and choose OData as Consumption mode in ADT.

The consumption system requires a representation of the remote service. This knowledge is used to create the URL, write and read the JSON form the HTTP requests and responses. Therefore, the wizard needs the EDMX file (a service metadata document that describes the data model exposed by the service as an HTTP endpoint. OData uses EDMX as the format for this description) of the remote service. You can get this by adding $metadata to the end of the service document, in my case it is the following URL:

GET /sap/opu/odata4/dmo/api_travel_u_v4/srvd_a2x/dmo/travel_u/0001/$metadata

I saved this file on my computer to use this in the wizard. In addition I chose ZBG_TRAVEL_SCM as the class name. This class is the model representation and will contain all the types for my client.

EDMX import and class name

The EDMX file is analyzed beforehand to identify potential problems. It may be that certain artifacts are ignored, for example, parts of them violate the OData metadata rules. In my case, the EDMX file describes several EntityTypes, EntitySets, a complex type and a Bound action.

Analyse of EDMX file

The next step looks for the OptimisticConcurrency annotation of the Org.OData.Core.V1 vocabulary. If an EntitySet has this annotation, modifying requests must use an etag. If an EntitySet does not have this annotation, you can select the ETag support here.

ETag support

Finally, I get the Service Consumption Model 2 for OData:

Service Consumption Model 2 for OData

In the upper left section, you can see the model class that describes the /dmo/travel service. I use the code snippets from Travel EntitySet and Read list as operation, as a starting point for my OData client.

Model class

The model class has the following parts:

  1. Type definitions that can be used in my client code. I use a table of zbg_travel_scm=>tys_travel_type to retrieve the travel data.
  2. To find the corresponding types, constants are created for EntitySets, EntityTypes, ComplexTypes, Actions and Functions. Here is the ABAP doc for the EntityType constant, including the link to the type:

3. The model definition is done in several method implementations. The types are used to define all the artifacts of the remote service. In addition a mapping between the ABAP and the EDMX name is done here. ABAP artifacts are limited to 30 characters. In EDMX they can be up to 128 characters long in camel case.

If you need to customize the result of the wizard, e.g. because you need to adapt to certain naming conventions, you can modify the source code of the generated class and adapt it to your needs.

Support for Action and Functions

To call an action or a function, structures and tables for the parameter are generated. With 2311 the ADT integration is still missing. So I can’t select a bound action, an action import, a bound function or a function import and use the code snippet from the ADT. However, the parameter structure is there and I can use it at runtime to call the operation.

Connecting to a remote service

In a cloud system I need an outbound communication scenario, an outbound service and a communication arrangement for the http connection. Tutorial: Prepare Consuming System and Service Consumption Model describes the steps to achieve this.

OData Client Proxy at Runtime

For my client, I have used the code snippet from ADT (right hand side in the Service Consumption Model) to read the EntitySet Travel. The client code uses the type from the model. I changed three things after that:

  1. Establish the HTTP connection using the communication scenario and the outbound service.
  2. Change the IV_RELATIVE_SERVICE_ROOT parameter of the factory (line 56 in the screenshot below) to point to the OData service. This path depends on the communication system.
  3. Add the out->write() statement at the end (line 81).

And voila, the GET request and transformation of the JSON response to ABAP was done for me.

OData Client Proxy at Runtime

Conclusion

The Service Consumption Model 2 for OData supports more OData features and generates fewer artifacts. The model class can be adapted to the my needs, if the result of the wizard does not exceed my expectations. It would be great to get feedback from you if this is indeed the case to further improve the wizard.

Tip for developers

Sometimes it is good to know what the OData Client Proxy does under the hood. Especially connecting to another system can be tricky. Therefore is the OData Client Proxy part of the ABAP Cross trace (ADT Windows -> Show View -> ABAP Cross trace):

Activate OData Client Proxy in cross trace

In the trace result you can see for example the response payload and the CSRF token fetch:

http payload in cross trace
Rating: 0 / 5 (0 votes)

The post Service Consumption Model 2 for OData Client Proxy appeared first on ERP Q&A.

]]>
ABAP RAP(RESTful Application Programming) Model https://www.erpqna.com/abap-raprestful-application-programming-model/?utm_source=rss&utm_medium=rss&utm_campaign=abap-raprestful-application-programming-model Wed, 27 Sep 2023 05:56:01 +0000 https://www.erpqna.com/?p=78396 It is a set of concepts, tools, languages, and frameworks that help developers build innovative, cloud-ready, enterprise applications. RAP is based on REST architectural style, which is a simple and lightweight way of building web services. Why RAP Model? It offers an end-to-end development experience with Standardized architecture and development flow. It is an ABAP […]

The post ABAP RAP(RESTful Application Programming) Model appeared first on ERP Q&A.

]]>
It is a set of concepts, tools, languages, and frameworks that help developers build innovative, cloud-ready, enterprise applications. RAP is based on REST architectural style, which is a simple and lightweight way of building web services.

Why RAP Model?

It offers an end-to-end development experience with Standardized architecture and development flow. It is an ABAP based model which uses frameworks such as Core Data Services (CDS) for defining semantically rich data models.

You will learn how to create a simple RAP model. In this example, we will create a table, CDS view entity, Metadata extension, Behavior definition, Service definition, Service Binding.

We will create below application for creation of Employee detail with the help of FIORI element.

Step1- Create package – ZTEST_NEWPR

Right click on the project –> New –> ABAP Package -> enter the name of package.

After that you can save this package in existing TR or if you want to create a new TR then you need to select radio button for create a new request, give description and finish.

Step2-Add Package in favorite packages- ZTEST_NEWPR

Right click on favorite package -> Add package -> name of the package ->finish.

Step-3 Create table – ZTEST_EMPTABLE

Right click on the package –> New –> Other ABAP Repository Object –> Select Database Table and enter the name of the table -> enter Description -> Next.

Below is the code snippet to create a table.

Step4 – Create class to pass data in table –ZTEST_EMPCLASS

Right click on package -> new -> ABAP class -> enter name of class -> description -> next.

This class is not a mandatory class. If you want to pass data in the table then you can create otherwise you can ignore this step.

Below is the code snippet to pass the data in table.

After executing this class, we will get the data in table.

Step5 – Create Root CDS Entity – ZTEST_EMP_DATADEF

Right click on the package –> New –> Other ABAP Repository Object –> Select Data Definition and enter the name of Root CDS Entity –> Select Define Root View Entity.

Below is the code snippet to create a CDS view.

Step-6 Create a Metadata Extension- ZTEST_EMP_METADATA

Right click on root view entity –> Select New Metadata Extension –> Enter the name of Metadata Extension.

Below is the code snippet to create a metadata extension.

Step7 – Create a Behavior Definition

Right click on root view entity –> Select New Behavior Definition.

All details will be already filled, just click next.

Below is code snippet for behavior definition.

Step8 – Create a Service Definition – ZTEST_EMP_SERVICE

Right click on root view entity –> Select New Service Definition

Below is the code snippet for service definition.

Step 9 – Create Service Binding

Right click on service definition –> Select New Service Binding. Here we need to choose Binding type OData V2-UI or OData V4-UI, for now we have chosen V2-UI.

Now publish the Service by clicking on Publish button.

After publishing, the service below screen will be display.

Test Application ->

Select Service and click on preview button.

To check the data, click on go button.

We can create a new record. Create button appearing in right corner.

After creating a new record.

If we want to update and delete record, we need to select that record and we can modify. Edit and delete button appearing in right corner.

If we want to get all the data in excel file or in PDF we can export as excel or PDF. Highlighted button is the export button.

Excel file after exporting.

PDF file after exporting.

Rating: 0 / 5 (0 votes)

The post ABAP RAP(RESTful Application Programming) Model appeared first on ERP Q&A.

]]>
Calling ABAP on Cloud Trial V4 ODATA from SAP S4 HANA On-premise Using ABAP – SM59 https://www.erpqna.com/calling-abap-on-cloud-trial-v4-odata-from-sap-s4-hana-on-premise-using-abap-sm59/?utm_source=rss&utm_medium=rss&utm_campaign=calling-abap-on-cloud-trial-v4-odata-from-sap-s4-hana-on-premise-using-abap-sm59 Thu, 22 Jun 2023 11:03:44 +0000 https://www.erpqna.com/?p=75715 As a ABAP developer, creating and deploying your application in SAP BTP Platform is now very easy. Use of ABAP Restful application Programming model makes this tasks very easy. More over there is a huge demand now to deploy your application in ABAP on Cloud and to consume it in your On-premise applications and enhancements. […]

The post Calling ABAP on Cloud Trial V4 ODATA from SAP S4 HANA On-premise Using ABAP – SM59 appeared first on ERP Q&A.

]]>
As a ABAP developer, creating and deploying your application in SAP BTP Platform is now very easy. Use of ABAP Restful application Programming model makes this tasks very easy. More over there is a huge demand now to deploy your application in ABAP on Cloud and to consume it in your On-premise applications and enhancements.

Introduction

This approach actually helps a ABAP developer to create more reusable content across different landscapes and for SAP Partners / Vendors – this can increase the reusability and easy plug and play of your functionality.

ABAP Cloud RAP development

Create a easy application in ABAP on Cloud environment.

Tables –

@EndUserText.label : 'Country Specific Material Tax'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table zmaterial_tax {

  key client            : abap.clnt not null;
  key uuid              : sysuuid_x16 not null;
  matnr                 : abap.char(40) not null;
  land                  : abap.char(3) not null;
  matnrtax              : abap.char(4) not null;
  local_created_by      : abp_creation_user;
  local_created_at      : abp_creation_tstmpl;
  local_last_changed_by : abp_locinst_lastchange_user;
  local_last_changed_at : abp_locinst_lastchange_tstmpl;
  last_changed_at       : abp_lastchange_tstmpl;

}

@EndUserText.label : 'Draft table for Material Tax'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table zmatnr_draft {

  key client         : abap.clnt not null;
  key uuid           : sysuuid_x16 not null;
  matnr              : abap.char(40) not null;
  land               : abap.char(3) not null;
  matnrtax           : abap.char(4) not null;
  locallastchangedat : abp_locinst_lastchange_tstmpl;
  "%admin"           : include sych_bdl_draft_admin_inc;

}

CDS Entity –

@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Material Details'
define root view entity ZCDS_I_MATERIAL 
    as select from zmaterial_tax
//composition of target_data_source_name as _association_name
{
    key uuid,
        matnr,
        land,
        matnrtax,
        local_last_changed_at as LocalLastChangedAt
}

Create a Projection View –

@EndUserText.label: 'Projection View'
@AccessControl.authorizationCheck: #NOT_REQUIRED
@Metadata.allowExtensions: true
define root view entity ZCDS_P_MATERIAL
provider contract transactional_query as projection on ZCDS_I_MATERIAL
{
    key uuid,
        matnr,
        land,
        matnrtax,
        LocalLastChangedAt
}

Create Behavior Definitions in Managed Scenario –

managed implementation in class zbp_cds_i_material unique;

//strict ( 1 );
with draft;

define behavior for ZCDS_I_MATERIAL alias MaterialTax
persistent table zmaterial_tax
draft table zmatnr_draft
lock master
total etag LocalLastChangedAt
authorization master ( global )
etag master LocalLastChangedAt
{
  field ( numbering : managed, readonly ) uuid;
  field ( mandatory ) matnr, land;
  field ( readonly ) LocalLastChangedAt;
  create;
  update;
  delete;
}

projection;
//strict ( 1 ); //Uncomment this line in order to enable strict mode 2. The strict mode has two variants (strict(1), strict(2)) and is prerequisite to be future proof regarding syntax and to be able to release your BO.
use draft;

define behavior for ZCDS_P_MATERIAL alias MaterialTax
{
  use create;
  use update;
  use delete;
}

You can also have some metadata extensions –

@Metadata.layer: #CORE
@UI:{
headerInfo:{typeName:'Material',
typeNamePlural:'Materials',
title:{type:#STANDARD,label:'Material Import Tax',value:'matnr'}},
presentationVariant:[{sortOrder:[{by:'matnr',direction:#DESC}]}]}
annotate view ZCDS_P_MATERIAL with
{
  @UI.facet:[{id:'matnr',
  purpose:#STANDARD,
  type:#IDENTIFICATION_REFERENCE,
  label:'Material Import Tax',
  position:10}]
  @UI:{lineItem:[{label: 'Material',position:10}],
  identification:[{label: 'Material',position:10}],
  selectionField:[{position:10}]}
  matnr;

  @UI:{lineItem:[{label: 'Country',position:20}],
  identification:[{label: 'Country',position:20}],
  selectionField:[{position:20}]}
  land;

  @UI:{lineItem:[{label: 'Tax Percentage',position:30}],
  identification:[{label: 'Tax Percentage',position:30}]}
  matnrtax;
  @UI.hidden: true
  LocalLastChangedAt;
}

Now Create Service Definition and Service Binding to expose your OData V4.

The App will be similar to this –

Now lets try to test this application through Postman. In order to do that you need to configure your Oauth Token as below –

Note that you need to use Password Credentials for to get a viable Token.

Now if you wander about the rest Client secrets, IDs and all, then just to clarify – this information is the same when you create the ABAP Trial tenant in SAP BTP. There are several blogs as well to give you idea about this API testing.

Anyways, moving forward. We are now a few step far away to call the ABAP Cloud Trial API from On-premise.

Note that ABAP BTP Trial gives us a Host which is dynamic and hosted in AWS as a Infrastructure Cloud. Hence we need to create a Custom proxy to the tenant Host

https://SECRET-4148-434a-YAYS-64b75d24911a.abap.us10.hana.ondemand.com

Now create a NodeJS adapter for proxy host –

var axios = require('axios');
var qs = require('qs');
var express = require('express')
var app = express()
var config = require('./.secret.json')

var tokenEndpoint = process.env['token_url'];
// console.log(tokenEndpoint)
if (tokenEndpoint) {
    tokenEndpoint = tokenEndpoint + '/oauth/token'
}
else {
    tokenEndpoint = config.uaa.url;
}

var clientId = process.env['client_id'];
if (!clientId) {
    clientId = config.uaa.clientid
}
var clientSecret = process.env['client_secret'];
if (!clientSecret) {
    clientSecret = config.uaa.clientsecret
}
var abaphost = process.env['abaphost'];
if (!abaphost) {
    abaphost = config.url
}
var emailid = process.env['emailid'];

if (!emailid) {
    emailid = config.emailid
}
var password = process.env['password'];
if (!password) {
    password = config.password
}
var port = process.env['nodeport'];
if (!port) {
    port = 8080
}

app.use(express.json());

async function getabapcloudodataresponse(url, accessToken, idToken) {
    console.log('I am here')
    console.log(url)
    try {
      const response = await axios.get(url, {
        headers: {
          Authorization: accessToken
        }
      });
      console.log('Response:', response.data);
      return response.data
      // Process the response
      
    } catch (error) {
      // Handle error
      console.error('Error:--------------------------------------------->')
      console.error(error);
    }
  }

app.get('*', async (req, res) => {
    // console.log(req.headers['authorization'])
    auth = req.headers['authorization']
    id_token = req.headers['x-id-token']
    var url = req.originalUrl;
    var fullUrl = abaphost + url;    
    var token_data = {
        access_token : auth,
        id_token : id_token
    }
    var responseABAP = await getabapcloudodataresponse(fullUrl, token_data.access_token, token_data.id_token);
    res.json(responseABAP);
})

app.listen(port)

console.log(`Server Listening in Port:: ${port}`)

Then create another file .secret.json and copy paste the Service Secret created for the ABAP Cloud Tenant.

Use manifest.yml to push your code to cloud foundry environment –

---
applications:
- name: abap_on_cloud
  random-route: false
  path: ./
  memory: 256M
  buildpack: nodejs_buildpack

After deploying this adapter to ABAP Cloud. Create new destinations in SM59 as below

This above destination is to get the Authorization Token. And the below SM59 destination is the proxy of the deployed NodeJS application –

We are almost done. Now here is a sample ABAP code to trigger ABAP BTP ODATA and consume it inside the ABAP S4 Onpremise.

*&---------------------------------------------------------------------*
*& Report ZTESTABAPCLOUD
*&---------------------------------------------------------------------*
*&
*&---------------------------------------------------------------------*
REPORT ztestabapcloud.
DATA: lo_http_client   TYPE REF TO if_http_client,
      lo_rest_client   TYPE REF TO cl_rest_http_client,
      lo_request       TYPE REF TO if_rest_entity,
      lv_uri           TYPE string,
      lv_username      TYPE string,
      lv_password      TYPE string,
      lv_client_id     TYPE string,
      lv_client_secret TYPE string,
      lv_token         TYPE string,
      lv_response      TYPE string,
      lt_fields        TYPE tihttpnvp,
      lt_response      TYPE STANDARD TABLE OF string.
PARAMETERS: p_matnr TYPE matnr,
            p_land  TYPE land1.

lv_uri = '/oauth/token'.
lv_username = 'sabarnXXYY@gmail.com'.
lv_password = 'XPASSWORDX'.
lv_client_id = 'sb-XXSECRET-cfef-4379-9d14-XXSECRET!b168955|abap-trial-service-broker!b3132'.
lv_client_secret = 'd7b6ec59-XXYY-4f5a-93f3-XXSECRET$RzEH2wP5xrEtL-XXSECRETZOZ1e268Q='.

cl_http_client=>create_by_destination(
 EXPORTING
   destination              = 'OAUTH_BTPABAP'    " Logical destination (specified in function call)
 IMPORTING
   client                   = lo_http_client    " HTTP Client Abstraction
 EXCEPTIONS
   argument_not_found       = 1
   destination_not_found    = 2
   destination_no_authority = 3
   plugin_not_active        = 4
   internal_error           = 5
   OTHERS                   = 6
).

lo_http_client->request->set_method( 'POST' ).

CALL METHOD cl_http_utility=>set_request_uri
  EXPORTING
    request = lo_http_client->request
    uri     = lv_uri.
lo_http_client->request->set_header_field( name = 'Content-Type' value = 'application/x-www-form-urlencoded' ).

DATA: lv_bodystr TYPE string.
lv_bodystr = 'grant_type=password&username=' && lv_username &&
             '&password=' && lv_password &&
             '&client_id=' && lv_client_id &&
             '&client_secret=' && lv_client_secret.


CREATE OBJECT lo_rest_client
  EXPORTING
    io_http_client = lo_http_client.
lo_request = lo_rest_client->if_rest_client~create_request_entity( ).

lo_request->set_string_data( lv_bodystr ).

lo_rest_client->if_rest_resource~post( lo_request ).

DATA(lo_response) = lo_rest_client->if_rest_client~get_response_entity( ).
DATA(http_status) = lo_response->get_header_field( '~status_code' ).
lv_response = lo_response->get_string_data( ).
DATA lr_json_deserializer TYPE REF TO cl_trex_json_deserializer.
TYPES: BEGIN OF ty_json_res,
         access_token TYPE string,
         id_token     TYPE string,
         token_type   TYPE string,
         expires_in   TYPE string,
         scope        TYPE string,
         jti          TYPE string,
       END OF ty_json_res.
DATA: json_res TYPE ty_json_res.
/ui2/cl_json=>deserialize(
  EXPORTING
  json = lv_response
  CHANGING
  data = json_res
  ).



START-OF-SELECTION.
  IF p_matnr IS NOT INITIAL AND p_land IS NOT INITIAL.

* ---------------------------------->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

    CLEAR: lv_uri, lo_http_client, lo_request, lo_rest_client, lv_response,lo_response,http_status.
    DATA: lo_http_client1 TYPE REF TO if_http_client,
          lo_rest_client1 TYPE REF TO cl_rest_http_client,
          lo_request1     TYPE REF TO if_rest_entity.
    cl_http_client=>create_by_destination(
     EXPORTING
       destination              = 'OAUTH_BTPABAP_SERVER'    " Logical destination (specified in function call)
     IMPORTING
       client                   = lo_http_client1    " HTTP Client Abstraction
     EXCEPTIONS
       argument_not_found       = 1
       destination_not_found    = 2
       destination_no_authority = 3
       plugin_not_active        = 4
       internal_error           = 5
       OTHERS                   = 6
    ).

*lo_http_client1->request->set_method( 'GET' ).
    lv_uri = '/sap/opu/odata4/sap/zsrv_def_material_details_v4/srvd/sap/zsrv_def_material_details/0001/ZCDS_P_MATERIAL'.

    DATA: lv_filter  TYPE string,
          lv_new_uri TYPE string.
    lv_filter = '?$filter=matnr%20eq%27' && p_matnr && '%27and%20land%20eq%27' && p_land && '%27'.
    "lv_new_uri = lv_uri && lv_filter.
    "lv_uri = lv_uri && lv_filter.
    "BREAK-POINT.
    CALL METHOD cl_http_utility=>set_request_uri
      EXPORTING
        request = lo_http_client1->request
        uri     = lv_uri.

*lo_http_client1->request->set_header_field( name = 'Content-Type' value = 'application/json;odata.metadata=minimal;charset=utf-8' ).

    DATA: lv_auth TYPE string.

    CONCATENATE 'Bearer ' json_res-access_token INTO lv_auth SEPARATED BY space.

    lo_http_client1->request->set_header_field( name = 'Authorization' value = lv_auth ).
    lo_http_client1->request->set_header_field( name = 'x-id-token' value = json_res-id_token ).

    CREATE OBJECT lo_rest_client1
      EXPORTING
        io_http_client = lo_http_client1.


    lo_rest_client1->if_rest_resource~get( ).

    lo_response = lo_rest_client1->if_rest_client~get_response_entity( ).
    http_status = lo_response->get_header_field( '~status_code' ).
    lv_response = lo_response->get_string_data( ).

    IF http_status EQ 200.
      TYPES: BEGIN OF ty_tax,
               matnr    TYPE string,
               land     TYPE string,
               matnrtax TYPE string,
             END OF ty_tax.
      TYPES: value TYPE TABLE OF ty_tax.
      DATA: ls_value TYPE ty_tax,
            lt_value TYPE value.

      DATA: lo_data TYPE REF TO data.
      FIELD-SYMBOLS: <lfs_data>   TYPE any,
                     <lfs_values> TYPE any,
                     <lfs_line>   TYPE   any.

      "DATA
      /ui2/cl_json=>deserialize(
        EXPORTING
        json = lv_response
        CHANGING
        data = lo_data
        ).
      ASSIGN lo_data->* TO <lfs_data>.
      "BREAK-POINT.
      ASSIGN COMPONENT 'VALUE' OF STRUCTURE <lfs_data> TO <lfs_values>.
      ASSIGN <lfs_values>->* TO FIELD-SYMBOL(<lfs_table>).
      LOOP AT <lfs_table> ASSIGNING <lfs_line>.
        "ASSIGN <lfs_line>->* T0 FIELD-SYMBOL(<lfs_table>).
        ASSIGN <lfs_line>->* TO FIELD-SYMBOL(<lfs_lineref>).

        ASSIGN COMPONENT 'MATNR'    OF STRUCTURE <lfs_lineref> TO FIELD-SYMBOL(<lfs_matnr>).
        ASSIGN COMPONENT 'LAND'     OF STRUCTURE <lfs_lineref> TO FIELD-SYMBOL(<lfs_land>).
        ASSIGN COMPONENT 'MATNRTAX' OF STRUCTURE <lfs_lineref> TO FIELD-SYMBOL(<lfs_matnrtax>).
        IF <lfs_matnr> IS ASSIGNED AND <lfs_land> IS ASSIGNED AND <lfs_matnrtax> IS ASSIGNED.
          ASSIGN <lfs_matnr>->* TO FIELD-SYMBOL(<lfs_any>).
          IF <lfs_any> IS ASSIGNED.
            ls_value-matnr = <lfs_any>.
          ENDIF.
          ASSIGN <lfs_land>->* TO <lfs_any>.
          IF <lfs_any> IS ASSIGNED.
            ls_value-land   = <lfs_any>.
          ENDIF.
          ASSIGN <lfs_matnrtax>->* TO <lfs_any>.
          IF <lfs_matnrtax> IS ASSIGNED.
            ls_value-matnrtax = <lfs_any>.
          ENDIF.
          APPEND ls_value TO lt_value.
          CLEAR: ls_value.
        ENDIF.
      ENDLOOP.
      "BREAK-POINT.
      DATA(lv_tax) = VALUE #( lt_value[ matnr = p_matnr land = p_land ]-matnrtax OPTIONAL ).
      DATA: lv_msg TYPE string.
      IF lv_tax IS NOT INITIAL.
        CONCATENATE 'Import Tax Amount for Material:'
                  p_matnr 'and Country :'
                  p_land 'is :'
                  lv_tax '%' INTO
                  lv_msg
        SEPARATED BY space.

      ELSE.
        CONCATENATE 'Import Tax Amount for Material:'
                  p_matnr 'and Country :'
                  p_land 'is not present in ABAP Cloud DB. Please maintain tax percentage.'
                  INTO
                  lv_msg
        SEPARATED BY space.

      ENDIF.
      MESSAGE lv_msg TYPE 'I'.

    ELSE.
      MESSAGE 'Error in ABAP Cloud Connection' TYPE 'E' DISPLAY LIKE 'I'.
      SET SCREEN 1000.
    ENDIF.
  ELSE.
    MESSAGE 'Please enter Material and land' TYPE 'E' DISPLAY LIKE 'I'.
  ENDIF.

We are done with all the configurations and developments. Lets test our program now from ABAP On-premise SE38 transaction:

Execute your program and get the response back from the ABAP Cloud OData:

Understandings

  1. While going through this POC you can get idea on how to call password credential Oauth with ABAP.
  2. You also create and can use the Oauth Profile from SE80
  3. You can leverage Cloud Applications and consume it easily from On-premise which will give robustness of your application designs.
  4. I have used a GET method while Creating a Proxy on top of ABAP Trial in NodeJS. In your scenario you can leverage POST operations as well to update back to ABAP Cloud BTP OData services.
Rating: 0 / 5 (0 votes)

The post Calling ABAP on Cloud Trial V4 ODATA from SAP S4 HANA On-premise Using ABAP – SM59 appeared first on ERP Q&A.

]]>