Overview
This blog post is to show how to create a 4 level deep draft enabled List report. This will be a lengthy post as many objects need to be created to show how to structure the business object with more than 3 levels and multiple children.
The other objective here is to show how to enable a Flexible Column Layout based List Report to go “deeper” than 3 levels and how to add multiple children to an object page via annotations and edits to manifest.json.
Read More: SAP Fiori System Administration Certification Preparation Guide
This example will use a simple sports hierarchy. The top level will be a “Sport” (i.e. Basketball) table as the root of the BOPF Business Object. The sport table will have a “League” table as a child (i.e. NCAA or NBA). The league table will have a “Team” table under it (i.e Atlanta, Phoenix etc..). And finally the team table will have 2 child tables “Coach” and “Player”. See below.
Table Definitions
Create the tables and define the key associations. Note: Each table should have a UUID as the primary key. There is a workaround for using natural keys instead of UUIDs but it’s not recommended unless there is no other option. There will also be a human readable ID associated with each table that will be assigned via a determination. Also, the tables need the direct parent and the root (zsport) as foreign keys in all child tables. This will become more clear once @ObjectModel annotations are created in the CDS Views.
ZSPORT
@EndUserText.label : 'The Sport Table'
@AbapCatalog.enhancementCategory : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #LIMITED
define table zsport {
key client : abap.clnt not null;
key contract_uuid : snwd_node_key not null;
sport_no : vbeln_va not null;
name : abap.sstring(120);
include /bobf/s_lib_admin_data;
}
ZLEAGUE
@EndUserText.label : 'The League Table'
@AbapCatalog.enhancementCategory : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #LIMITED
define table zleague {
key client : abap.clnt not null;
key leauge_uuid : snwd_node_key not null;
@AbapCatalog.foreignKey.screenCheck : false
sport_uuid : snwd_node_key not null
with foreign key [0..*,1] zsport
where client = zleague.client
and sport_uuid = zleague.sport_uuid;
league_no : posnr_va not null;
name : abap.sstring(120);
include /bobf/s_lib_admin_data;
}
ZTEAM
@EndUserText.label : 'The Team Table'
@AbapCatalog.enhancementCategory : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #LIMITED
define table zteam {
key client : abap.clnt not null;
key team_uuid : snwd_node_key not null;
@AbapCatalog.foreignKey.screenCheck : false
league_uuid : snwd_node_key not null
with foreign key [0..*,1] zleague
where client = zteam.client
and league_uuid = zteam.league_uuid;
@AbapCatalog.foreignKey.screenCheck : false
sport_uuid : snwd_node_key not null
with foreign key [0..*,1] zsport
where client = zteam.client
and sport_uuid = zteam.sport_uuid;
team_no : posnr_va not null;
name : abap.sstring(120);
include /bobf/s_lib_admin_data;
}
ZPLAYER
@EndUserText.label : 'The Player Table'
@AbapCatalog.enhancementCategory : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #LIMITED
define table zplayer {
key client : abap.clnt not null;
key player_uuid : snwd_node_key not null;
@AbapCatalog.foreignKey.screenCheck : false
team_uuid : snwd_node_key not null
with foreign key [0..*,1] zteam
where client = zplayer.client
and team_uuid = zplayer.team_uuid;
@AbapCatalog.foreignKey.screenCheck : false
sport_uuid : snwd_node_key not null
with foreign key [0..*,1] zsport
where client = zplayer.client
and sport_uuid = zplayer.sport_uuid;
player_no : posnr_va not null;
name : abap.sstring(120);
include /bobf/s_lib_admin_data;
}
ZCOACH
@EndUserText.label : 'The Coach Table'
@AbapCatalog.enhancementCategory : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #LIMITED
define table zcoach {
key client : abap.clnt not null;
key coach_uuid : snwd_node_key not null;
@AbapCatalog.foreignKey.screenCheck : false
team_uuid : snwd_node_key not null
with foreign key [0..*,1] zteam
where client = zcoach.client
and team_uuid = zcoach.team_uuid;
@AbapCatalog.foreignKey.screenCheck : false
sport_uuid : snwd_node_key not null
with foreign key [0..*,1] zsport
where client = zcoach.client
and sport_uuid = zcoach.sport_uuid;
player_no : posnr_va not null;
name : abap.sstring(120);
include /bobf/s_lib_admin_data;
}
CDS View Definitions
There are 3 levels of CDS that will be defined. Basic, Composite and Consumption.
Create the Basic Views. Note: Do not alias any column names here. It is necessary to include an association to all direct child views in the parent views. In the child views it is necessary to create an association to the direct parent and the root view (in this case the SPORT view).
As the views are being created, you will need to go back to the parent view to add the child associations once the child is created.
Z_I_SPORT
@AbapCatalog.sqlViewName: 'ZISPORT'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'The Sport Basic View'
@VDM.viewType: #BASIC
define view Z_I_SPORT as select from zsport
{
//zsport
key sport_uuid,
sport_no,
name,
crea_date_time,
crea_uname,
lchg_date_time,
lchg_unamee
}
Z_I_LEAGUE
@AbapCatalog.sqlViewName: 'ZILEAGUE'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'The League Basic View'
@VDM.viewType: #BASIC
define view Z_I_LEAGUE as select from zleague
association[1..1] to Z_I_SPORT as _zsport on $projection.sport_uuid = _zsport.sport_uuid
{
//zleague
key league_uuid,
@ObjectModel.foreignKey.association:'_zsport'
sport_uuid,
@ObjectModel.text.element:['name']
league_no,
@Semantics.text:true
name,
crea_date_time,
crea_uname,
lchg_date_time,
lchg_uname,
//associations
_zsport
}
Now that Z_I_LEAGUE is created, we need to go back to Z_I_SPORT and add an association to it.
Z_I_SPORT(EDIT).
...
define view Z_I_SPORT as select from zsport
association[0..*] to z_i_league as _zleague on $projection.sport_uuid = _zleague.sport_uuid
{
...
//Assocations
_zleague
}
Z_I_TEAM
@AbapCatalog.sqlViewName: 'ZITEAM'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'The Team Basic View'
@VDM.viewType: #BASIC
define view Z_I_TEAM as select from zteam
association[1..1] to Z_I_SPORT as _zsport on $projection.sport_uuid = _zsport.sport_uuid
association[1..1] to Z_I_LEAGUE as _zleague on $projection.league_uuid = _zleague.league_uuid
{
//zteam
key team_uuid,
@ObjectModel.foreignKey.association:'_zleague'
league_uuid,
@ObjectModel.foreignKey.association:'_zsport'
sport_uuid,
@ObjectModel.text.element:['name']
team_no,
@Semantics.text:true
name,
crea_date_time,
crea_uname,
lchg_date_time,
lchg_uname,
//Associations
_zsport,
_zleague
}
Now that Z_I_TEAM is created, go back to Z_I_LEAGUE and add the association to it.
Z_I_LEAGUE(EDIT)
...
define view Z_I_LEAGUE as select from zleague
association[1..1] to Z_I_SPORT as _zsport on $projection.sport_uuid = _zsport.sport_uuid
association[0..*] to Z_I_TEAM as _zteam on $projection.league_uuid = _zteam.league_uuid
{
...
_zteam
}
Z_I_PLAYER
@AbapCatalog.sqlViewName: 'ZIPLAYER'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'The Player Basic View'
@VDM.viewType: #BASIC
define view Z_I_PLAYER
as select from zplayer
association [1..1] to Z_I_SPORT as _zsport on $projection.sport_uuid = _zsport.sport_uuid
association [1..1] to Z_I_TEAM as _zteam on $projection.team_uuid = _zteam.team_uuid
{
//zplayer
key player_uuid,
@ObjectModel.foreignKey.association:'_zteam'
team_uuid,
@ObjectModel.foreignKey.association:'_zsport'
sport_uuid,
@ObjectModel.text.element:['name']
player_no,
@Semantics.text: true
name,
crea_date_time,
crea_uname,
lchg_date_time,
lchg_uname,
//Assocations
_zsport,
_zteam
}
Z_I_COACH
@AbapCatalog.sqlViewName: 'ZICOACH'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'The Coach Basic View'
@VDM.viewType: #BASIC
define view Z_I_COACH
as select from zcoach
association [1..1] to Z_I_SPORT as _zsport on $projection.sport_uuid = _zsport.sport_uuid
association [1..1] to Z_I_TEAM as _zteam on $projection.team_uuid = _zteam.team_uuid
{
//zplayer
key coach_uuid,
@ObjectModel.foreignKey.association:'_zteam'
team_uuid,
@ObjectModel.foreignKey.association:'_zsport'
sport_uuid,
@ObjectModel.text.element:['name']
coach_no,
@Semantics.text: true
name,
crea_date_time,
crea_uname,
lchg_date_time,
lchg_uname,
//Assocations
_zsport,
_zteam
}
Now that Z_I_COACH and Z_I_PLAYER are created, go back to Z_I_TEAM and add the relevant associations
Z_I_TEAM(EDIT)
...
select from zteam
...
association[0..*] to Z_I_PLAYER as _zplayer on $projection.team_uuid = _zplayer.team_uuid
association[0..*] to Z_I_COACH as _zcoach on $projection.team_uuid = _zcoach.team_uuid
{
...
_zplayer,
_zcoach
}
Create the Composite Views.
This is the level where the transactional processing will be defined. A BOPF Business Object will be generated after creating the composite views. Also, do not alias columns at this level either. Similar to the BASIC views, we will need to come back and add the child associations to the parent views after they are created.This will cause BOPF generation errors. You may need to go back through and activate each view after adding the child associations back to the parent.
The @ObjectModel.writeDraftPersistence will automatically create the draft table, there is no need to manually create it. The @ObjectModel.semanticKey is the most specific non UUID field.
Z_I_SPORT_TP
@AbapCatalog.sqlViewName: 'ZISPORTTP'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'The Sport Composite View'
@VDM.viewType: #COMPOSITE
@ObjectModel.modelCategory:#BUSINESS_OBJECT
@ObjectModel.compositionRoot:true
@ObjectModel.transactionalProcessingEnabled:true
@ObjectModel.writeActivePersistence:'ZSPORT'
@ObjectModel.createEnabled:true
@ObjectModel.deleteEnabled:true
@ObjectModel.updateEnabled:true
@ObjectModel.draftEnabled: true
@ObjectModel.semanticKey:['sport_no']
@ObjectModel.writeDraftPersistence: 'ZSPORT_D'
define view Z_I_SPORT_TP as select from Z_I_SPORT {
//Z_I_SPORT
key sport_uuid,
@ObjectModel.text.element:['name']
sport_no,
@Semantics.text:true
name,
crea_date_time,
crea_uname,
lchg_date_time,
lchg_uname,
/* Associations */
//Z_I_SPORT
_zleague
}
Z_I_LEAGUE_TP
@AbapCatalog.sqlViewName: 'ZILEAGUETP'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'The League Composite View.'
@VDM.viewType: #COMPOSITE
@ObjectModel.modelCategory:#BUSINESS_OBJECT
@ObjectModel.writeActivePersistence:'ZLEAGUE'
@ObjectModel.createEnabled:true
@ObjectModel.updateEnabled:true
@ObjectModel.deleteEnabled:true
@ObjectModel.writeDraftPersistence: 'ZLEAGUE_D'
@ObjectModel.semanticKey:['league_no']
define view Z_I_LEAGUE_TP as select from Z_I_LEAGUE
association[1..1] to Z_I_SPORT_TP as _zsport on $projection.sport_uuid = _zsport.sport_uuid
{
//Z_I_LEAGUE
key league_uuid,
sport_uuid,
league_no,
name,
crea_date_time,
crea_uname,
lchg_date_time,
lchg_uname,
/* Associations */
//Z_I_LEAGUE
@ObjectModel.association.type:[ #TO_COMPOSITION_PARENT, #TO_COMPOSITION_ROOT ]
_zsport,
_zteam
}
Now go back to Z_I_SPORT_TP and add the association to Z_I_LEAGUE_TP
Z_I_SPORT_TP(EDIT)
...
define view Z_I_SPORT_TP as select from Z_I_SPORT
association[0..*] to Z_I_LEAGUE_TP as _zleague on $projection.sport_uuid = _zleague.sport_uuid
{
...
@ObjectModel.association.type: [ #TO_COMPOSITION_CHILD ]
_zleague
}
Z_I_TEAM_TP
@AbapCatalog.sqlViewName: 'ZITEAMTP'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'The Team Composite View'
@VDM.viewType: #COMPOSITE
@ObjectModel.modelCategory:#BUSINESS_OBJECT
@ObjectModel.writeActivePersistence:'ZTEAM'
@ObjectModel.createEnabled:true
@ObjectModel.updateEnabled:true
@ObjectModel.deleteEnabled:true
@ObjectModel.writeDraftPersistence: 'ZTEAM_D'
@ObjectModel.semanticKey:['team_no']
define view Z_I_TEAM_TP as select from Z_I_TEAM
association[1..1] to Z_I_SPORT_TP as _zsport on $projection.sport_uuid = _zsport.sport_uuid
association[1..1] to Z_I_LEAGUE_TP as _zleague on $projection.league_uuid = _zleague.league_uuid
{
//Z_I_TEAM
key team_uuid,
league_uuid,
sport_uuid,
team_no,
name,
crea_date_time,
crea_uname,
lchg_date_time,
lchg_uname,
/* Associations */
//Z_I_TEAM
_zcoach,
@ObjectModel.association.type:[ #TO_COMPOSITION_PARENT]
_zleague,
_zplayer,
@ObjectModel.association.type:[ #TO_COMPOSITION_ROOT ]
_zsport
}
Now that Z_I_TEAM_TP is created, go back into Z_I_LEAGUE_TP and add it as an association.
Z_I_LEAGUE_TP(EDIT)
...
define view Z_I_LEAGUE_TP as select from Z_I_LEAGUE
...
association[0..*] to Z_I_TEAM_TP as _zteam on $projection.league_uuid = _zteam.league_uuid
{
...
@ObjectModel.association.type:[ #TO_COMPOSITION_CHILD ]
_zteam
}
Z_I_COACH_TP
@AbapCatalog.sqlViewName: 'ZICOACHTP'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'The Coach Composite View'
@VDM.viewType: #COMPOSITE
@ObjectModel.modelCategory:#BUSINESS_OBJECT
@ObjectModel.writeActivePersistence:'ZCOACH'
@ObjectModel.createEnabled:true
@ObjectModel.updateEnabled:true
@ObjectModel.deleteEnabled:true
@ObjectModel.writeDraftPersistence: 'ZCOACH_D'
@ObjectModel.semanticKey:['coach_no']
define view Z_I_COACH_TP as select from Z_I_COACH
association[1..1] to Z_I_SPORT_TP as _zsport on $projection.sport_uuid = _zsport.sport_uuid
association[1..1] to Z_I_TEAM_TP as _zteam on $projection.team_uuid = _zteam.team_uuid
{
//Z_I_COACH
key coach_uuid,
team_uuid,
sport_uuid,
coach_no,
name,
crea_date_time,
crea_uname,
lchg_date_time,
lchg_uname,
/* Associations */
//Z_I_COACH
@ObjectModel.association.type:[ #TO_COMPOSITION_ROOT]
_zsport,
@ObjectModel.association.type:[ #TO_COMPOSITION_PARENT]
_zteam
}
Z_I_PLAYER_TP
@AbapCatalog.sqlViewName: 'ZIPLAYERTP'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'The Player Composite View'
@VDM.viewType: #COMPOSITE
@ObjectModel.modelCategory:#BUSINESS_OBJECT
@ObjectModel.writeActivePersistence:'ZPLAYER'
@ObjectModel.createEnabled:true
@ObjectModel.updateEnabled:true
@ObjectModel.deleteEnabled:true
@ObjectModel.writeDraftPersistence: 'ZPLAYER_D'
@ObjectModel.semanticKey:['player_no']
define view Z_I_PLAYER_TP as select from Z_I_PLAYER
association[1..1] to Z_I_SPORT_TP as _zsport on $projection.sport_uuid = _zsport.sport_uuid
association[1..1] to Z_I_TEAM_TP as _zteam on $projection.team_uuid = _zteam.team_uuid
{
//Z_I_PLAYER
key player_uuid,
team_uuid,
sport_uuid,
player_no,
name,
crea_date_time,
crea_uname,
lchg_date_time,
lchg_uname,
/* Associations */
//Z_I_PLAYER
@ObjectModel.association.type:[ #TO_COMPOSITION_ROOT]
_zsport,
@ObjectModel.association.type:[ #TO_COMPOSITION_PARENT]
_zteam
}
Now that Z_I_PLAYER_TP and Z_I_COACH_TP are created, go back to Z_I_TEAM_TP and add the relevant associations.
Z_I_TEAM_TP(EDIT)
...
define view Z_I_TEAM_TP as select from Z_I_TEAM
...
association[0..*] to Z_I_PLAYER_TP as _zplayer on $projection.team_uuid = _zplayer.team_uuid
association[0..*] to Z_I_COACH_TP as _zcoach on $projection.team_uuid = _zcoach.team_uuid
{
...
@ObjectModel.association.type:[ #TO_COMPOSITION_CHILD ]
_zcoach,
@ObjectModel.association.type:[ #TO_COMPOSITION_CHILD ]
_zplayer,
...
}
Create the Consumption Views.
These are the Views that will be exposed via an OData service. They will read off of the composite views. They are created much like the BASIC and COMPOSITE views in that you must create them, create the child view then add the child view back as an association to the parent. I will just give them in their entirety here. The one thing to take note of is the @ObjectModel.transactionalProcessingDelegated: true This delegates the transactional processing to the composite views.
Z_C_SPORT
@AbapCatalog.sqlViewName: 'ZCSPORT'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'The Sport Consumption View'
@VDM.viewType: #CONSUMPTION
@ObjectModel.compositionRoot: true
@ObjectModel.transactionalProcessingDelegated: true
@ObjectModel.createEnabled:true
@ObjectModel.deleteEnabled:true
@ObjectModel.updateEnabled:true
@ObjectModel.draftEnabled: true
@ObjectModel.semanticKey:['sport_no']
@Metadata.allowExtensions: true
@UI.headerInfo.description.label: 'Sport'
define view Z_C_SPORT as select from Z_I_SPORT_TP
association[0..*] to Z_C_LEAGUE as _zleague on $projection.sport_uuid = _zleague.sport_uuid
{
//Z_I_SPORT_TP
key sport_uuid,
@ObjectModel.readOnly: true
sport_no,
name,
crea_date_time,
crea_uname,
lchg_date_time,
lchg_uname,
/* Associations */
@ObjectModel.association.type: [ #TO_COMPOSITION_CHILD ]
_zleague
}
Z_C_LEAGUE
@AbapCatalog.sqlViewName: 'ZCLEAGUE'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'The League Consumption View'
@VDM.viewType: #CONSUMPTION
@ObjectModel.semanticKey:['league_no']
@Metadata.allowExtensions: true
@ObjectModel.createEnabled:true
@ObjectModel.deleteEnabled:true
@ObjectModel.updateEnabled:true
@UI.headerInfo.description.label: 'League'
define view Z_C_LEAGUE as select from Z_I_LEAGUE_TP
association[1..1] to Z_C_SPORT as _zsport on $projection.sport_uuid = _zsport.sport_uuid
association[0..*] to Z_C_TEAM as _zteam on $projection.league_uuid = _zteam.league_uuid
{
//Z_I_LEAGUE_TP
key league_uuid,
sport_uuid,
@ObjectModel.readOnly: true
league_no,
name,
crea_date_time,
crea_uname,
lchg_date_time,
lchg_uname,
/* Associations */
//Z_I_LEAGUE_TP
@ObjectModel.association.type: [ #TO_COMPOSITION_ROOT, #TO_COMPOSITION_PARENT ]
_zsport,
@ObjectModel.association.type: [ #TO_COMPOSITION_CHILD ]
_zteam
}
Z_C_TEAM
@AbapCatalog.sqlViewName: 'ZCTEAM'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'The Team Consumption View'
@VDM.viewType: #CONSUMPTION
@ObjectModel.semanticKey:['team_no']
@Metadata.allowExtensions: true
@ObjectModel.createEnabled:true
@ObjectModel.deleteEnabled:true
@ObjectModel.updateEnabled:true
@UI.headerInfo.description.label: 'Team'
define view Z_C_TEAM as select from Z_I_TEAM_TP
association[1..1] to Z_C_SPORT as _zsport on $projection.sport_uuid = _zsport.sport_uuid
association[1..1] to Z_C_LEAGUE as _zleague on $projection.league_uuid = _zleague.league_uuid
association[0..*] to Z_C_COACH as _zcoach on $projection.team_uuid = _zcoach.team_uuid
association[0..*] to Z_C_PLAYER as _zplayer on $projection.team_uuid = _zplayer.team_uuid
{
//Z_I_TEAM_TP
key team_uuid,
league_uuid,
sport_uuid,
@ObjectModel.readOnly: true
team_no,
name,
crea_date_time,
crea_uname,
lchg_date_time,
lchg_uname,
/* Associations */
//Z_I_TEAM_TP
@ObjectModel.association.type: [ #TO_COMPOSITION_CHILD ]
_zcoach,
@ObjectModel.association.type: [ #TO_COMPOSITION_PARENT ]
_zleague,
@ObjectModel.association.type: [ #TO_COMPOSITION_CHILD ]
_zplayer,
@ObjectModel.association.type: [ #TO_COMPOSITION_ROOT ]
_zsport
}
Z_C_COACH
@AbapCatalog.sqlViewName: 'ZCCOACH'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'The Coach Consumption View'
@VDM.viewType: #CONSUMPTION
@ObjectModel.semanticKey:['coach_no']
@Metadata.allowExtensions: true
@ObjectModel.createEnabled:true
@ObjectModel.deleteEnabled:true
@ObjectModel.updateEnabled:true
@UI.headerInfo.description.label: 'Coach'
define view Z_C_COACH as select from Z_I_COACH_TP
association[1..1] to Z_C_SPORT as _zsport on $projection.sport_uuid = _zsport.sport_uuid
association[1..1] to Z_C_TEAM as _zteam on $projection.team_uuid = _zteam.team_uuid
{
//Z_I_COACH_TP
key coach_uuid,
team_uuid,
sport_uuid,
@ObjectModel.readOnly: true
coach_no,
name,
crea_date_time,
crea_uname,
lchg_date_time,
lchg_uname,
/* Associations */
//Z_I_COACH_TP
@ObjectModel.association.type: [ #TO_COMPOSITION_ROOT ]
_zsport,
@ObjectModel.association.type: [ #TO_COMPOSITION_PARENT ]
_zteam
}
Z_C_PLAYER
@AbapCatalog.sqlViewName: 'ZCPLAYER'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'The Player Consumption View'
@VDM.viewType: #CONSUMPTION
@ObjectModel.semanticKey:['player_no']
@Metadata.allowExtensions: true
@ObjectModel.createEnabled:true
@ObjectModel.deleteEnabled:true
@ObjectModel.updateEnabled:true
@UI.headerInfo.description.label: 'Player'
define view Z_C_PLAYER as select from Z_I_PLAYER_TP
association[1..1] to Z_C_SPORT as _zsport on $projection.sport_uuid = _zsport.sport_uuid
association[1..1] to Z_C_TEAM as _zteam on $projection.team_uuid = _zteam.team_uuid
{
//Z_I_PLAYER_TP
key player_uuid,
team_uuid,
sport_uuid,
@ObjectModel.readOnly: true
player_no,
name,
crea_date_time,
crea_uname,
lchg_date_time,
lchg_uname,
/* Associations */
//Z_I_PLAYER_TP
@ObjectModel.association.type: [ #TO_COMPOSITION_ROOT ]
_zsport,
@ObjectModel.association.type: [ #TO_COMPOSITION_PARENT ]
_zteam
}
That completes the 3 levels of CDS Views that are needed on top of the tables. The next step is to create metadata extensions for the Consumption Views to drive the front end elements app.
Add Metadata Extensions
Z_E_SPORT
@Metadata.layer: #CUSTOMER
annotate view Z_C_SPORT
with
{
@UI.facet: [{
label : 'General Information',
id : 'GeneralInfo',
purpose: #STANDARD,
type : #COLLECTION,
position: 10
},
{
label:'Sport Info',
id : 'SportInfo',
purpose: #STANDARD,
parentId : 'GeneralInfo',
type : #FIELDGROUP_REFERENCE,
targetQualifier : 'basic',
position: 20
},
{
label: 'Leagues',
id : 'leagues',
purpose: #STANDARD,
type : #LINEITEM_REFERENCE,
targetElement: '_zleague',
position: 30
}]
@UI.lineItem: [{ importance: #HIGH, label: 'Sport Number', position: 40 }]
@UI.fieldGroup: [{ label: 'Sport Number',
qualifier: 'basic',
position: 40 }]
sport_no;
@UI.lineItem: [{ importance: #HIGH, label: 'Sport Name', position: 50 }]
@UI.fieldGroup: [{ label: 'Sport Name',
qualifier: 'basic',
position: 50 }]
name;
}
Z_E_LEAGUE
@Metadata.layer: #CUSTOMER
annotate view Z_C_LEAGUE
with
{
@UI.facet: [{
label : 'General Information',
id : 'GeneralInfo',
purpose: #STANDARD,
type : #COLLECTION,
position: 10
},
{
label:'League Info',
id : 'LeagueInfo',
purpose: #STANDARD,
parentId : 'GeneralInfo',
type : #FIELDGROUP_REFERENCE,
targetQualifier : 'basic',
position: 20
},
{
label: 'Teams',
id : 'teams',
purpose: #STANDARD,
type : #LINEITEM_REFERENCE,
targetElement: '_zteam',
position: 30
}]
@UI.lineItem: [{ importance: #HIGH, label: 'League Number', position: 40 }]
@UI.fieldGroup: [{ label: 'League Number',
qualifier: 'basic',
position: 40 }]
league_no;
@UI.lineItem: [{ importance: #HIGH, label: 'League Name', position: 50 }]
@UI.fieldGroup: [{ label: 'League Name',
qualifier: 'basic',
position: 50 }]
name;
}
Z_E_TEAM
@Metadata.layer: #CUSTOMER
annotate view Z_C_TEAM
with
{
@UI.facet: [{
label : 'General Information',
id : 'GeneralInfo',
purpose: #STANDARD,
type : #COLLECTION,
position: 10
},
{
label:'Team Info',
id : 'TeamInfo',
purpose: #STANDARD,
parentId : 'GeneralInfo',
type : #FIELDGROUP_REFERENCE,
targetQualifier : 'basic',
position: 20
},
{
label: 'Players',
id : 'player',
purpose: #STANDARD,
type : #LINEITEM_REFERENCE,
targetElement: '_zplayer',
position: 30
},
{
label: 'Coaches',
id : 'coaches',
purpose: #STANDARD,
type : #LINEITEM_REFERENCE,
targetElement: '_zcoach',
position: 40
}]
@UI.lineItem: [{ importance: #HIGH, label: 'Team Number', position: 50 }]
@UI.fieldGroup: [{ label: 'Team Number',
qualifier: 'basic',
position: 50 }]
team_no;
@UI.lineItem: [{ importance: #HIGH, label: 'Team Name', position: 60 }]
@UI.fieldGroup: [{ label: 'Team Name',
qualifier: 'basic',
position: 60 }]
name;
}
Z_E_PLAYER
@Metadata.layer: #CUSTOMER
annotate view Z_C_PLAYER
with
{
@UI.facet: [{
label : 'General Information',
id : 'GeneralInfo',
purpose: #STANDARD,
type : #COLLECTION,
position: 10
},
{
label:'Player Info',
id : 'PlayerInfo',
purpose: #STANDARD,
parentId : 'GeneralInfo',
type : #FIELDGROUP_REFERENCE,
targetQualifier : 'basic',
position: 20
}]
@UI.lineItem: [{ importance: #HIGH, label: 'Player Number', position: 30 }]
@UI.fieldGroup: [{ label: 'Player Number',
qualifier: 'basic',
position: 30 }]
player_no;
@UI.lineItem: [{ importance: #HIGH, label: 'Player Name', position: 40 }]
@UI.fieldGroup: [{ label: 'Player Name',
qualifier: 'basic',
position: 40 }]
name;
}
Z_E_COACH
@Metadata.layer: #CUSTOMER
annotate view Z_C_COACH
with
{
@UI.facet: [{
label : 'General Information',
id : 'GeneralInfo',
purpose: #STANDARD,
type : #COLLECTION,
position: 10
},
{
label:'Coach Info',
id : 'CoachInfo',
purpose: #STANDARD,
parentId : 'GeneralInfo',
type : #FIELDGROUP_REFERENCE,
targetQualifier : 'basic',
position: 20
}]
@UI.lineItem: [{ importance: #HIGH, label: 'Coach Number', position: 30 }]
@UI.fieldGroup: [{ label: 'Coach Number',
qualifier: 'basic',
position: 30 }]
coach_no;
@UI.lineItem: [{ importance: #HIGH, label: 'Coach Name', position: 40 }]
@UI.fieldGroup: [{ label: 'COach Name',
qualifier: 'basic',
position: 40 }]
name;
}
Add Determinations to the BOPF Business Object
In order to internally number the records with a non UUID field, a determination needs to be added to each node of the BOPF Business Object.
1. Open the generated business object in ADT.
2. Select Z_I_SPORT_TP node from the drop down menu beside the BO name
3. Click on “Determinations” then Click “New”
4. Give a Name SPORT_ASSIGN_NO and a Description then click finish.
5. Hold down and click the newly added “SPORT_ASSIGN_NO” in the determinations list
6. Press Activate
7. Click on “Implementation Class”
8. Enter the below code into the /BOBF/IF_FRW_DETERMINATION~EXECUTE method
method /BOBF/IF_FRW_DETERMINATION~EXECUTE.
WITH +both AS ( SELECT sport_no FROM zsport
UNION ALL
SELECT sport_no FROM zsport_d )
SELECT SINGLE
FROM +both
FIELDS MAX( sport_no ) AS sport_no
INTO @DATA(lv_max_sport_no).
IF lv_max_sport_no IS INITIAL.
lv_max_sport_no = '0000000001'.
ENDIF.
DATA lt_data TYPE ztisport_tp.
io_read->retrieve(
EXPORTING
iv_node = is_ctx-node_key
it_key = it_key
IMPORTING
eo_message = eo_message
et_data = lt_data
et_failed_key = et_failed_key
).
LOOP AT lt_data REFERENCE INTO DATA(lr_data).
IF lr_data->sport_no IS INITIAL.
ADD 1 TO lv_max_sport_no.
lr_data->sport_no = lv_max_sport_no.
lr_data->sport_no = |{ lr_data->sport_no alpha = IN }|.
io_modify->update(
EXPORTING
iv_node = is_ctx-node_key
iv_key = lr_data->key
is_data = lr_data
it_changed_fields = VALUE #( ( zif_i_sport_tp_c=>sc_node_attribute-z_i_sport_tp-sport_no ) )
).
ENDIF.
ENDLOOP.
endmethod.
Similar implementations of the EXECUTE method are needed for the rest of the nodes. Code for each posted below.
LEAGUE_ASSIGN_NO
method /BOBF/IF_FRW_DETERMINATION~EXECUTE.
WITH +both AS ( SELECT league_no FROM zleague
UNION ALL
SELECT league_no FROM zleague_d )
SELECT SINGLE
FROM +both
FIELDS MAX( league_no ) AS league_no
INTO @DATA(lv_max_league_no).
IF lv_max_league_no IS INITIAL.
lv_max_league_no = '0000000001'.
ENDIF.
DATA lt_data TYPE ztileague_tp.
io_read->retrieve(
EXPORTING
iv_node = is_ctx-node_key
it_key = it_key
IMPORTING
eo_message = eo_message
et_data = lt_data
et_failed_key = et_failed_key
).
LOOP AT lt_data REFERENCE INTO DATA(lr_data).
IF lr_data->league_no IS INITIAL.
ADD 1 TO lv_max_league_no.
lr_data->league_no = lv_max_league_no.
lr_data->league_no = |{ lr_data->league_no alpha = IN }|.
io_modify->update(
EXPORTING
iv_node = is_ctx-node_key
iv_key = lr_data->key
is_data = lr_data
it_changed_fields = VALUE #( ( zif_i_sport_tp_c=>sc_node_attribute-z_i_league_tp-league_no ) )
).
ENDIF.
ENDLOOP.
endmethod.
TEAM_ASSIGN_NO
method /BOBF/IF_FRW_DETERMINATION~EXECUTE.
WITH +both AS ( SELECT team_no FROM zteam
UNION ALL
SELECT team_no FROM zteam_d )
SELECT SINGLE
FROM +both
FIELDS MAX( team_no ) AS team_no
INTO @DATA(lv_max_team_no).
IF lv_max_team_no IS INITIAL.
lv_max_team_no = '0000000001'.
ENDIF.
DATA lt_data TYPE ztiteam_tp.
io_read->retrieve(
EXPORTING
iv_node = is_ctx-node_key
it_key = it_key
IMPORTING
eo_message = eo_message
et_data = lt_data
et_failed_key = et_failed_key
).
LOOP AT lt_data REFERENCE INTO DATA(lr_data).
IF lr_data->team_no IS INITIAL.
ADD 1 TO lv_max_team_no.
lr_data->team_no = lv_max_team_no.
lr_data->team_no = |{ lr_data->team_no alpha = IN }|.
io_modify->update(
EXPORTING
iv_node = is_ctx-node_key
iv_key = lr_data->key
is_data = lr_data
it_changed_fields = VALUE #( ( zif_i_sport_tp_c=>sc_node_attribute-z_i_team_tp-team_no ) )
).
ENDIF.
ENDLOOP.
endmethod.
PLAYER_ASSIGN_NO
method /BOBF/IF_FRW_DETERMINATION~EXECUTE.
WITH +both AS ( SELECT player_no FROM zplayer
UNION ALL
SELECT player_no FROM zplayer_d )
SELECT SINGLE
FROM +both
FIELDS MAX( player_no ) AS player_no
INTO @DATA(lv_max_player_no).
IF lv_max_player_no IS INITIAL.
lv_max_player_no = '0000000001'.
ENDIF.
DATA lt_data TYPE ztiplayer_tp.
io_read->retrieve(
EXPORTING
iv_node = is_ctx-node_key
it_key = it_key
IMPORTING
eo_message = eo_message
et_data = lt_data
et_failed_key = et_failed_key
).
LOOP AT lt_data REFERENCE INTO DATA(lr_data).
IF lr_data->player_no IS INITIAL.
ADD 1 TO lv_max_player_no.
lr_data->player_no = lv_max_player_no.
lr_data->player_no = |{ lr_data->player_no alpha = IN }|.
io_modify->update(
EXPORTING
iv_node = is_ctx-node_key
iv_key = lr_data->key
is_data = lr_data
it_changed_fields = VALUE #( ( zif_i_sport_tp_c=>sc_node_attribute-z_i_player_tp-player_no ) )
).
ENDIF.
ENDLOOP.
endmethod.
COACH_ASSIGN_NO
method /BOBF/IF_FRW_DETERMINATION~EXECUTE.
WITH +both AS ( SELECT coach_no FROM zcoach
UNION ALL
SELECT coach_no FROM zcoach_d )
SELECT SINGLE
FROM +both
FIELDS MAX( coach_no ) AS coach_no
INTO @DATA(lv_max_coach_no).
IF lv_max_coach_no IS INITIAL.
lv_max_coach_no = '0000000001'.
ENDIF.
DATA lt_data TYPE zticoach_tp.
io_read->retrieve(
EXPORTING
iv_node = is_ctx-node_key
it_key = it_key
IMPORTING
eo_message = eo_message
et_data = lt_data
et_failed_key = et_failed_key
).
LOOP AT lt_data REFERENCE INTO DATA(lr_data).
IF lr_data->coach_no IS INITIAL.
ADD 1 TO lv_max_coach_no.
lr_data->coach_no = lv_max_coach_no.
lr_data->coach_no = |{ lr_data->coach_no alpha = IN }|.
io_modify->update(
EXPORTING
iv_node = is_ctx-node_key
iv_key = lr_data->key
is_data = lr_data
it_changed_fields = VALUE #( ( zif_i_sport_tp_c=>sc_node_attribute-z_i_coach_tp-coach_no ) )
).
ENDIF.
ENDLOOP.
endmethod.
That completes the CDS/BOPF work needed in ADT.
The next step is to expose the Consumption views as an OData service.
Create an OData service
1. Launch T-Code SEGW on the S/4 System.
2. Create a new Service
3. Right Click on “Data Model” and select “Reference”->”Data Source”
4. Enter the name of the root Consumption (Z_C_SPORT)
5. Start with SPORT and select all child nodes (See Below)
6. Press the “Generate Runtime Objects” button and accept the defaults
That’s it for the Service! Now log into the Gateway system and expose the service
Add Service to the Gateway System
1. Run T-Code /n/iwfnd/maint_service on the gateway system.
2. Click “Add Service”
3. Set the System Alias for your S/4 System.
4. Enter the Service Name and Press “Get Services”
5. Select the service and press “Add Selected Services”
6. Assign a package, accept defaults and press continue.
The service is now exposed on the gateway.
Create the FIORI Elements List Report.
1. Login to WebIDE on the the SAP Cloud platform.
2. Your gateway system should be configured as a destination on your cloud platform account.
3. Click on “File”->”New”->”Project From Template”
4. Select “List Report Application” and Click “Next”
5. Enter the Basic Project Information and Click “Next”
6. Select your gateway system and service and click “Next”
7. Select both annotation files and click “Next”
8. Select the data bindings and check “Flexible Column Layout”
At this point, this is a working Draft Enabled list report. However, you cannot create or edit Players or Coaches. By default, in the data binding wizard, you can only specify 3 layers. So, manifset.json must be edited manually to add the Player and Coach object pages.
- Open manifest.json
- find the entry for “ObjectPage|to_zteam”
- Add a pages attribute after the component attribute.
"ObjectPage|to_zteam":{
"navigationProperty":"to_zteam",
"entitySet":"Z_C_TEAM",
"component":{
"name":"sap.suite.ui.generic.template.ObjectPage"
},
"pages":{
"ObjectPage|to_zcoach":{
"navigationProperty":"to_zcoach",
"entitySet":"Z_C_COACH",
"component":{
"name":"sap.suite.ui.generic.template.ObjectPage"
}
},
"ObjectPage|to_zplayer":{
"navigationProperty":"to_zplayer",
"entitySet":"Z_C_PLAYER",
"component":{
"name":"sap.suite.ui.generic.template.ObjectPage"
}
}
}
}
That’s it. You should now be able to create/edit/delete Players and Coaches.