Custom hierarchy extension in SAP Fiori app based on CDS Query(step-by-step)
This blog will show you how to create custom hierarchy using CDS view and integrate it into CDS Query based Fiori application.
Prerequisite:
You have understanding of how to extend analytical query based Fiori app by adding custom field to analytical query and cube. If not, find out how by following my blog at Extending SAP Fiori app based on CDS Query.
The Setup
This blog will demonstrate how to create profit center hierarchy using CDS view and integrate it to custom CDS cube and CDS Query. By the end of this blog, you will be able to show profit center as filter and dimension in the Fiori analytical report.
Data modeling
Creating hierarchy view that can be used for CDS Query requires to follow certain data modeling framework. The green part of the image is minimum views required to create hierarchy in CDS views. Hierarchy Directory contains hierarchy registry information such as hierarchy ID, validity date and hierarchy type. Hierarchy Node contains the hierarchy node information such as which node belongs to which parent node and which level in the structure it belongs to. Both Hierarchy Directory and Node view will create association with their text information. This is so that user will see text of each node rather than just node ID in the Fiori application. Finally, Master data view is the view that contains the master data of the entity. In our case, master data will be profit center so it will this view will fetch data from CEPC.
Define profit center hierarchy
Go to Fiori app Manage Global Hierarchies and create profit center hierarchy. Define node structures, assign profit center under nodes and activate the hierarchy.
Hierarchy directory views
Create hierarchy directory text views. This view will use HRRP_DIRECTORYT as datasource. After activating your profit center hierarchy, you can view this table content by specifying Hierarchy Type(HRYTYPE) as ‘0106’(Profit Center) and you will see your hierarchy.
Now having checked this, let’s create hierarchy directory text views in ADT(ABAP Development Tool). As profit center hierarchy can be assigned to different controlling area, we must add hryclas(controlling area) as key and associate I_ControllingArea. As you can see in the where clause, hierarchy type is restricted with 0106(profit center).
@EndUserText.label: 'Profit Center Hierarchy - Text'
@ObjectModel.representativeKey: 'ProfitCenterHierarchy'
@Analytics: { dataExtraction.enabled: true }
@ObjectModel.dataCategory: #TEXT
@VDM.viewType: #BASIC
@ObjectModel.modelingPattern: #LANGUAGE_DEPENDENT_TEXT
@ObjectModel.usageType.serviceQuality: #A
@ObjectModel.usageType.sizeCategory: #XL
@ObjectModel.usageType.dataClass: #MASTER
define view entity YI_ProfitCenterHierarchyText
as select from hrrp_directoryt
association [0..1] to I_ControllingArea as _ControllingAreaText on $projection.ControllingArea = _ControllingAreaText.ControllingArea
association [0..1] to I_ControllingArea as _ControllingArea on $projection.ControllingArea = _ControllingArea.ControllingArea
association [0..1] to I_Language as _Language on $projection.Language = _Language.Language
{
@Consumption.valueHelpDefinition: [
{ entity: { name: 'I_ControllingArea',
element: 'ControllingArea' }
}]
@ObjectModel.text.association: '_ControllingAreaText'
@ObjectModel.foreignKey.association: '_ControllingArea'
key hrrp_directoryt.hryclas as ControllingArea,
key cast(hrrp_directoryt.hryid as fis_hryid_prctr ) as ProfitCenterHierarchy,
@Semantics.businessDate.to: true
key hrrp_directoryt.hryvalto as ValidityEndDate,
@Semantics.language
key hrrp_directoryt.spras as Language,
@Semantics.businessDate.from: true
hrrp_directoryt.hryvalfrom as ValidityStartDate,
@Semantics.text: true
hrrp_directoryt.hrytxt as ProfitCenterHierarchyName,
_ControllingArea,
_Language,
_ControllingAreaText
}
where
hrrp_directoryt.hrytype = '0106';
Save and activate the CDS view.
Create hierarchy directory views. This view will use HRRP_DIRECTORY as data source. HRRP_NODE is contains more registry information such as creation date and user and it just does not have text information. That’s why we created text view for hierarchy directory first in the previous step. Same as HRRP_NODET, you can view this table content by specifying Hierarchy Type(HRYTYPE) as ‘0106’(Profit Center).
Create hierarchy directory views in ADT.
@ObjectModel.representativeKey: 'ProfitCenterHierarchy'
@EndUserText.label: 'Profit Center Hierarchy'
@Analytics: { dataCategory: #DIMENSION }
@VDM.viewType: #BASIC
@AccessControl.authorizationCheck: #CHECK
@Metadata.ignorePropagatedAnnotations:true
@ObjectModel.usageType.serviceQuality: #A
@ObjectModel.usageType.sizeCategory: #XL
@ObjectModel.usageType.dataClass: #MASTER
define view entity YI_PROFITCENTERHIERARCHY
as select from hrrp_directory
association [0..1] to I_ControllingArea as _ControllingAreaText on $projection.ControllingArea = _ControllingAreaText.ControllingArea
association [*] to YI_ProfitCenterHierarchyText as _Text on $projection.ProfitCenterHierarchy = _Text.ProfitCenterHierarchy
and $projection.ControllingArea = _Text.ControllingArea
association [0..1] to I_ControllingArea as _ControllingArea on $projection.ControllingArea = _ControllingArea.ControllingArea
{
@Consumption.valueHelpDefinition: [
{ entity: { name: 'I_ControllingArea',
element: 'ControllingArea' }
}]
@ObjectModel.text.association: '_ControllingAreaText'
@ObjectModel.foreignKey.association: '_ControllingArea'
key cast( hrrp_directory.hrycls as fis_kokrs ) as ControllingArea,
@ObjectModel.text.association: '_Text'
key cast(hrrp_directory.hryid as fis_hryid_prctr ) as ProfitCenterHierarchy,
@Semantics.businessDate.to: true
key cast(hrrp_directory.hryvalto as fis_datbi ) as ValidityEndDate,
@Semantics.businessDate.from: true
cast(hrrp_directory.hryvalfrom as fis_datab ) as ValidityStartDate,
_Text,
_ControllingArea,
_ControllingAreaText
}
where
hrrp_directory.hrytyp = '0106';
Save and activate the CDS view.
Hierarchy node views
Create hierarchy node text views. This view will use HRRP_NODET as data source and this contains child parent relationship and description for each node defined in the hierarchy. You can view your hierarchy in this table content by specifying Hierarchy Type(HRYTYPE) as ‘0106’(Profit Center).
Create hierarchy node text views in ADT.
@ObjectModel.dataCategory: #TEXT
@ObjectModel.representativeKey: 'HierarchyNode'
@EndUserText.label: 'Profit Center Hierarchy Node - Text'
@VDM.viewType: #BASIC
@ObjectModel.usageType.serviceQuality: #A
@ObjectModel.usageType.sizeCategory: #XL
@ObjectModel.usageType.dataClass: #MASTER
define view entity YI_PROFITCENTERHIERNODET
as select from hrrp_nodet
association [0..1] to I_ControllingArea as _ControllingAreaText on $projection.ControllingArea = _ControllingAreaText.ControllingArea
association [0..1] to I_ControllingArea as _ControllingArea on $projection.ControllingArea = _ControllingArea.ControllingArea
association [0..1] to I_Language as _Language on $projection.Language = _Language.Language
association [1..*] to YI_PROFITCENTERHIERARCHY as _Hierarchy on $projection.ProfitCenterHierarchy = _Hierarchy.ProfitCenterHierarchy
and $projection.ControllingArea = _Hierarchy.ControllingArea
{
@Consumption.valueHelpDefinition: [
{ entity: { name: 'I_ControllingArea',
element: 'ControllingArea' }
}]
@ObjectModel.text.association: '_ControllingAreaText'
@ObjectModel.foreignKey.association: '_ControllingArea'
key cast( hrrp_nodet.nodecls as fis_kokrs ) as ControllingArea,
@ObjectModel.foreignKey.association: '_Hierarchy'
key cast(hrrp_nodet.hryid as fis_hryid_prctr ) as ProfitCenterHierarchy,
@ObjectModel.text.element: [ 'HierarchyNodeText' ]
key hrrp_nodet.hrynode as HierarchyNode,
@Semantics.businessDate.to: true
key cast(hrrp_nodet.hryvalto as fis_datbi ) as ValidityEndDate,
@Semantics.language: true
key hrrp_nodet.spras as Language,
@Semantics.text: true
hrrp_nodet.nodetxt as HierarchyNodeText,
@Semantics.text: true
substring(hrrp_nodet.nodetxt, 1, 20) as HierarchyNodeShortText,
@Semantics.businessDate.from: true
cast(hrrp_nodet.hryvalfrom as fis_datab ) as ValidityStartDate,
_ControllingArea,
_Hierarchy,
_Language,
_ControllingAreaText
}
where
hrrp_nodet.hrytyp = '0106';
Save and activate.
Create hierarchy node views. This view will use HRRP_NODE as data source and this contains all the node and leaf information defined in the hierarchy including the node level, node sequence and parent child relationship between each node and leaf. You can view your hierarchy in this table content by specifying Hierarchy Type(HRYTYPE) as ‘0106’(Profit Center).
Create hierarchy node text views in ADT. Few things to note in this view:
- @Hierarchy.parentChild annotation defines which field to be used for child and parent in order to create hierarchy
- @Hierarchy.parentChild.directriy annotation defines which hierarchy directory is used. That’s why we are associating YI_PROFITCENTERHIERARCHY created in the previsous steps.
- Associate YI_PROFITCENTERHIERNODET to extract text information for hierarchy node.
- Associate YI_PROFITCENTERHIERARCHY to extract hierarchy directory information.
- Associate master data view YI_PROFITCENTER which we will create in the next step.
@EndUserText.label: 'Profit Center Hierarchy Node'
@ObjectModel.representativeKey: 'HierarchyNode'
@ObjectModel: { dataCategory: #HIERARCHY }
@VDM.viewType: #BASIC
@Hierarchy.parentChild:[
{ name: 'ProfitCetner_Hierarchy',
label: 'ProfitCetner_Hierarchy',
recurse: { parent:[ 'ParentNode'], child:[ 'HierarchyNode'] },
siblingsOrder: [{ by: 'HierarchyNodeSequence', direction: #ASC }],
directory: '_Hierarchy' }]
@ObjectModel.usageType.serviceQuality: #A
@ObjectModel.usageType.sizeCategory: #XL
@ObjectModel.usageType.dataClass: #MASTER
define view entity YI_PROFITCENTERHIERARCHYNODE
as select from hrrp_node
association [0..1] to I_ControllingArea as _ControllingAreaText on $projection.ControllingArea = _ControllingAreaText.ControllingArea
association [0..*] to YI_PROFITCENTERHIERNODET as _Text on $projection.ProfitCenterHierarchy = _Text.ProfitCenterHierarchy
and $projection.HierarchyNode = _Text.HierarchyNode
and $projection.ControllingArea = _Text.ControllingArea
and $projection.ProfitCenter = '' // just to show that this association is only to be followed if profitcenter is blank
association [0..*] to YI_PROFITCENTER as _ProfitCenter on $projection.ProfitCenter = _ProfitCenter.ProfitCenter
and $projection.ControllingArea = _ProfitCenter.ControllingArea
association [1..1] to YI_PROFITCENTERHIERARCHY as _Hierarchy on $projection.ProfitCenterHierarchy = _Hierarchy.ProfitCenterHierarchy
and $projection.ControllingArea = _Hierarchy.ControllingArea
and $projection.ValidityEndDate = _Hierarchy.ValidityEndDate
association [0..1] to I_ControllingArea as _ControllingArea on $projection.ControllingArea = _ControllingArea.ControllingArea
{
@Consumption.valueHelpDefinition: [
{ entity: { name: 'I_ControllingArea',
element: 'ControllingArea' }
}]
@ObjectModel.text.association: '_ControllingAreaText'
@Consumption.filter: {mandatory : true, selectionType : #SINGLE, multipleSelections : false }
@ObjectModel.foreignKey.association: '_ControllingArea'
key cast( hrrp_node.nodecls as fis_kokrs ) as ControllingArea,
@Consumption.filter: {mandatory : true, selectionType : #SINGLE, multipleSelections : false }
@ObjectModel.foreignKey.association: '_Hierarchy'
key cast(hrrp_node.hryid as fis_hryid_prctr) as ProfitCenterHierarchy,
@ObjectModel.text.association: '_Text'
key hrrp_node.hrynode as HierarchyNode,
@Semantics.businessDate.to: true
@Consumption.filter: {mandatory : true, selectionType : #SINGLE, multipleSelections : false }
key cast(hrrp_node.hryvalto as fis_datbi preserving type ) as ValidityEndDate,
hrrp_node.parnode as ParentNode,
hrrp_node.hryver as HierarchyVersion,
@Semantics.businessDate.from: true
cast(hrrp_node.hryvalfrom as fis_datab preserving type ) as ValidityStartDate,
@Consumption.valueHelpDefinition: [
{ entity: { name: 'I_ProfitCenterStdVH',
element: 'ProfitCenter' },
additionalBinding: [{ localElement: 'ControllingArea',
element: 'ControllingArea' }]
}]
@ObjectModel.foreignKey.association: '_ProfitCenter'
cast ( hrrp_node.leafvalue as fis_prctr ) as ProfitCenter,
hrrp_node.hryseqnbr as HierarchyNodeSequence,
hrrp_node.hrylevel as HierarchyNodeLevel,
hrrp_node.nodetype as NodeType,
hrrp_node.nodevalue as HierarchyNodeVal,
_Text,
_ProfitCenter,
_Hierarchy,
_ControllingArea,
_ControllingAreaText
}
where
nodetype <> 'D'
and hrrp_node.hrytype = '0106';
Save but at this point YI_PROFITCENTER is not created so we cannot activate it yet.
Master data entity view
Create custom master data view that consumes all the CDS views we created so far. Since we are using profit cetner hierarchy, CEPC(profit center master) will be used as data source. The most important part is to associate YI_PROFITCENTERHIERARCHYNODE and use it for field ProfitCenter.
@EndUserText.label: 'Profit Center'
@VDM.viewType: #BASIC
@ObjectModel.representativeKey: 'ProfitCenter'
@ObjectModel.usageType: {
dataClass: #MASTER,
serviceQuality: #A,
sizeCategory: #M
}
@Analytics:{
dataCategory: #DIMENSION,
dataExtraction: {
enabled: true,
delta.changeDataCapture: {
automatic: true
}
}
}
@Search.searchable: true
@Consumption.filter.businessDate.at: true
define view entity YI_PROFITCENTER
as select distinct from cepc
association [1..1] to I_ControllingArea as _ControllingAreaText on $projection.ControllingArea = _ControllingAreaText.ControllingArea
association [1..1] to I_ControllingArea as _ControllingArea on $projection.ControllingArea = _ControllingArea.ControllingArea
association [0..*] to I_ProfitCenterText as _Text on $projection.ControllingArea = _Text.ControllingArea
and $projection.ProfitCenter = _Text.ProfitCenter
and $projection.ValidityEndDate = _Text.ValidityEndDate
association [0..*] to YI_PROFITCENTERHIERARCHYNODE as _ProfitCenterHierarchyNode on $projection.ControllingArea = _ProfitCenterHierarchyNode.ControllingArea
and $projection.ProfitCenter = _ProfitCenterHierarchyNode.ProfitCenter
{
@Consumption.valueHelpDefinition: [
{ entity: { name: 'I_ControllingArea',
element: 'ControllingArea' }
}]
@ObjectModel.text.association: '_ControllingAreaText'
@ObjectModel.foreignKey.association: '_ControllingArea'
key kokrs as ControllingArea,
@ObjectModel.text.association: '_Text'
@ObjectModel.hierarchy.association: '_ProfitCenterHierarchyNode'
@Search.defaultSearchElement: true
@Search.fuzzinessThreshold: 0.8
key prctr as ProfitCenter,
@Semantics.businessDate.to: true
key datbi as ValidityEndDate,
verak as ProfitCtrResponsiblePersonName,
bukrs as CompanyCode,
verak_user as ProfitCtrResponsibleUser,
@Semantics.businessDate.from: true
datab as ValidityStartDate,
_Text,
_ControllingArea,
_ProfitCenterHierarchyNode,
_ControllingAreaText
}
Save and activate the view. After that activate YI_ProfitCetnerHierNode as well.
Testing hierarchy
Go to transaction code RSH1 and enter 2C+’your master data view that uses hierarchy view’ so in our case it is 2CYI_PROFITCENTER. Double click on the hierarchy displayed and you will see the hierarchy defined in the app Manage Global Hierarchy. If there is no hit when entering 2CYI_PROFITCENTER, there is something wrong with the view defined. Double check all the views we created so far to see if you missed anything.
Create CDS Cube and Query
To test our hierarchy views, we will create a sample CDS cube and query.
CDS Cube. This is a simple cube using ACDOCA as data source to present accounting document data. Make sure to add profit center and controlling area add associate to our custom CDS YI_ProfitCenter. We are adding controlling area because as explained above, same hierarchy can be created for multiple controlling area therefore it must be specified in the association join condition.
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Test analytical cube'
@Analytics: { dataCategory: #CUBE }
@VDM.viewType: #COMPOSITE
@ObjectModel.usageType: { sizeCategory: #XXL, dataClass: #MIXED, serviceQuality: #D }
define view entity YANALYTICAL_CUBE
as select from acdoca
association [0..1] to YI_ProfitCenter as _ProfitCenter on $projection.ControllingArea = _ProfitCenter.ControllingArea
and $projection.ProfitCenter = _ProfitCenter.ProfitCenter
association [0..1] to I_ControllingArea as _ControllingArea on $projection.ControllingArea = _ControllingArea.ControllingArea
{
key rldnr as Ledger,
key rbukrs as CompanyCode,
key rbukrs as FiscalYear,
key belnr as AccountingDocument,
key docln as LedgerGLLineItem,
@ObjectModel.foreignKey.association: '_ControllingArea'
kokrs as ControllingArea,
@ObjectModel.foreignKey.association: '_ProfitCenter'
prctr as ProfitCenter,
rhcur as CompanyCodeCurrency,
@DefaultAggregation: #SUM
@Semantics: { amount : {currencyCode: 'CompanyCodeCurrency'} }
hsl as AmountInCompanyCodeCurrency,
_ProfitCenter,
_ControllingArea,
}
CDS Query. Use Cube YANALYTICAL_CUBE as data source to create CDS Query. Make sure to add highlighted annotation to make profit center available as hierarchy for filter as well.
@AbapCatalog.viewEnhancementCategory: [#NONE]
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Test analytical Query'
@Analytics.query: true
@VDM: {viewType: #CONSUMPTION}
@ObjectModel.usageType: { dataClass: #MIXED, sizeCategory: #XXL, serviceQuality: #D }
@ObjectModel.modelingPattern: #ANALYTICAL_QUERY
@ObjectModel.supportedCapabilities: [ #ANALYTICAL_QUERY ]
define view entity YANALYITICAL_QUERY
as select from YANALYTICAL_CUBE
{
Ledger,
@Consumption.filter: { selectionType: #SINGLE, multipleSelections: true, mandatory: true}
@AnalyticsDetails.query.display: #KEY_TEXT
CompanyCode,
FiscalYear,
AccountingDocument,
LedgerGLLineItem,
@AnalyticsDetails.query.displayHierarchy: #FILTER
@Consumption.filter: { selectionType: #HIERARCHY_NODE, multipleSelections: true}
ProfitCenter,
CompanyCodeCurrency,
AmountInCompanyCodeCurrency
}
Save and activate CDS Cube and Query.
Testing hierarchy in analytical query
Go to transaction code RSRT and enter 2C+’Your CDS query’ so in our case 2CYANALYITICAL_QUERY. Choose WD Grid if you want to test with Fiori UI. You can see profit center in the filter and search help should be displayed as hierarchy view. The hierarchy view is applied also in the report as dimension.