Technology Blogs by Members
Explore a vibrant mix of technical expertise, industry insights, and tech buzz in member blogs covering SAP products, technology, and events. Get in the mix!
cancel
Showing results for 
Search instead for 
Did you mean: 
hazem2020
Explorer

Overview:

This blog is a detailed technical guide for the use of the Developer Extensibility option to develop an E2E Application deployed and running on SAP Public Cloud launchpad with the help of the Restful Application Programming Model (RAP).

Case Background:

Create a custom Fiori application to save product data, the app would simply do the following:

  • The CRUD Operations
  • Calculate the total amount for the product (QTY ** Price)
  • Validate Price and Quantity
  • Set the product to be “Ready for Sale”.

We will go through some stages to have the final result:

  • Develop the backend service.
  • Establish the destination between the Backend and BTP
  • Consume the service in a Fiori element template using Business Application Studio.
  • Deploy the application to the public cloud launchpad.

Detailed Steps:

1- Develop Backend Service:

  • Create a database table

 

 

 

@EndUserText.label : 'Products Table'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #ALLOWED
define table zproducts {
  key client            : abap.clnt not null;
  key product_uuid      : sysuuid_x16 not null;
  product_name          : abap.char(40);
  product_description   : abap.char(120);
  @Semantics.amount.currencyCode : 'zproducts.product_currency'
  product_price         : abap.curr(23,2);
  product_currency      : abap.cuky;
  @Semantics.quantity.unitOfMeasure : 'zproducts.product_uom'
  product_qty           : abap.quan(23,2);
  product_uom           : meins;
  @Semantics.amount.currencyCode : 'zproducts.product_currency'
  total_amount          : abap.curr(23,2);
  ready_for_sale        : abap_boolean;
  last_changed_at       : timestampl;
  local_last_changed_at : timestampl;

}​

 

 

 

  • Create an interface CDS view on top of the DB table

 

 

 

@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Products Interface View'
define root view entity zi_products 
  as select from zproducts
{
  key product_uuid          as ProductUuid,
      product_name          as ProductName,
      product_description   as ProductDescription,
      product_price         as ProductPrice,
      product_currency      as ProductCurrency,
      product_qty           as ProductQuantity,
      product_uom           as ProductUOM,
      total_amount          as TotalAmount,
      ready_for_sale        as ReadyForSale,
      @Semantics.systemDateTime.lastChangedAt: true
      last_changed_at       as LastChangedAt,
      @Semantics.systemDateTime.localInstanceLastChangedAt: true
      local_last_changed_at as LocalLastChangedAt
}

 

 

 

  • Then we create a consumption view.

 

 

 

@EndUserText.label: 'Products Consumption View'
@AccessControl.authorizationCheck: #NOT_REQUIRED
@Search.searchable: true
@Metadata.allowExtensions: true
define root view entity zc_products
provider contract transactional_query
  as projection on zi_products as Products
{
  @EndUserText.label: 'Product ID'
  key ProductUuid,
  @Search.defaultSearchElement: true
  @EndUserText.label: 'Product Name'
      ProductName,
  @EndUserText.label: 'Product Description'    
      ProductDescription,
  @EndUserText.label: 'Product Price'    
      ProductPrice,
  @EndUserText.label: 'Product Currency'    
      ProductCurrency,
  @EndUserText.label: 'Product Quantity'    
      ProductQuantity,
  @EndUserText.label: 'Product UOM'
      ProductUOM,
  @EndUserText.label: 'Total Amount'    
      TotalAmount,
  @EndUserText.label: 'Product is Ready for Sale'    
      ReadyForSale,
      LastChangedAt,
      LocalLastChangedAt
}​

 

 

 

  • A metadata extension was created to adapt the application's UI

 

 

 

@Metadata.layer: #CORE

@UI:{ headerInfo:{ typeName:'Products',
                   typeNamePlural:'Products',
                   title:{ type: #STANDARD , label: 'Products' } } }
annotate view zc_products with
{

  @UI.facet: [{ id:'Products' , purpose: #STANDARD , type: #IDENTIFICATION_REFERENCE , label: 'Product Details', position: 10 }]

  @UI:{ lineItem: [{ position: 1  }, {  type: #FOR_ACTION,
                        dataAction: 'setToReadyForSale' ,
                        label: 'Ready For Sale' , invocationGrouping: #CHANGE_SET } ] ,
                        identification: [ { position: 1 } ]  }
  ProductUuid;
  @UI: {  lineItem: [ { position: 2 } ],
  identification: [ { position: 2 } ],
  selectionField: [ { position: 2 } ] }
  ProductName;                                         
  @UI: {  lineItem: [ { position: 3 } ],
  identification: [ { position: 3 } ],
  selectionField: [ { position: 3 } ] }
  ProductDescription;
  @UI: {  lineItem: [ { position: 4 } ],
  identification: [ { position: 4 } ] }
  ProductPrice;
  @UI: {  lineItem: [ { position: 5 } ],
  identification: [ { position: 5 } ] }
  ProductCurrency;
  @UI: {  lineItem: [ { position: 6 } ],
  identification: [ { position: 6 } ] }
  ProductQuantity;
  @UI: {  lineItem: [ { position: 7 } ],
  identification: [ { position: 7 } ] }
  ProductUOM;
  @UI: {  lineItem: [ { position: 8 } ],
  identification: [ { position: 8 } ] }
  TotalAmount;
  @UI: {  lineItem: [ { position: 9 } ],
  identification: [ { position: 9 } ] , 
  selectionField: [ { position: 9 } ] }
  ReadyForSale;
  @UI.hidden: true
  LastChangedAt;
  @UI.hidden: true
  LocalLastChangedAt;
}​

 

 

 

  • Create a behavior definition for the interface view with all the required operations.

 

 

 

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

define behavior for zi_products alias products
with additional save
persistent table zproducts
lock master
authorization master ( global )
etag master LocalLastChangedAt
{


  field ( readonly ) ReadyForSale, TotalAmount, LastChangedAt, LocalLastChangedAt;
  field ( numbering : managed , readonly ) ProductUuid;

  create;
  update;
  delete;



  action ( features : instance ) setToReadyForSale result [1] $self;
  determination calcTotalAmount on save { field ProductPrice , ProductQuantity ;  }
  validation validatePrice on save { field ProductPrice; create; }
  validation validateQTY on save { field ProductQuantity; create; }


  mapping for zproducts
    {
      ProductUuid        = product_uuid;
      ProductName        = product_name;
      ProductDescription = product_description;
      ProductPrice       = product_price;
      ProductCurrency    = product_currency;
      ProductQuantity    = product_qty;
      ProductUOM         = product_uom;
      TotalAmount        = total_amount;
      ReadyForSale       = ready_for_sale;
      LastChangedAt      = last_changed_at;
      LocalLastChangedAt = local_last_changed_at;
    }
}​

 

 

 

  •  Create a behavior definition for the consumption view.

 

 

 

projection;
strict ( 2 );

define behavior for zc_products alias Products
{
  use create;
  use update;
  use delete;

  use action setToReadyForSale;
}​

 

 

 

  • Implement the Logic in a behavior implementation class

 

 

 

CLASS lhc_products DEFINITION INHERITING FROM cl_abap_behavior_handler.
  PRIVATE SECTION.

    METHODS get_instance_features FOR INSTANCE FEATURES
      IMPORTING keys REQUEST requested_features FOR products RESULT result.

    METHODS get_global_authorizations FOR GLOBAL AUTHORIZATION
      IMPORTING REQUEST requested_authorizations FOR products RESULT result.

    METHODS settoreadyforsale FOR MODIFY
      IMPORTING keys FOR ACTION products~settoreadyforsale RESULT result.

    METHODS calctotalamount FOR DETERMINE ON SAVE
      IMPORTING keys FOR products~calctotalamount.

    METHODS validateprice FOR VALIDATE ON SAVE
      IMPORTING keys FOR products~validateprice.

    METHODS validateqty FOR VALIDATE ON SAVE
      IMPORTING keys FOR products~validateqty.

ENDCLASS.

CLASS lhc_products IMPLEMENTATION.

  METHOD get_instance_features.
  ENDMETHOD.

  METHOD get_global_authorizations.
  ENDMETHOD.


  METHOD settoreadyforsale.


    MODIFY ENTITIES OF zi_products IN LOCAL MODE
    ENTITY products
    UPDATE FIELDS ( readyforsale )
    WITH VALUE #( FOR key IN keys
                                  ( %key = key-%key
                                    readyforsale = 'X' ) )
    FAILED failed
    REPORTED reported .

  ENDMETHOD.

  METHOD calctotalamount.


    READ ENTITIES OF zi_products IN LOCAL MODE
    ENTITY products
    FIELDS ( productprice  productquantity ) WITH CORRESPONDING #( keys )
    RESULT DATA(lt_products) .

    LOOP AT lt_products INTO DATA(ls_prd) .

      DATA(lv_result) = ls_prd-productprice * ls_prd-productquantity .

      MODIFY ENTITIES OF  zi_products IN LOCAL MODE
      ENTITY products UPDATE
      FIELDS ( totalamount ) WITH VALUE #( ( %key = ls_prd-%key
                                             totalamount = lv_result ) ) .
    ENDLOOP.

  ENDMETHOD.

  METHOD validateprice.
    READ ENTITIES OF zi_products IN LOCAL MODE
    ENTITY products
    FIELDS ( productprice ) WITH CORRESPONDING #( keys )
    RESULT DATA(lt_products) .

    LOOP AT lt_products INTO DATA(ls_prd) .
      IF ls_prd-productprice < 1 .
        APPEND VALUE #( %tky = ls_prd-%tky ) TO failed-products .
      ENDIF.
    ENDLOOP.
  ENDMETHOD.

  METHOD validateqty.
    READ ENTITIES OF zi_products IN LOCAL MODE
    ENTITY products
    FIELDS ( productquantity ) WITH CORRESPONDING #( keys )
    RESULT DATA(lt_products) .

    LOOP AT lt_products INTO DATA(ls_prd) .
      IF ls_prd-productquantity < 1 .
        APPEND VALUE #( %tky = ls_prd-%tky ) TO failed-products .
      ENDIF.
    ENDLOOP.
  ENDMETHOD.

ENDCLASS.

CLASS lsc_zi_products DEFINITION INHERITING FROM cl_abap_behavior_saver.
  PROTECTED SECTION.

    METHODS save_modified REDEFINITION.

    METHODS cleanup_finalize REDEFINITION.

ENDCLASS.

CLASS lsc_zi_products IMPLEMENTATION.

  METHOD save_modified.
  ENDMETHOD.

  METHOD cleanup_finalize.
  ENDMETHOD.

ENDCLASS.​

 

 

 

  • Create a service definition exposing the consumption view

 

 

 

@EndUserText.label: 'Products Service definition'
define service ZD_products {
  expose zc_products as Products;
}​

 

 

 

  • Create a service binding (V2 OData – UI Service)
  • Application Previewinghazem2020_1-1706316796349.png

     

2- The destination between the Backend and BTP:

  •        In the connectivity section, we open destinations.
  •        create a new destination according to the below Guidance provided by SAP.

    Field Name

    Value

    Name

    <YOUR_SYSTEMS_ID>_SAML_ASSERTION

    Type

    HTTP

    Description

    SAML Assertion Destination to SAP S/4HANA Cloud system <YOUR_SYSTEMS_ID>

    URL

    In the SAP S/4HANA Cloud system, navigate to the Communication Systems app and copy the Host Name from Own SAP Cloud System = Yes

    hazem2020_2-1706317781623.png

     

    and paste it with prefix https:// for example https://my12345-api.s4hana.ondemand.com.

    Proxy Type

    Internet

    Authentication

    SAMLAssertion

    Audience

    Enter the URL of your system and remove -api, for example https://my12345.s4hana.ondemand.com.

    AuthnContextClassRef

    urn:oasis:names:tc:SAML:2.0:ac:classes:PreviousSession

    Select New Property and maintain the following Additional Properties and values.

    Field Name

    Value

    HTML5.DynamicDestination

    true

    HTML5.Timeout

    60000

    WebIDEEnabled

    true

    WebIDEUsage

    odata_abap,dev_abap

    nameIDFormat

    urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress

3- Service consumption in BAS:

  • From the services section open the instances and subscriptions and open BAS(we assume you subscribe to BAS)
  • Create a new DEV space of the type “SAP FIORI” and then run it.hazem2020_3-1706318469125.png

     

  • Run and Open the dev space
  • Create a new SAP Fiori App and choose the service we created before (It will appear automatically if the destination was created successfully) .

    hazem2020_6-1706318603727.png

  • Choose the entity.
  • Add the project attributes and be sure the deployment and FLP are checked .
     
     

    BAS3.png

  • Provide Deployment Configurationbas5.png
  • launchpad Configurationbas7.png
  • Start the Application.

 

  • bas9.png

4- Deployment to Public Cloud Launchpad:

  • Choose Deploy from application information page – choose (Y) from the terminal
  • The deployment is successful when you receive messages like thisbtpapp4-1.png
  • From Eclipse Create IAM app and add the created service from service tabhazem2020_10-1706319839797.png
  • Create a business catalog – and add the IAM app to it then publish locallyhazem2020_11-1706319879988.png
  • Add the ui5 app ID to your IAM then activate and publishhazem2020_12-1706319925711.png
  •  Publish your business catalog
  •  From your cloud launchpad – open “Maintain Business Roles” app – and create new role
  •   Add the created business catalogBR-2.png
  • Assign your user
  • Change the restrictions to be unrestricted
  • BR -4.png
  • From “Manage launchpad space” create new space and pageBR - 5.png
  • Again open the role and assign the created space
  • Go to maintain launchpad pages – open the created page – Edit – add description and choose the business catalog by clicking addBR-7.png
  • Now you can test preview from "page preview"
  • The app is deployed, assigned to your user, and ready for useBR-8.png

Conclusion:

      At the end I hope this blog was helpful and detailed for the whole process of creating Cloud Application. In the coming blogs we will build more complex Application which is interacted with standard entities such as (Purchase order , Sales orders , etc...)

References:

  • Developer Extensibility Documentation here
  • Developer Extensibility Overview here
  • Destination and Deployment here
  • Developing Extensions using ABAP Environment here
2 Comments
SRINIVAS_KATTA
Explorer

Thank you @hazem2020. Helpful blog.

AbdelrahmanZaki
Explorer

very helpful blog hazem keep up the great work 

Labels in this area