Skip to Content
Technical Articles
Author's profile photo Geert-Jan Klaps

Creating a draft enabled Sales Order Fiori App using the new ABAP Programming Model – Part 2: Virtual Data Model & Consumption views

Building the Virtual Data Model

To provide our app with data and draft capabilities, we’ll build two CDS views which will form the base for our business object. I won’t go into details on how to create CDS views here, as there are enough guides available on how to create them.

Our business object will consist out of 2 nodes:

  • Sales Order Header Node (root node)
  • Sales Order Item Node

We’ll use CDS annotations to model our business object. As you’ll notice, both CDS reference each other so you’ll need to activate both of them at the same time in order to get them activated.

Sales Order Header node

Create following CDS view. This view is just a regular join of sales order header data (VBAK) and partner data (VBPA). We’ll also need to expose our item data using an association to have them available as a subnode in our business object.

This business object is defined by the @ObjectModel annotations. More information about these annotations can be found in the documentation delivered by SAP.

@AbapCatalog.sqlViewName: 'ZSD_I_SOHEAD_V'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Sales order header model view'

@ObjectModel: { compositionRoot: true,
                modelCategory: #BUSINESS_OBJECT,
                transactionalProcessingEnabled: true,
                createEnabled: true,
                updateEnabled: true,
                deleteEnabled: true,
                semanticKey: ['vbeln'],
                draftEnabled: true,
                writeDraftPersistence: 'ZSD_SOHEAD_D' }

define view ZSD_I_SOHEADER as select from vbak
  association [1..*] to ZSD_I_SOITEM as _items on $projection.vbeln = _items.vbeln
  association [0..1] to vbpa as _shipTo on $projection.vbeln = _shipTo.vbeln and _shipTo.posnr = '000000' and _shipTo.parvw = 'WE' {

  @ObjectModel.readOnly: true
  key vbak.vbeln,
      vbak.auart,
      vbak.vkorg,
      vbak.vtweg,
      vbak.spart,
      vbak.vkbur,
      vbak.vkgrp,
      vbak.kunnr,
      cast( _shipTo.kunnr as kunwe preserving type ) as kunwe,
      vbak.bstnk,
      vbak.ernam,
      vbak.erdat,
      vbak.erzet,
      vbak.aedat,
      vbak.netwr,
      vbak.waerk,

      @ObjectModel: {
        association: {
            type: [#TO_COMPOSITION_CHILD]
        }
      }
      _items,
      _shipTo
}
where vbak.vbtyp = 'C'

 

Sales Order Item node

@AbapCatalog.sqlViewName: 'ZSD_I_SOITEM_V'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Sales order item model view'

@ObjectModel: { updateEnabled: true,
                createEnabled: true,
                deleteEnabled: true,
                semanticKey: ['vbeln', 'posnr'],
                writeDraftPersistence: 'ZSD_SOITEM_D'}

define view ZSD_I_SOITEM as select from vbap
  association [1..1] to ZSD_I_SOHEADER as _header on $projection.vbeln = _header.vbeln {
  @ObjectModel.readOnly: true
  key vbap.vbeln,
  @ObjectModel.readOnly: true
  key vbap.posnr, 
      vbap.matnr,   
      vbap.arktx,
      vbap.kwmeng,
      vbap.vrkme,
      vbap.netwr, 
      vbap.waerk, 
      vbap.ernam, 
      vbap.erdat, 
      vbap.erzet,
      vbap.aedat, 
      
      @ObjectModel: {
        association: {
            type: [#TO_COMPOSITION_PARENT,#TO_COMPOSITION_ROOT]
        }
      }
      _header
}

After activating both CDS views the system generates a business object and all required repository objects. We’ll use the generated objects throughout this blog series to add functionality to our business object.

 

Building the consumption views

To expose the business object data as an oData service, we’ll create a consumption for both of the previously created views. In these views we’ll:

  • Change internal fieldnames to a more readable format (e.g. VBELN -> orderNumber)
  • Determine which fields are mandatory or read only
  • Add foreign key associations to fields where possible (this will automatically add search helps to the fields in our Fiori App)
  • Expose the view as oData service using the @OData.publish annotation
  • Separate our UI annotations using metadata extensions by setting the @Metadata.allowExtensions annotation to true

Sales Order Header Consumption view

@AbapCatalog.sqlViewName: 'ZSD_C_SOHEAD_V'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Sales order header consumption view'

@OData.publish: true

@Metadata.allowExtensions: true

@VDM.viewType: #CONSUMPTION

@ObjectModel: { compositionRoot: true,
                transactionalProcessingDelegated: true,
                createEnabled: true,
                updateEnabled: true,
                deleteEnabled: true,
                semanticKey: ['orderNumber'],
                draftEnabled: true }
define view ZSD_C_SOHEADER as select from ZSD_I_SOHEADER
    association [1..*] to ZSD_C_SOITEM as _items on $projection.orderNumber = _items.orderNumber
    association [1..1] to I_SalesOrderType as _orderType on $projection.orderType = _orderType.SalesOrderType
    association [1..1] to I_SalesOrganization as _salesOrg on $projection.salesOrganization = _salesOrg.SalesOrganization
    association [1..1] to I_DistributionChannel as _distributionChannel on $projection.distributionChannel = _distributionChannel.DistributionChannel
    association [1..1] to I_Division as _division on $projection.division = _division.Division
    association [0..1] to I_SalesOffice as _salesOffice on $projection.salesOffice = _salesOffice.SalesOffice
    association [0..1] to I_SalesGroup as _salesGroup on $projection.salesGroup = _salesGroup.SalesGroup
    association [0..1] to I_Customer as _soldTo on $projection.soldTo = _soldTo.Customer 
    association [0..1] to I_Customer as _shipTo on $projection.shipTo = _shipTo.Customer
    {
    
    @ObjectModel.readOnly: true 
    key ZSD_I_SOHEADER.vbeln as orderNumber,
    
    @ObjectModel.readOnly: true
    @ObjectModel.foreignKey.association: '_orderType'
    ZSD_I_SOHEADER.auart as orderType,
    
    @ObjectModel.mandatory: true
    @ObjectModel.foreignKey.association: '_salesOrg'
    ZSD_I_SOHEADER.vkorg as salesOrganization,
    
    @ObjectModel.mandatory: true
    @ObjectModel.foreignKey.association: '_distributionChannel'
    ZSD_I_SOHEADER.vtweg as distributionChannel,
    
    @ObjectModel.mandatory: true
    @ObjectModel.foreignKey.association: '_division'
    ZSD_I_SOHEADER.spart as division, 
    
    @ObjectModel.foreignKey.association: '_salesOffice'
    ZSD_I_SOHEADER.vkbur as salesOffice, 
    
    @ObjectModel.foreignKey.association: '_salesGroup'
    ZSD_I_SOHEADER.vkgrp as salesGroup, 
    
    @ObjectModel.mandatory: true
    @ObjectModel.foreignKey.association: '_soldTo'
    ZSD_I_SOHEADER.kunnr as soldTo, 
    
    @ObjectModel.readOnly: true
    _soldTo.CustomerName as soldToName,
    
    @ObjectModel.mandatory: true
    @ObjectModel.foreignKey.association: '_shipTo'
    ZSD_I_SOHEADER.kunwe as shipTo, 
    
    @ObjectModel.readOnly: true
    _shipTo.CustomerName as shipToName,
    
    ZSD_I_SOHEADER.bstnk as customerReference,
    
    @ObjectModel.readOnly: true 
    ZSD_I_SOHEADER.ernam as createdBy,
    
    @ObjectModel.readOnly: true
    ZSD_I_SOHEADER.erdat as createdOn, 
    
    @ObjectModel.readOnly: true
    ZSD_I_SOHEADER.erzet as createdAt, 
    
    @ObjectModel.readOnly: true
    ZSD_I_SOHEADER.aedat as changedOn, 
    
    @ObjectModel.readOnly: true
    @Semantics.amount.currencyCode: 'currency'
    ZSD_I_SOHEADER.netwr as netValue, 
    
    @ObjectModel.readOnly: true
    @Semantics.currencyCode: true 
    ZSD_I_SOHEADER.waerk as currency,
    
    @ObjectModel.association: {
        type: [#TO_COMPOSITION_CHILD]
    }
    _items,
    _orderType,
    _salesOrg,
    _distributionChannel,
    _division,
    _salesOffice,
    _salesGroup,
    _soldTo,
    _shipTo
}

 

Sales Order Item Consumption view

@AbapCatalog.sqlViewName: 'ZSD_C_SOITEM_V'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Sales order item consumption view'

@Metadata.allowExtensions: true

@VDM.viewType: #CONSUMPTION

@ObjectModel: { updateEnabled: true,
                createEnabled: true,
                deleteEnabled: true,
                semanticKey: ['orderNumber', 'orderItem']}

define view ZSD_C_SOITEM as select from ZSD_I_SOITEM
    association [1..1] to ZSD_C_SOHEADER as _header on $projection.orderNumber = _header.orderNumber
    association [0..1] to I_Material as _material on $projection.material = _material.Material
    association [0..1] to I_UnitOfMeasure as _unitOfMeasure on $projection.salesUnit = _unitOfMeasure.UnitOfMeasure {
    
    @ObjectModel.readOnly: true 
    key ZSD_I_SOITEM.vbeln as orderNumber,
     
    @ObjectModel.readOnly: true 
    key ZSD_I_SOITEM.posnr as orderItem,
    
    @ObjectModel.mandatory: true
    @ObjectModel.foreignKey.association: '_material' 
    ZSD_I_SOITEM.matnr as material, 
    
    @ObjectModel.mandatory: true
    ZSD_I_SOITEM.arktx as itemDescription,
    
    @ObjectModel.mandatory: true 
    @Semantics.quantity.unitOfMeasure: 'salesUnit'
    ZSD_I_SOITEM.kwmeng as orderQuantity, 
    
    @ObjectModel.mandatory: true
    @Semantics.unitOfMeasure: true
    @ObjectModel.foreignKey.association: '_unitOfMeasure' 
    ZSD_I_SOITEM.vrkme as salesUnit, 
    
    @ObjectModel.readOnly: true 
    @Semantics.amount.currencyCode: 'currency'
    ZSD_I_SOITEM.netwr as netValue,
    
    @ObjectModel.readOnly: true
    @Semantics.currencyCode: true  
    ZSD_I_SOITEM.waerk as currency,
    
    @ObjectModel.readOnly: true  
    ZSD_I_SOITEM.ernam as createdBy, 
    
    @ObjectModel.readOnly: true 
    ZSD_I_SOITEM.erdat as createdOn, 
    
    @ObjectModel.readOnly: true 
    ZSD_I_SOITEM.erzet as createdAt,
    
    @ObjectModel.readOnly: true  
    ZSD_I_SOITEM.aedat as changedOn,
    
    @ObjectModel.association: {
        type: [#TO_COMPOSITION_PARENT, #TO_COMPOSITION_ROOT]
    }
    _header,
    _material,
    _unitOfMeasure
}

After activating the consumption views the data model for our Fiori app is ready for use. We can now continue with the next part of the blog series to define the UI and generate the Fiori Elements app.

 

Quick navigation

Assigned Tags

      9 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Timothy Muchena
      Timothy Muchena

      Thank you for the detailed blog.

      Just wondering what the structure of ZSD_SOHEAD_D and ZSD_SOITEM_D looks like as I am getting errors

      [BO check] Active Entity DB fields not marked as KEY (view ZSD_I_SOHEADER, table ZSD_SOHEAD_D)

      [BO check] Active Entity DB fields not marked as KEY (view ZSD_I_SOITEM, table ZSD_SOITEM_D) 

       

      Thank you

      Author's profile photo Geert-Jan Klaps
      Geert-Jan Klaps
      Blog Post Author

      Hi Timothy,

      These two database tables normally shouldn't exist before trying to activate the CDS views, these are the actual draft tables and get generated (or regenerated) when you activate the CDS views.

      Maybe it's a system error and you need to implement some notes to get this fixed? (I wrote this guide using an S/4HANA 1809 so the basis level was 7.53)

      If you take a look at this wiki for example, maybe note "#2005370 - CDS views: Key fields not copied from DDL source" is relevant for your system?

      Best regards,

      Geert-Jan Klaps

      Author's profile photo Roy Mafi
      Roy Mafi

      Thank you for the detailed blog Geert-Jan,

      We are an S/4HANA 1809 with basis level 7.53 too, but I am getting below error when I am trying to add “transactionalProcessingEnabled: true” statement for draft mode.

      Error Message: "[BO Sync] Neither 'draft pers.' or 'active pers.' not supported"

      Are we missing a patch or parameter on S4/HANA to make draft mode enabled?

      I Appreciate your feedback.

      Regards,

      Roy

      Author's profile photo Pavan Golesar
      Pavan Golesar

      Hey Geert-Jan Klaps,

      Thanks for such in-depth blog series. ?

      I am facing issue.

      The concern is that in step 5( where we are adding the determination for the ZSD_I_SOITEM,  I am not getting the root node of this BOPF, As there isn’t a BOPF object generated for this as you see in the below annotations.

      @ObjectModel: { updateEnabled: true,
                      createEnabled: true,
                      deleteEnabled: true,
                      semanticKey: ['orderNumber', 'orderItem']}

       

      Am I miss anything here?

      May be adding these below annotations (which are currently missing) would do the job.

      @ObjectModel: { compositionRoot: true,
      modelCategory: #BUSINESS_OBJECT,
      transactionalProcessingEnabled: true,
      draftEnabled: true}

      Any help would be appreciated!! This would enable me to complete the “Generating a Sales Order item number based on customizing” part.

      Cheers,

      Pavan Golesar

      Author's profile photo Geert-Jan Klaps
      Geert-Jan Klaps
      Blog Post Author

      Hi Pavan Golesar,

      You don't need to have a BOPF object for ZSD_I_SOITEM, if you look closely to part 5 you'll see that it's a determination being added to the BOPF object of the header but on the item node (which doesn't have it's own BOPF object)

      Best regards,

      Geert-Jan Klaps

      Author's profile photo Pavan Golesar
      Pavan Golesar

      Ya, Thanks Worked liked charm. It was just part of navigating from header root node to item note with simple ctrl + right click. 🙂

      Cheers,

      Pavan Golesar

      Author's profile photo Shiva Krishna
      Shiva Krishna

      Hello Geert-Jan Klaps,

      Thanks for your detailed blog.

      I have followed the same steps as you , but i am facing an issue while activating the cds view and generating the draft table.

      Below is my header view:

       

      Item View:

       

       

      Error while activating CDS view :

       

      Could you please make me understand , what is the issue here.

      Any help would be much appreciated.

      Thanks for your help in advance.

      Regards,

      Shiva

       

      Author's profile photo Pooja Upadhyay
      Pooja Upadhyay

      Hi Shiva,

      I am facing the same issue related to Draft tables while activating the CDS view (getting the same error - "[BO check] DB table <table name> does not exist in the active version"). Could you please let me know if you were able to fix this issue?

       

      Regards,

      Pooja

       

      Author's profile photo Shiva Krishna
      Shiva Krishna

      Hello Pooja,

      I had that issue because of the SAP NW version .

      The pre requisite for this feature is to have NW version equal/above 751 (mine was 750).

      Regards,

      Shiva