Wednesday, 20 March 2019

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.

@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.

SAP ABAP Tutorial and Materials, SAP ABAP Guides, SAP ABAP Learning, SAP ABAP Study Materials

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.

No comments:

Post a Comment