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