Skip to Content
Technical Articles

ABAP Programming Model for SAP Fiori (Draft based) for NON GUID Keys & Much more..

Hello Everyone,

This blog is about creating a Transactional capable(Draft) Fiori application using the SAP ABAP  Programming model for SAP Fiori with end-to-end till saving to db.

I am not trying to duplicate the help content. This is created for the following reasons:

  1. This is actually a reference blog for my another blog about “Durable Locks” and how to configure them in the CDS views. So reading this will help you a lot in understanding that durable locks blog 🙂 Link
  2. This gives some technical insights, information & links about the draft framework.
  3. This uses the same example from the SAP help but instead of the GUIDs for the key fields, it uses the normal fields as most of the legacy solutions are not based on the GUIDs or a fresh Greenfield solution. So it will be easy for you guys to map the differences between GUID(help) & NON GUID draft application(this blog).
  4. I will also discuss about an issue that we usually face while doing this NON GUID key based scenario.

Links on this topic:

https://help.sap.com/viewer/cc0c305d2fab47bd808adcad3ca7ee9d/1809.000/en-US/3b77569ca8ee4226bdab4fcebd6f6ea6.html

https://help.sap.com/viewer/cc0c305d2fab47bd808adcad3ca7ee9d/7.52.2/en-US/3b77569ca8ee4226bdab4fcebd6f6ea6.html

 

I recommend you guys to check the blog by Diego Borja on draft enabled Fiori app for the custom/standard tables.

https://blogs.sap.com/2018/06/24/abap-programming-model-for-fiori-transactional-apps-with-draft-capabilities-using-standard-tables/

 

This is targeted for the people who have a basic knowledge in CDS views, OOABAP & Fiori and helps to kick start your development in this new framework.

Introduction:

From Help:

The ABAP programming model for SAP Fiori defines the architecture for efficient end-to-end development of intrinsically SAP HANA-optimized Fiori apps in SAP S/4HANA. It supports the development of all types of Fiori applications like transactional, search, analytics and planning apps and is based on customer-proven technologies and frameworks such as Core Data Services (CDS) for defining semantically rich data models, OData protocol, ABAP-based application services for custom logic and SAPUI5-based user interface.

For more information about this new programming model, check the SAP Help.

Using the ABAP Programming model for Fiori, you can develop two kinds of applications

“Transactional apps ‘without’ the Draft features”:

If the applications are developed without Draft, there are many limitations & issues:

  1. You cannot create an item and its subitems at the same time. e.g., you cannot create the sales order header and it’s item at the same time. You have to save the header first and later you have to create the items one by one.
  2. You cannot dynamically make the fields visible/hidden/editable/readonly. It has very limited field control capabilities.
  3. Exclusive locking like the locking in GUI/Webdynpro applications is not available.
  4. Context depended search help is not there.. e.g., if you enter a country, you need to show only that entered country states, this is not possible.
  5. and many more, you can check the links

Links:

https://help.sap.com/viewer/cc0c305d2fab47bd808adcad3ca7ee9d/1809.000/en-US/971e03cd952a47458e57f87fc566a8f3.html

https://ui5.sap.com/#/topic/a90c55840b144f2ebc2d836adbc1a54f

“Transactional apps ‘with’ the Draft features” 

(Available from 7.51 SP02)

If we develop the apps with Draft capabilities, then we will have all the above and much more awesome features.

So what is this “Draft”..?  Like the name “Draft” suggests, It’s something that is not final, its just a temporary version.

https://experience.sap.com/fiori-design-web/draft-handling/

https://help.sap.com/viewer/cc0c305d2fab47bd808adcad3ca7ee9d/1809.000/en-US/d36820f082c84085b6634be4576e351a.html

When we create or edit an entry in the Draft enabled Fiori app, an entry is created in the draft table(which is configured in the CDS view). When we enter any data in the inputs, a request will go and save the data to the draft table. So even after refreshing the browser, the data which we entered before refreshing the Fiori app in the edit mode will be retrieved back.

What we will get by using this so called “Draft” features while developing the new Fiori apps.

  1. The much awaited and most wanted feature “Not the Draft” if you are wondering 😀 (atleast for me).. But it is the exclusive locking- “the Durable locks”, which were not possible with fiori till sometime backBTW I will have another blog on this topic 🙂
  2. Now of course the “Draft” feature itself- Data loss protection.
  3. Device Switch: So using this, you can start on one device(laptop) and continue the changes on another device(mobile/tablet).
  4. This will not have any limitations that I mentioned for “Transactional apps without using the Draft features”

So let’s see how it works:

The above image is taken from the SAP UI5 documentation, link available below. It has a very good information about the draft features.

https://ui5.sap.com/#/topic/ed9aa41c563a44b18701529c8327db4d

Active data: original data

Draft data: temporary data(copy of active data with the new changes)., which will be removed after the final save.

Taking the BP example here.

  • First if we open an existing BP and click on Edit, it will create the draft version for the actual version and from here on, all the changes you do there will be saved in the draft version.
  • If we open the role of the business partner and change some data there, roles data will be saved in the role draft table.
  • So after clicking on the SAVE, it will save all the data from the draft version to the active version and deletes the draft entries.

This draft table is not the actual table, it is the copy of the original table, which we will define in the CDS view. BTW we do not need to create this table manually, SAP will create it automatically when the CDS view is generated.

An overview of how to develop the Draft capable apps using this framework:

  1. Create the Base CDS views to fetch the data from the DB tables,
  2. Create the “Transactional” type CDS view which will be draft enabled. Here we will just mention the draft table name, SAP framework will create the draft table automatically based on the CDS view.
  3. After the activation of the the CDS view, the framework will create the BOPF object, where all our transactional processing will be handled like Field control, validations, lockings, CRUD operations etc.,
  4. Then we will create the Consumption view and will provide the annotations, which the Smart Fiori template will use and generate the UI dynamically without us writing the UI5 code

To be frank, there is no big difference between the draft & non draft in terms of development for us, it’s mostly taken care by SAP 🙂 but the main difference comes in terms of functionality.

Now let’s see the development part of it.

Development:

This example will be very similar to the one that was provided in the help, please check the help  links that I’ve provided first before going through this. The only thing that changes in this is instead of using the GUID as the key field, I will use a normal character field.

Before going to the Development part, let’s see a small demo of it.

 

 

Tables:

Let’s create 2 new custom tables.( we can use your existing custom tables or standard tables ).

  • Sales Order header

CLIENT

MANDT(Key)

SALESORDER

VBELN(Key)

BUSINESSPARTNER

SNWD_PARTNER_ID

OVERALLSTATUS

SNWD_SO_OA_STATUS_CODE

CHANGEDAT

TIMESTAMPL

CREATEDAT

TIMESTAMPL

CHANGEDBY

UNAME

CREATEDBY

UNAME

  • Sales Order Item

CLIENT

MANDT (Key)

SALESORDER

VBELN (Key)

SALESORDERITEM

SNWD_SO_ITEM_POS (Key)

PRODUCT

SNWD_PRODUCT_ID

GROSSAMOUNT

SNWD_TTL_GROSS_AMOUNT

CURRENCYCODE

SNWD_CURR_CODE

QUANTITY

INT4

Base CDS Views:

We create the base CDS views to fetch the data from the actual database tables, we usually put the logic in these views like doing some conversions or calculations or text changes or writing some join and fetching any data if required. But here in our case we won’t do anything as this is just a basic app.

Sales order Header

@AbapCatalog.sqlViewName: 'ZVSOH'
@AbapCatalog.compiler.compareFilter: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Sales Orders Headers'
@VDM: {
    viewType: #BASIC
}
define view ZSalesOrderHeaders
  as select from zso_head
{
  key zso_head.salesorder,
      zso_head.businesspartner,
      zso_head.overallstatus,
      zso_head.changedat,
      zso_head.createdat,
      zso_head.changedby,
      zso_head.createdby
}

Sales order Items

@AbapCatalog.sqlViewName: 'ZVSOI'
@AbapCatalog.compiler.compareFilter: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Sales Order Items'
@VDM: {
    viewType: #BASIC
}
define view ZSalesOrderItems
  as select from zso_items
{
  key zso_items.salesorder,
  key zso_items.salesorderitem,
      zso_items.product,
      zso_items.grossamount,
      zso_items.currencycode,
      zso_items.quantity
}

 

Transaction type CDS Views & BOPF BO:

To enable the draft functionality and Transnational processing.

Sales Order Header

@AbapCatalog.sqlViewName: 'ZSOHTP'
@AbapCatalog.compiler.compareFilter: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Sales Order Header BO Layer'
@VDM: {
    viewType: #TRANSACTIONAL
}
//****---->>>> Add the below objectModel code after activation of both header and items
@ObjectModel: {
    transactionalProcessingEnabled: true,
    compositionRoot: true,
    createEnabled: true,
    updateEnabled: true,
    deleteEnabled: true,
    draftEnabled: true,
    writeDraftPersistence: 'zsoh_d',
    semanticKey: [ 'salesorder' ]
}

define view ZI_SalesOrdersHeadTP
  as select from ZSalesOrderHeaders as Header
  // Assocations
  association [0..*] to ZI_SalesOrderItems           as _items           on _items.salesorder = $projection.salesorder
  // Values Helps
  association [0..1] to SEPM_I_BusinessPartner       as _BusinessPartner on $projection.businesspartner = _BusinessPartner.BusinessPartner
  association [0..1] to Sepm_I_SalesOrdOverallStatus as _Status          on $projection.overallstatus = _Status.SalesOrderOverallStatus
{
      @ObjectModel.readOnly: true
  key Header.salesorder,
      @ObjectModel.foreignKey.association: '_BusinessPartner'
      Header.businesspartner,
      @ObjectModel.foreignKey.association: '_Status'
      Header.overallstatus,
      @ObjectModel.readOnly: true
      Header.changedat,
      @ObjectModel.readOnly: true
      Header.createdat,
      @ObjectModel.readOnly: true
      Header.changedby,
      @ObjectModel.readOnly: true
      Header.createdby,
      @ObjectModel.association: {
          type: [ #TO_COMPOSITION_CHILD ]
      }
      _items,
      
      // Value help assocations
      _Status,
      _BusinessPartner
}

For more information about the objectmodel check the link below:

https://help.sap.com/viewer/cc0c305d2fab47bd808adcad3ca7ee9d/1809.000/en-US/d36820f082c84085b6634be4576e351a.html

The ObjectModel annotations provides the draft & transactional capabilities to the CDS view.

transactionalProcessingEnabled: true,
compositionRoot: true,

The above two will tell that CDS view has transaction processing is enabled and this particular CDS view is the root view(top level view).

draftEnabled: true,
writeDraftPersistence: 'zsoh_d',

The above two will tell that the CDS view is having the draft capabilities and we will just propose a draft table name. The draft table will be auto generated when the view is activated.

semanticKey: [ 'salesorder' ]

semanticKey tells the SAP framework what the key is, it is used mostly for the CDS views which were developed using the GUID as the key field. Using this SAP will understand what the actual key is instead of the GUID.

This is also used by the List template UI5 application to show “Draft” status under the column in the List report app, which it identifies based on the semantickey. So this is better to give for all the CDS views.

      @ObjectModel.foreignKey.association: '_BusinessPartner'
      Header.businesspartner,

The above are for the value helps

@ObjectModel.readOnly: true

This will make the tell the view that the column is be readonly.

@ObjectModel.association: {
  type: [ #TO_COMPOSITION_CHILD ]
}
_items

This will create the association from sales order header to sales order items in the Business Object.

 

Sales Order Items

@AbapCatalog.sqlViewName: 'ZSOITP'
@AbapCatalog.compiler.compareFilter: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Sales Order Items BO Layer'
@VDM: {
    viewType: #TRANSACTIONAL
}
//****---->>>> Add the below objectModel code after activation of both header and items
@ObjectModel:{
    createEnabled: true,
    updateEnabled: true,
    deleteEnabled: true,
    writeDraftPersistence: 'zsoi_d',
   semanticKey:['salesorder', 'salesorderitem']
}
define view ZI_SalesOrderItems
  as select from ZSalesOrderItems as Items
  association [1..1] to ZI_SalesOrdersHeadTP as _header   on _header.salesorder = $projection.salesorder
  // Value Help
  association [0..1] to SEPM_I_Product_E     as _product  on $projection.product = _product.Product
  association [0..1] to SEPM_I_Currency      as _currency on $projection.currencycode = _currency.Currency
{
      @ObjectModel.readOnly: true
  key Items.salesorder,
      @ObjectModel.readOnly: true
  key Items.salesorderitem,
      @ObjectModel.mandatory: true
      @ObjectModel.foreignKey.association: '_product'
      Items.product,
      @Semantics.amount.currencyCode: 'currencycode'
      Items.grossamount,
      @ObjectModel.foreignKey.association: '_currency'
      @Semantics.currencyCode: true
      Items.currencycode,
      Items.quantity,
      @ObjectModel.association: {
          type: [ #TO_COMPOSITION_PARENT, #TO_COMPOSITION_ROOT ]
      }
      _header,
      _product,
      _currency
}

Here we just need to mention the draftpersistance table and we do not need to mention the draftEnabled and transactionalProcessingEnabled as the parent view takes this child view data and use it in the BO.

Other things are same as the above, we just need to mention the association to the parent and the root.

So after create the associations and all add the object model to both the header and item cds view and activate them, this will create the BOPF BO and the Draft tables.

 

How to check the created BO? Just hover on the header CDS view, where the “transactionalProcessingEnabled” is set as true.

Now click on the Business Object, which will navigate to the BO.

Now click on the “Go to the ROOT node” and open the Draft class that is generated.

So what happens is that when we click on create button in the Fiori app, a draft entry will be created in the auto generated “Draft Table”. Then if we enter any of the data in the fiori app, it will saved to the draft table.

(All the logic regarding this will be taken care by the BOPF framework to create the draft entry, to delete and to update using the above class)

So what we need to do is to write the code to save the Draft data from the draft table to the Actual DB tables. To do this, we need to implement the code for the method:  copy_draft_to_active_entity. I’ve provide comments inside the code to understand what is going on. The below code will take care of the create and update scenarios.

I am using the direct DB updates, instead you can use the BAPI  or Update FMs.

 DATA:
            ls_msg TYPE symsg.
" Get the message container, to push any messages to the UI if requred
    IF eo_message IS NOT BOUND.
      eo_message = /bobf/cl_frw_message_factory=>create_container( ).
    ENDIF.
" header and items data declaration
    DATA(lt_header) = VALUE ztisalesordersheadtp( ).
    DATA(lt_items) = VALUE ztisalesorderitems( ).

    "Read Header Data
    "Pass the node key, which tells if we are trying to read header or item, in this case header
    "Pass the draft keys, if we create an entry in the Fiori, they will be saved as draft intitial, those keys we will pass to get the data
    io_read->retrieve(
      EXPORTING
        iv_node       = zif_i_salesordersheadtp_c=>sc_node-zi_salesordersheadtp
        it_key        = it_draft_key
        iv_fill_data = abap_true
      IMPORTING
        et_data       = lt_header
    ).
    " Read the Items data
    " We need to read via the assocation, so passing the assocation key here
    io_read->retrieve_by_association(
      EXPORTING
        iv_node                 = zif_i_salesordersheadtp_c=>sc_node-zi_salesordersheadtp                 " Node Name
        it_key                  = it_draft_key                 " Key Table
        iv_association          = zif_i_salesordersheadtp_c=>sc_association-zi_salesordersheadtp-_items
        iv_fill_data = abap_true
      IMPORTING
        et_data                 = lt_items                 " Data Return Structure
    ).
    " Always 1 header only expected as we can only create one sales order at a time
    DATA(ls_so_header) = CORRESPONDING zso_head( lt_header[ 1 ] ).

    " Updating the header
    GET TIME STAMP FIELD ls_so_header-changedat.
    ls_so_header-changedby = sy-uname.
    " I am checking if the sales order number is initial, it means it is the new entry
    " Else it is an existing entry
    " Alternatively we can also use lt_header[ 1 ]-HASACTIVEENTRY, which will tell if the active entry is available
    " If the active entry is available means it is update sceanrio else it is create scenario
    IF ls_so_header-salesorder IS NOT INITIAL.
      MODIFY zso_head FROM ls_so_header.
    ELSE.
      GET TIME STAMP FIELD ls_so_header-createdat.
      ls_so_header-createdby = sy-uname.

      CALL FUNCTION 'NUMBER_GET_NEXT'
        EXPORTING
          nr_range_nr = '01'
          object      = 'ZSONR'
        IMPORTING
          number      = ls_so_header-salesorder.

      INSERT zso_head FROM ls_so_header.
    ENDIF.

    " Now update/create the items
    DATA:
            lt_items_save TYPE TABLE OF zso_items.
    " Updating the items
    SELECT COUNT(  * )
      FROM zso_items
      INTO @DATA(lv_count)
     WHERE salesorder = @ls_so_header-salesorder.
    " Setting the item number here.
    LOOP AT lt_items REFERENCE INTO DATA(lo_item) WHERE hasactiveentity EQ abap_false.
      lv_count = lv_count + 1.
      lo_item->salesorder = ls_so_header-salesorder.
      lo_item->salesorderitem = lv_count.
      CALL FUNCTION 'CONVERSION_EXIT_ALPHA_INPUT'
        EXPORTING
          input  = lo_item->salesorderitem
        IMPORTING
          output = lo_item->salesorderitem.                " Internal display of INPUT, any category
    ENDLOOP.

    lt_items_save = CORRESPONDING #( lt_items ).
    IF lt_items_save IS NOT INITIAL.
      MODIFY zso_items FROM TABLE lt_items_save.
    ENDIF.
    
    " Now we need to tell the BOPF framework to delete the draft entries as we have successfully created the data in the DB
    " But BOPF only understands the data in in GUIDs, so we need to convert the sales order number to BOPF key, we just need to parent key
    DATA(lr_key_util) = /bobf/cl_lib_legacy_key=>get_instance( zif_i_salesordersheadtp_c=>sc_bo_key ).

    DATA(lv_bobf_key) = lr_key_util->convert_legacy_to_bopf_key(
                            iv_node_key   = zif_i_salesordersheadtp_c=>sc_node-zi_salesordersheadtp
                            is_legacy_key = ls_so_header-salesorder ).

    APPEND VALUE #( draft = it_draft_key[ 1 ]-key active = lv_bobf_key ) TO et_key_link.

Issue in the Update Scenario for the Non GUID key Fields:

When it comes to Update scenario(Sales order is already created and saved) and if you try to create the new sales order items, the items will be added but will not be displayed in the Fiori app till we save it to the database.

This issue is coming because the framework will not know copy the “SalesOrderNo” from header to the newly created “Draft SO Item”. So we have to update the draft entry with the salesorder number manually in the code. Then the app will understand that the SO item belongs to the SO header.

For the Create scenario, it will not happen.

To fix this, we need to create a determination for the BOPF BO sales order item. So this determination will be called at the time of create and update.

BTW I found this solution in the Standard BP Fiori App which also uses the non GUID key fields.

The code is provided with enough comments to understand.

    " This is used in the update scenario of the sales order(which has already been created) and when we are creating sales order items    
    DATA:
      lt_header TYPE ztisalesordersheadtp,
      lt_items  TYPE ztisalesorderitems.
      " read the sales order items
    io_read->retrieve(
      EXPORTING
        iv_node                 = zif_i_salesordersheadtp_c=>sc_node-zi_salesorderitems                " Node Name
        it_key                  = it_key
      IMPORTING
        et_data                 = lt_items
    ).
    
    " Obviously one item is only expected, check if the sales order number is initial for the draft entry
    READ TABLE lt_items REFERENCE INTO DATA(lo_item) INDEX 1.
    IF lo_item->salesorder IS INITIAL.
    " If initial, then get the header draft entry, which will have the sales order number via the assocation from child to header
      io_read->retrieve_by_association(
        EXPORTING
          iv_node                 = zif_i_salesordersheadtp_c=>sc_node-zi_salesorderitems                 " Node Name
          it_key                  = it_key                 " Key Table
          iv_association          = zif_i_salesordersheadtp_c=>sc_association-zi_salesorderitems-to_root
          iv_fill_data            = abap_true
        IMPORTING
          et_data                 = lt_header
      ).
     " Update the salesorder item with the so number
      lo_item->salesorder = lt_header[ key = lo_item->parent_key ]-salesorder.
      io_modify->update(
        EXPORTING
          iv_node           = zif_i_salesordersheadtp_c=>sc_node-zi_salesorderitems                 " Node
          iv_key            = lo_item->key                 " Key
          iv_root_key       = lo_item->root_key                 " NodeID
          is_data           = lo_item
      ).
    ENDIF.

For deletion, we have any action for that

Go to the auto generated class and write the code to delete the sales order.

   " To get only the active data as this code wil trigger for draft deletion as well
    " For draft deletion the framework wil take care, for active, we need to.
    /bobf/cl_lib_draft=>/bobf/if_lib_union_utilities~separate_keys(
      EXPORTING iv_bo_key = is_ctx-bo_key
        iv_node_key = is_ctx-node_key
        it_key = it_key
      IMPORTING
        et_active_key = DATA(lt_active_bopf_keys)
        ).
    "we will get the data in BOPF keys format, we need to get the sales order number from that
    CHECK lt_active_bopf_keys IS NOT INITIAL.
    DATA(ls_header) = VALUE zsk_isalesordersheadtp_active(  ).
    " Convert the bopf key to active document key
    /bobf/cl_lib_draft=>/bobf/if_lib_union_utilities~get_active_document_key(
      EXPORTING iv_bo_key = is_ctx-bo_key
        iv_node_key = is_ctx-node_key
        iv_bopf_key = lt_active_bopf_keys[ 1 ]-key
      IMPORTING es_active_document_key = ls_header ).
    " Delete the data
    DELETE FROM zso_head WHERE salesorder = ls_header-salesorder.
    DELETE FROM zso_items WHERE salesorder = ls_header-salesorder.

So till here we completed the business object logic. Now lets see how to create the odata service and generate the Fiori app.

 

Consumption Processing CDS Views:

Create the Consumption CDS views for both the header and the items.

Sales Order Header

@AbapCatalog.sqlViewName: 'ZCSOH'
@AbapCatalog.compiler.compareFilter: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Sales Orders Headers Consumption View'
@VDM.viewType: #CONSUMPTION
@Metadata: {
    allowExtensions: true
}
@ObjectModel.transactionalProcessingDelegated: true
@ObjectModel: {
    compositionRoot: true,
    createEnabled: true,
    updateEnabled: true,
    deleteEnabled: true,
    draftEnabled: true
}

@ObjectModel.semanticKey: [ 'salesorder' ]

@OData: {
    publish: true
}
define view ZC_SalesOrdersHead
  as select from ZI_SalesOrdersHeadTP as SalesOrderHeader
  association [0..*] to ZC_SalesOrderItems as _items on _items.salesorder = $projection.salesorder
{
      @UI.selectionField: [{
          position: 10
      }]
      @UI.lineItem.position: 10
      @UI.identification: [{
          position: 10
      }]
  key SalesOrderHeader.salesorder,
      @UI.lineItem.position: 20
      @UI.identification: [{
          position: 20
      }]
      SalesOrderHeader.businesspartner,
      @UI.lineItem.position: 30
      @UI.identification: [{
          position: 30
      }]
      SalesOrderHeader.overallstatus,
      @UI.lineItem.position: 40
      @UI.lineItem.label:'Created on'
      @UI.identification: [{
          position: 40,
          label:'Created on'
      }]
      SalesOrderHeader.createdat,
      @UI.lineItem.position: 50
      @UI.lineItem.label:'Created by'
      @UI.identification: [{
          position: 50,
          label:'Created by'
      }]
      SalesOrderHeader.createdby,
      @UI.lineItem.position: 60
      @UI.lineItem.label: 'Changed on'
      @UI.identification: [{
          position: 60,
          label:'Changed on'
      }]
      SalesOrderHeader.changedat,
      @UI.lineItem.position: 70
      @UI.lineItem.label:'Changed by'
      @UI.identification: [{
          position: 70,
          label:'Changed by'
      }]
      SalesOrderHeader.changedby,
      @ObjectModel.association: {
          type: [ #TO_COMPOSITION_CHILD ]
      }
      _items,
      // Associations
      SalesOrderHeader._BusinessPartner,
      SalesOrderHeader._Status
}

It is mandatory to mention the ObjectModel & Associations again here as they are utilized for the Odata service generation. This is because they both are having View scope, this documentation you can find in the help.

CDS rule: Remember to double-maintain the annotations that have the VIEW scope. In CDS views, only the annotations with ELEMENT and ASSIOCIATION scope are inherited from the business object view.

@UI.lineItem.position: 50
@UI.identification: [{
    position: 50
}]

Line item position is to for showing the item in the table and identification position is to show the it i the form, when opened.

@OData: {
    publish: true
}

This will auto create the Odata service for us.

Sales Order Items

@AbapCatalog.sqlViewName: 'ZCSOI'
@AbapCatalog.compiler.compareFilter: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Sales Order Items Consumption View'
@VDM.viewType: #CONSUMPTION
@Metadata: {
    allowExtensions: true
}
@ObjectModel: {
   semanticKey:['salesorder', 'salesorderitem'],

   createEnabled: true,
   deleteEnabled: true,
   updateEnabled: true
}
define view ZC_SalesOrderItems
  as select from ZI_SalesOrderItems as Items
  association [1..1] to ZC_SalesOrdersHead as _header on _header.salesorder = $projection.salesorder
{
      @UI.hidden: true
  key Items.salesorder,
      @UI.identification.position: 10
      @UI.lineItem.position: 10
  key Items.salesorderitem,
      @UI.identification.position: 20
      @UI.lineItem.position: 20
      Items.product,
      @UI.identification.position: 30
      @UI.lineItem.position: 30
      Items.grossamount,
      @UI.lineItem.label: 'Currency'
      @UI.hidden: true
      Items.currencycode,
      @UI.lineItem.label: 'Quantity'
      @UI.lineItem.position: 50
      @UI.identification: [{
      position: 50,
          label: 'Quantity'
      }]
      Items.quantity,
      @ObjectModel: {
          association: {
              type: [ #TO_COMPOSITION_PARENT, #TO_COMPOSITION_ROOT ]
          }
      }
      _header,
      Items._product,
      Items._currency 
}

Now active both the views, which will create the odata service. We just need to go to the /n/IWFND/MAINT_SERVICE and register the odata service.

 

So now the consumption view part is done, so we just need to use the webide to generate the list report app from the odata service that we just registered.

 

Fiori App using the List report template:

There are a lot of blogs out there where they show the process of generating the app based on the template using the webide.

Now run the app, which will be same as the app that was shown in the above video.

 

I strongly recommend you guys to try out the example in the SAP help as well. It has very good documentation(especially for the GUID keys based).

 

Please let me know if you have any questions. Also, check out my other blog on “Durable Locks”.

https://blogs.sap.com/2019/01/08/abap-programming-model-for-sap-fioridraft-durable-locks-cds-view-objectmodel.lifecycle-annotation

 

Best Regards,

Mahesh

Be the first to leave a comment
You must be Logged on to comment or reply to a post.