Skip to Content
Technical Articles
Author's profile photo Mahesh Palavalli

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

Note: This scenario is not officially supported from SAP

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/09/abap-programming-model-for-sap-fioridraft-durable-locks-cds-view-objectmodel.lifecycle-annotation/

 

Best Regards,

Mahesh

Assigned Tags

      57 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Sitakant Tripathy
      Sitakant Tripathy

      Brilliant write up and detailing there Mahesh...Kudos 🙂

      Regards,

      Sitakant

      Author's profile photo Abhijeet Kankani
      Abhijeet Kankani

      Excellent blog, I was looking for this kind of solution quite some time, where in we can use CDS-BOPF for Standard table where guide is not maintained.

       

      Thanks a lot.

       

      As using annotation Object Model we can use BOPF draft concept in smart template(Fiori Element), but my doubt here is if we are not using UI5 ot FIORI framework instead of that if one is using Dot net then Is there is any work around for draft, create and delete functionality only by giving CDS view service name which is generated odata service via annotation.

      Looking for some alternative solution.

      Regards,

      Abhijeet Kankani

       

      Author's profile photo Mahesh Palavalli
      Mahesh Palavalli
      Blog Post Author

      Thanks abhijeet kankani

      Achieving draft is easy in the fiori list report template app as it does all the frontend work you like sending the requests on change of ui elements(In the draft version) and when clicked on save, it will save the draft data to actual db.

      So to do this first you need to open the network tab and see what are the requests that will go when you click on new button. First a create request will go to create the draft entry. After that whatever the changes you do in the UI, asynchronous without blocking the UI, draft entry update requests will go to the backend and will be updated in the draft entry. On click of save, a funciton import call(don't rememeber for sure) will go and copy the data to the DB(your draft to active entry method will be called).

      So just go through the network tab and provide all the request to the .net people and tell them how to save the draft entry everytime a change is made in the UI, which would do the job.

      BR,

      Mahesh

      Author's profile photo Abhijeet Kankani
      Abhijeet Kankani

      Thanks Mahesh for that quick reply, even though I read today only.

      So one have to first build app in fiori and then need to check by pressing F12(about all the calls) or there is any other alternative.

      Want to build same draft in .net.

      Regards,

      Abhijeet Kankani

      Author's profile photo Mahesh Palavalli
      Mahesh Palavalli
      Blog Post Author

      Yeah.. I think that is the only and easiest way. SAP didn't provide any details about what request does what as the whole draft process is taken care by the template and the framework.

      You can also find details in the odata service metadata as well.

      BR,

      Mahesh

      Author's profile photo Gayatri k
      Gayatri k

       

      Hi Mahesh,

      This is really very useful information.

      When I tried similar example I am facing error at draft table(When I create the CDS view the draft table is not created automatically and raising error saying DB table ZXXX_XXX does not exist in active version and if I create the DB draft table manually it is through different error :Active Entity DB fields not marked as KEY), I followed the steps provided here. Can you help ?

      Regards,

      Gayatri

      Author's profile photo Mahesh Palavalli
      Mahesh Palavalli
      Blog Post Author

      Hi Gayatri, Thanks for the feedback.

      It seems you have already posted a question on this. I will reply there.

      BR

      Mahesh

      Author's profile photo Abhijeet Kankani
      Abhijeet Kankani

      Hi Mahesh,

      You are saying "Standard BP Fiori App" means api which SAP is providing also have the same concept as above ?

      API like API_Business_Partner, API_Sales_Order.

      Regards,

      Abhijeet kankani

      Author's profile photo Mahesh Palavalli
      Mahesh Palavalli
      Blog Post Author

      Hi abhijeet kankani

      I am not sure about the API's API_Business_Partner, API_Sales_Order

      But the standard Customer and Vendor master data fiori apps (Business partner) are done based on the concept described in this blog or you could say this blog is based on those apps 🙂

       

      BR,

      Mahesh

      Author's profile photo Igor Cepoi
      Igor Cepoi

      Hi Mahesh,

      I didn't see in the post the Deletion of the item, could you suggest please what changes and were to do them ?

      The draft creation/deletion is working properly, but after save of all the changes, the deleted items appear again, are not deleted from the 'real' custom table .

      Thanks.

      Author's profile photo Mahesh Palavalli
      Mahesh Palavalli
      Blog Post Author

      Hi Igor Cepoi

      You need to delete it manually in the class that is available in the actions. There is an another blog which does that.

      https://blogs.sap.com/2019/03/11/creating-a-draft-enabled-sales-order-fiori-app-using-the-new-abap-programming-model-part-4-implementing-locking-delete-functionality/

      BR,

      Mahesh

      Author's profile photo Igor Cepoi
      Igor Cepoi

      The Header deletion is working, I'm having problem when deleting an item, from the second facet.

      Delete button removes the item from the UI list, but after saving the Sales order, the item appears again in the UI list on the second facet.

      Author's profile photo Igor Cepoi
      Igor Cepoi

      Hi Mahesh,

      I solved this by deleting the item from table during save draft, in method copy_draft_to_active_entity.

      Do you see any problem doing like this ?

      Thanks.

      Author's profile photo Mahesh Palavalli
      Mahesh Palavalli
      Blog Post Author

      Hi Igor Cepoi

      From my understanding you are right, I saw the same behaviour in the standard develied app as well, they are checking if the draft instance against the original data if the draft item is not available then they are deleting the original data.

      You need to write that code in "copy_draft_to_active_entity"

      BR,

      Mahesh

       

      Author's profile photo Igor Cepoi
      Igor Cepoi

      Thanks Mahesh, it was a very usefull blog.

       

      Author's profile photo Mahesh Palavalli
      Mahesh Palavalli
      Blog Post Author

      Thanks for the feedback 🙂

      Author's profile photo Karan Shaheri
      Karan Shaheri

      Hi Mahesh,

      Thanks for the excellent blog. I tried passing "draft key" and "BOPF key" which is generated from the sales order in the last line in method “copy_draft_to_active_entity” to the exporting parameter "et_key_link" and I was able to successfully delete the draft entry from the table.

      Regards,

      Karan Shaheri.

      Author's profile photo Mahesh Palavalli
      Mahesh Palavalli
      Blog Post Author

      Thats good to hear Karan Shaheri,

      Thanks, Mahesh

      Author's profile photo Vishnu Pankajakshan Panicker
      Vishnu Pankajakshan Panicker

      Hey Mahesh,

      Thanks such a very well document on non-guid based Draft feature. Nice work !!.

       

      Regards,

      Vishnu P

       

      Author's profile photo Mahesh Palavalli
      Mahesh Palavalli
      Blog Post Author

      Thanks Vishnu for the feedback 🙂

      Author's profile photo Yellappa Madigonde
      Yellappa Madigonde

      Hi Mahesh,

      Very detailed explanation and easy to understand. I am looking forward to implement.

      Regards,

      Yellappa.

      Author's profile photo Mahesh Palavalli
      Mahesh Palavalli
      Blog Post Author

      Thanks & Awesome, Let the community know if you have any issues or if have any new findings to share with us 🙂

      BR,

      Mahesh

      Author's profile photo AJAY GUPTA
      AJAY GUPTA

      Hi Mahesh,

      Thanks for the blog. It is indeed very helpful. I have one question

      I wanted to achieve the same locking mechanism in my custom fiori apps which are build using only  SAPUI5 and odata (but no CDS).
      Is it possible to achieve this using BOPF ?

      Author's profile photo Mahesh Palavalli
      Mahesh Palavalli
      Blog Post Author

      Thanks AJAY GUPTA for the feedback.. Unfortunately durable locks are tightly integrated with the ABAP programming model for Fiori especially the draft scenarios. If you want to check the other options(ETAGS & SOFT STATE) check my other blog below:

      BR,
      Mahesh
      Author's profile photo Dhiraj More
      Dhiraj More

      HI Mahesh,

      Nice Blog. I was really looking for content something like this.

      I am also working on similar requirement where I am creating a BO for maintenance order where we don't have GUID key in database. I have created my BO with all CRUD operations. Then I created Consumption CDS on top of BO CDS in which I am only going for UPDATE operation along with draft functionality. Annotation transactionProcessingDelayed is marked as true.

      Now when I created UI application, first screen is displayed correctly with all the filters and maintenance order list. But when I select a order to navigate to second screen for update, I am getting the error as shown below. The entityset in below URI is for consumption CDS only.

      URI ==>

      GET ZCDSV_ITR_E0324_ObjPgOrderTP(MaintenanceOrder='100000001',DraftUUID=guid'00000000-0000-0000-0000-000000000000',IsActiveEntity=true)

      Error ==> "The parser produced the error: The field \"DRAFTUUID\" does not have a corresponding field in the work area."

      Can you please help and suggest the issue here.

       

      Thanks in advance for your expertise,

      Dhiraj M

       

       

      Author's profile photo Mahesh Palavalli
      Mahesh Palavalli
      Blog Post Author

      Hi Dhiraj,
      I replied to your question.

      BR, Mahesh

      Author's profile photo Reda Abdallah
      Reda Abdallah

      Hi Mahesh,

       

      Where is the answer to that question? I'm facing the exact same issue.

      Author's profile photo Reda Abdallah
      Reda Abdallah

      Update: For anyone in the future facing this issue, just regenerate your project in SEGW. Took me 2 hours to figure this out.

      Author's profile photo sujit dash
      sujit dash

      Thanks Mahesh for this Nice article.

      I want to  extend the  SSP Purchase requisition  Application.  From  Standard  CDS view  a draft  table and BOPF object is geneated. When  we are extending the standard  CDS view the  corresponding  Draft  Table  is   not picking up the  new field.

       

       

       

      Author's profile photo Mahesh Palavalli
      Mahesh Palavalli
      Blog Post Author

      Thanks for the feedback ?

      Can you create a question for this, the comments here are only for the blog related. It seems there is an issue with the extension in your case. I hope you have extended the BO CDS view(that ends with TP)

      BR,

      Mahesh

      Author's profile photo sumanth mitta
      sumanth mitta

      Awesome Mahesh! Worth the content to go through.. looking forward for more bolgs like this.

      Regards,

      Sumanth

      Author's profile photo Mahesh Palavalli
      Mahesh Palavalli
      Blog Post Author

      🙂 Thanks Sumanth.. Happy to see you here at the community 🙂 🙂

      Author's profile photo Sagar Patil
      Sagar Patil

      well explained.. keep contributing..keep inspiring.

      Author's profile photo Mahesh Palavalli
      Mahesh Palavalli
      Blog Post Author

      Thank you so much sagar for the encouraging comments ?

      Author's profile photo Jesse Brook
      Jesse Brook

      Hi Mahesh, thanks for this. Great article.

      Correct me if I'm wrong but I find this 'Draft' functionality only works with Fiori Elements. Not any other SAP Fiori template application.

       

      Thanks

      Again.

      Author's profile photo Mahesh Palavalli
      Mahesh Palavalli
      Blog Post Author

      Thank you Jesse ?

      Yeah true, List report template is the only one that utilizes the front end part of the draft ABAP programming model(as far as I know). At the end it's just the odata requests which is taking care of the actual "draft" create,save,update.., so we can still create drafts and work on them in the regular ui5 app as well or any other technology but in this case we need to completely take care of the UI part including sending requests asynchronously whenever the data is entered.

      Author's profile photo Mark Lewis
      Mark Lewis

      Hi Mahesh

      This may be inappropriate place but I am using above and have a custom action (all ok), I need to perform a check on the action so am trying to add an action check validation. But the option for Action Check is disabled. Should this be possible?

      Regards

      Mark

      Author's profile photo Mahesh Palavalli
      Mahesh Palavalli
      Blog Post Author

      I think you can do the same directly in the action itself right?

      Author's profile photo Mark Lewis
      Mark Lewis

      Hi Mahesh

      Yeah, had to do it in the Action and Field Control.

      Thanks

      Author's profile photo Jono Thomas Kannankara
      Jono Thomas Kannankara

      Hi Mahesh,

      I created CDS with the draft option allowing only the update for my scenario. Updates are happenning using the class that you described on the top. Unfortunately i’m getting an error when i try to cancel the draft data. Im getting the responce in odata saying “Deleting operations are disabled for entity ‘<entity_name>”. Do we really need to enable the delete opration inorder to cancel/Delete the draft? Or is there any method i need to redefine to perform this?

      Thank You

      Jono Thomas

       

      Author's profile photo Mahesh Palavalli
      Mahesh Palavalli
      Blog Post Author

      Hi Jono Thomas Kannankara

      ,

      Sorry for the late reply. I can see that your query is already answered. Just adding the link for others reference.

      https://answers.sap.com/questions/12835976/cds-bopf-draft--error-while-cancel-draft-from-fior.html

      Thanks,

      Mahesh

       

      Author's profile photo Swastik Mukherjee
      Swastik Mukherjee

      I read this blog one month ago and tried with non-guid based key. However, That time I did not realize that we can also save data in active table directly without coding from draft table if we use GUID based key on Active table. am I correct? Such a wonderful blog. Thank you so much.

      So, my understanding is if we use GUID based key in active table then when draft table gets generated it uses that key only. However, non-guid based it creates a new GUID based filed in draft tableand that becomes it's key. Did i understand it correctly?

       

      Author's profile photo Mahesh Palavalli
      Mahesh Palavalli
      Blog Post Author

      Hi Swastik Mukherjee . Thank you so much for the feedback ? ?

      you are right, it is possible and SAP takes care about everything if the key is GUID. Sadly, I don’t remember the behaviour.. But  like you said , it makes sense if the same active table key is being used in Draft table also as we don’t need any new GUID again right.. Also, there will always be one draft per one active entry. So it makes sense..

      But did you observe this behaviour?

      Author's profile photo Joachim Rees
      Joachim Rees

      Wow, this seems extremely detailed and helpful! ( I only skimmed it as of now, but will keep it for later reading!)

      Thanks!

      Author's profile photo Mahesh Palavalli
      Mahesh Palavalli
      Blog Post Author

      Thank you so much Joachim Rees for the feedback 🙂 🙂

      You should take a deep dive into this and you will  surely love the draft framework (esp. if you are frustrated with the development timelines of UI5 apps)

      (Advertisement 😛 ) You can also check my other blog on Durable locks which is interesting as well

      Author's profile photo Louis-Arnaud BOUQUIN
      Louis-Arnaud BOUQUIN

      Hello Mahesh,

      Thanks again for this blog.

      Do you know if it is possible to create a draft fiori elements but with just the items editable ?

      I can't achieve to do this without creating a useless draft table for the header.

      Thank you,

       

      Louis-Arnaud

      Author's profile photo Mahesh Palavalli
      Mahesh Palavalli
      Blog Post Author

      Hi Louis,

      Thanks for the feedback 🙂

      I never tried this scenario 🙁 and I doubt if that option is given by SAP.

      If we give transactional process enabled as true then we have to mention writeActivePersistance or writeDraftPersistance as true in the root view, then it will throw error if we don't provide the draft table right?  If yes, then it might not be possible..(Try mentioning create, update, delete as false and check as well).

      Creating a dummy draft table won't be a big issue i guess, But if you want to check if it is avoidable, I would suggest you create a new question, so you might attract some other audience.

      Thanks,
      Mahesh

      Author's profile photo K C
      K C

      hi Mahesh

      Nice blog.

      Just one question. you created 2 consumption views and generated one Odata from Header CDS. So the consumption view from Item CDS will be automatically linked with header during consumption?

      Author's profile photo Mahesh Palavalli
      Mahesh Palavalli
      Blog Post Author

      Yes and we also need to maintain the associations and the below annotation, so the framework will recognize the relationship and will create the odata service with the required annotations.

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

      In child.

      @ObjectModel: {
                association: {
                    type: [ #TO_COMPOSITION_PARENT, #TO_COMPOSITION_ROOT ]
                }
            }
      Author's profile photo Venkat dattatreya
      Venkat dattatreya

      Hi Mahesh,

      I tried your example. When I run the application BTP and click on the sales order item to navigate to object page, the fields which are declared with UI.Identification are not visible in the navigation section. Do i have to add a facet?

       

      Author's profile photo Mahesh Palavalli
      Mahesh Palavalli
      Blog Post Author

      Hi Venkat,

      Yeah, facet is needed from my understanding. else it wouldn't show the sections.

       

      Thanks,

      Mahesh

      Author's profile photo CHENGALARAYULU DAMALACHERUVU
      CHENGALARAYULU DAMALACHERUVU

      Thanks Mahesh, nice blog. would you mind adding how can we implement method copy_draft_to_active_entity? under which class?

      Author's profile photo Seeger Benjamin
      Seeger Benjamin

      Thanks for this great Blog.

      Does this Concept also works with a Fiori Elements Application generated with SAP Business Application Studio and deployment on ABAP System?

      Is it also possible to achiev it with local XML Annotation without CDS?

       

      Author's profile photo Christian Israel Lopez Villalobos
      Christian Israel Lopez Villalobos

      Thanks for the very detailed blog Mahesh.

      I have followed every step of the tutorial and everything works fine for me, but I have an issue. My requirement allow the user to set the keys of the tables (only at the time of creation) but if I have the draft active, the key for some reason is not reflected in the draft table, and this causes that when I save the data, a row is created in the table but with the empty key.

      Do you know how I can enable key fields to be reflected in the draft table as well?

      Thanks!

      Author's profile photo Sameer Dhuke
      Sameer Dhuke

      Hi Christian Israel Lopez Villalobos,

       

      Did you get the solution for this? I am also having similar requirement but its not updating key in the draft.

      Author's profile photo Sameer Dhuke
      Sameer Dhuke

      Hi Mahesh,

      Thanks for the excellent blog. I followed the steps and most of the things are working, only change in the requirement is, key field will be entered by user on fiori screen.

      I am not able to fetch key field from screen in the method COPY_DRAFT_TO_ACTIVE_ENTITY. Do you know how to achieve this requirement.

       

      Regards,

      Sameer Dhuke

      Author's profile photo shabnam jalal
      shabnam jalal

      Dear Mahesh,

      Thanks for the detailed steps. I have a similar requirement to make a transactional app with attachments. How to do the attachment part here? Any hints?

      Thanks in advance

      Shabnam