Technical Articles
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.
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
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
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
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.
Am I miss anything here?
May be adding these below annotations (which are currently missing) would do the job.
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
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
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
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
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
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