Friday 26 June 2020

SAP ABAP Programming Model for FIORI- List Report Application (Part 1)

I am writing this blog after a long time. The Idea to write this blog is because the challenges I face with getting all the required details at one place while creating a List Report Application based on Fiori Elements.

When I was developing a List Report Application I have to refer lot many blogs and tutorial, that’s why I thought to sum them all at one place so that it can save time for others

As I am trying to club all topics at one place with more importance to complex things (which are usually not covered together) I will not be explaining the basic things here to make this blog crisp (But dont worry if you are beginner I will share the links of other blogs to you to brush the basic concept).

Prerequisite:


◉ Knowledge with ABAP
◉ Basic understanding to CDS View (https://sapabapcentral.blogspot.com/2018/01/spotlight-on-abap-for-sap-hana-again.html)

As the content I am sharing here is too big I will divide it into 3 parts.

◉ This will be the 1st part where we will be creating the CDS views.
◉ In the next part I will explain you the BOPF for Determination, Validation and Action
◉ In the final part I will show you how you can create the App on this CDS view using WebIDE

What you will get from this blog:


◉ We will be creating a List Report Application based on Fiori Elements/CDS View
◉ The Application can handle the CRUD Operation
◉ We will also add Determination, Action and Validation to it using BOPF
◉ To make sure everyone can try it I used the SPFLI and SFLIGHT Table data

Ok Before starting lets see how the end result will look.

1. In the main screen, we have list with data from header table.
2. We have given a filter bar to filter out the data with a Default Search bar too
3. We have enabled Create, Update and Delete
4. We have even added an Action using BOPF

SAP ABAP Certification, SAP ABAP Guides, SAP ABAP Learning, SAP ABAP Certifications, SAP ABAP Exam Prep

Initial List Screen

In the next Detail Screen:

1. We can see we have divided the Page into 3 Sections:
2. We have an Item table displayed as a list in the second section under Flight Details(We can Navigate to it’s detail page too)

SAP ABAP Certification, SAP ABAP Guides, SAP ABAP Learning, SAP ABAP Certifications, SAP ABAP Exam Prep

Detail Page of Header Entry

SAP ABAP Certification, SAP ABAP Guides, SAP ABAP Learning, SAP ABAP Certifications, SAP ABAP Exam Prep

Detail Page of Item Entry

So lets start with the objects:

For this Application we have 5 CDS View and 2 MetaData Extensions

◉ 1 Projection\Transactional View, 1 Cosumption and 1 MetaData extension each for SPFLI and SFLIGHT Table

◉ 1 CDS View for F4 Help

◉ We have one root node for BOPF, which will have 3 classes for- Determination, Action and Validation

We will continue with creating first the basic transaction views of CDS. I have added comments before every code line to help you understand the use of that line:

@AbapCatalog.sqlViewName: 'YIDEMO_SPFLI'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #NOT_REQUIRED // To enable Auth Check
@EndUserText.label: 'Flight Schedule'  //Description for the CDS View
//Transactional CDS View on SPFLI

//To Create a Business Object from the view
@ObjectModel:{semanticKey: 'carrid',
modelCategory: #BUSINESS_OBJECT,
compositionRoot: true,
transactionalProcessingEnabled: true,
writeActivePersistence: 'SPFLI',    //DB Table

//Enable the CRUD Activity
createEnabled: true,
deleteEnabled: true,
updateEnabled: true}

define view ZIDEMO_DDL_SPFLI
  as select from spfli
{
      //spfli Table fields
  key carrid,
  key connid,
      countryfr,
      cityfrom,
      airpfrom,
      countryto,
      cityto,
      airpto,
      fltime,
      deptime,
      arrtime,
      distance,
      distid,
      fltype,
      period
}

Now similarly create a Transaction CDS view for SFLIGHT Item Table

@AbapCatalog.sqlViewName: 'YIDEMO_SFLIGHT'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Transactional CDS for SFLIGHT'
@ObjectModel.semanticKey: 'carrid'
                
@ObjectModel.modelCategory: #BUSINESS_OBJECT 
@ObjectModel.compositionRoot: true  
@ObjectModel.transactionalProcessingEnabled: true  
@ObjectModel.writeActivePersistence: 'SFLIGHT'  //DB Table
                
@ObjectModel.createEnabled: true
@ObjectModel.deleteEnabled: true 
@ObjectModel.updateEnabled: true
define view ZIDEMO_DDL_SFLIGHT as select from sflight {
//sflight
key carrid,
key connid,
key fldate,
price,
currency,
planetype,
seatsmax,
seatsocc,
paymentsum,
seatsmax_b,
seatsocc_b,
seatsmax_f,
seatsocc_f    
}

Create a CDS View for Value Help:

@AbapCatalog.sqlViewName: 'YDEMO_SCURX'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Currency Table for Value Help'
@Search.searchable: true
define view ZDEMO_DDL_SCURX
  as select from scurx
{
      //scurx
      @Search.defaultSearchElement: true
      @Search.fuzzinessThreshold: 0.8
  key currkey,
      @UI.hidden: true //Will not appear in Value help fields
      currdec
}

Now let’s go ahead and create Consumption views. This will define what all fields we will be making available to our Presentation layer in FIORI Application:

@AbapCatalog.sqlViewName: 'YDEMO_SPFLI'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Consumption CDS for SPFLI'
@OData.publish: true

//To Make it BOPF Enabled
@ObjectModel:{semanticKey: 'AirlineCode',
//This will Delegate the CRUD to Transactional CDS
transactionalProcessingDelegated: true,
//To define what all actions are enabled
createEnabled: true,
deleteEnabled: true,
updateEnabled: true }

//Adding the List Title
@UI:{headerInfo: { typeName: 'Flight Detail', typeNamePlural: 'Flight Details',
    title: {
//Check different better Page header option at https://help.sap.com/viewer/cc0c305d2fab47bd808adcad3ca7ee9d/7.5.18/en-US/7d892d2c94cb483ebe28b133373ba109.html
// This come in Detail Page as Header
    label: 'Flight',
    value: 'AirlineCode' -- Reference to element in element list
  },
  description: {
    label: 'Flight Connection',
    value: 'FlightConnectionNo'    -- Reference to element in element list
  } },
  lineItem:[{criticality:'Criticality'}],
//Not working with List report but added for your reference
  chart: {
    title: 'Airfare by Airline',
    description: 'Line-chart displaying the fare amount by airline',
    chartType: #LINE,
    dimensions: [ 'AirlineCode' ],  -- Reference to one element
    measures: [ 'Airfare' ]     -- Reference to one or more elements
}
  }

@Metadata.allowExtensions: true
define view ZDEMO_DDL_SPFLI
  as select from ZIDEMO_DDL_SPFLI as schedule //Transactional CDS
  association        to scarr             as _airline on  _airline.carrid = $projection.AirlineCode //Association for Text Element
  association [1..*] to ZDEMO_DDL_SFLIGHT as _flight  on  _flight.AirlineCode        = $projection.AirlineCode
                                                      and _flight.FlightConnectionNo = $projection.FlightConnectionNo
{
      //schedule
      //Make Key Value as Read Only and Mandatory
      @ObjectModel:{readOnly: true, mandatory: true }
      //Language-independent Text Elements
      //Other 2 ways can be found at:https://help.sap.com/viewer/cc0c305d2fab47bd808adcad3ca7ee9d/7.5.18/en-US/03cc4e1b8fd447789daaa27ec4927328.html
      @ObjectModel.text.element: ['CarrierName']
      //Adding Field Labels and Descriptions
      @EndUserText: { label:  'Airline Code#',
      quickInfo: 'Airline Code part of key' }
  key schedule.carrid    as AirlineCode,
      @ObjectModel:{readOnly: true, mandatory: true }
  key schedule.connid    as FlightConnectionNo,
      @EndUserText: { label:  'Country From',
      quickInfo: 'Airline Country From' }
      @Semantics.address.country: true
      schedule.countryfr as CountryFrom,
      @Semantics.address.city: true
      schedule.cityfrom  as CityFrom,
      schedule.airpfrom  as AirportFrom,
      @EndUserText: { label:  'Country To',
      quickInfo: 'Airline Country To' }
      @Semantics.address.country: true
      schedule.countryto as CountryTo,
      @Semantics.address.city: true
      schedule.cityto    as CityTo,
      schedule.airpto    as AirportTo,
      @Semantics.dateTime: true
      schedule.deptime   as DepartureTime,
      @Semantics.dateTime: true
      schedule.arrtime   as ArrivalTime,
      schedule.distance  as Distance,
      schedule.distid    as DistanceId,
      schedule.fltype    as FlightType,
      schedule.period    as Period,
       @ObjectModel.readOnly: true
      case schedule.carrid
        when 'AA' then 1    //red
        when 'AB' then 2    //yellow
        when 'AZ' then 2    //yellow
        when 'AC' then 3    //green
        else 0  //No change
        end                                                       as Criticality,
      @ObjectModel.readOnly: true
      _airline.carrname  as CarrierName,
      _flight
}

Similarly create a Consumption view for SFLIGHT Table:

@AbapCatalog.sqlViewName: 'YDEMO_FLIGHT'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'List reporting for Flight Details'
@OData.publish: true

//To Make it BOPF Enabled
@ObjectModel:{semanticKey: 'AirlineCode',
//This will Delegate the CRUD to Transactional CDS
transactionalProcessingDelegated: true,
//To define what all actions are enabled
createEnabled: true,
deleteEnabled: true,
updateEnabled: true }

//Adding the List Title
@UI:{headerInfo: { typeName: 'Flight Detail', typeNamePlural: 'Flight Details',
    title: {
//Check different better Page header option at https://help.sap.com/viewer/cc0c305d2fab47bd808adcad3ca7ee9d/7.5.18/en-US/7d892d2c94cb483ebe28b133373ba109.html
// This come in Detail Page as Header
    label: 'Flight',
    value: 'AirlineCode' -- Reference to element in element list
  },
  description: {
    label: 'Flight Connection',
    value: 'FlightConnectionNo'    -- Reference to element in element list
  } },
  lineItem:[{criticality:'Criticality'}],
//Not working with List report
  chart: {
    title: 'Airfare by Airline',
    description: 'Line-chart displaying the fare amount by airline',
    chartType: #LINE,
    dimensions: [ 'AirlineCode' ],  -- Reference to one element
    measures: [ 'Airfare' ]     -- Reference to one or more elements
}
//Below Annotation not working
//,presentationVariant:{sortOrder: [{ by: 'AirlineCode',direction: #ASC },{ by: 'FlightConnectionNo',direction: #ASC },
//
//       { by: ' FlightDate' ,direction: #ASC}]
//  }
  }
//Adding a Standard Search Filter
@Search.searchable: true
@Metadata.allowExtensions: true
define view ZDEMO_DDL_SFLIGHT
  as select from ZIDEMO_DDL_SFLIGHT as flight //Transactional CDS
  association        to scarr           as _airline           on _airline.carrid = $projection.AirlineCode //Association for Text Element
  association [0..1] to ZDEMO_DDL_SCURX as _currencyValueHelp on _currencyValueHelp.currkey = $projection.LocalCurrency //Association for Value Help
{
      //Make Key Value as Mandatory
      @ObjectModel:{ mandatory: true }
      //Language-independent Text Elements
      //Other 2 ways can be found at:https://help.sap.com/viewer/cc0c305d2fab47bd808adcad3ca7ee9d/7.5.18/en-US/03cc4e1b8fd447789daaa27ec4927328.html
      @ObjectModel.text.element: ['CarrierName']
      //Adding Field Labels and Descriptions
      @EndUserText: { label:  'Airline Code#',
      quickInfo: 'Airline Code part of key' }
  key flight.carrid                                               as AirlineCode,
      @ObjectModel:{ mandatory: true }
  key flight.connid                                               as FlightConnectionNo,
      @ObjectModel:{ mandatory: true }
  key flight.fldate                                               as FlightDate,
      @Semantics.amount.currencyCode: 'LocalCurrency'
      //      @DefaultAggregation: #SUM
      flight.price                                                as Airfare,
      @Semantics.currencyCode: true
      //Value Help
      //Other way for Value Help can be found at:https://help.sap.com/viewer/cc0c305d2fab47bd808adcad3ca7ee9d/7.5.18/en-US/8a8415c033d441b2b079a53aff129463.html
      @Consumption.valueHelp: '_currencyValueHelp'
      flight.currency                                             as LocalCurrency,
      flight.planetype                                            as AircraftType,
      //Calculated Column has to make read only with BOPF
      @ObjectModel.readOnly: true
      flight.seatsmax  + flight.seatsmax_b + flight.seatsmax_f    as total_max_seat,
      @ObjectModel.readOnly: true
      (flight.seatsmax  + flight.seatsmax_b + flight.seatsmax_f)
      -(flight.seatsocc  + flight.seatsocc_b + flight.seatsocc_f) as total_avail_seat,
      flight.seatsmax                                             as SeatsMaxEco,
      flight.seatsocc                                             as SeatsOccEco,
      @Semantics.amount.currencyCode: 'LocalCurrency'
      flight.paymentsum                                           as TotalCurrBook,
      flight.seatsmax_b                                           as SeatsMaxBus,
      flight.seatsocc_b                                           as SeatsOccBus,
      flight.seatsmax_f                                           as SeatsMaxFirst,
      flight.seatsocc_f                                           as SeatsOccFirst,
      @ObjectModel.readOnly: true
      _airline.carrname                                           as CarrierName,
      @ObjectModel.readOnly: true
      case flight.carrid
        when 'AA' then 1    //red
        when 'AZ' then 2    //yellow
        when 'DL' then 3    //green
        when 'JL' then 1
        when 'LH' then 2
        when 'SQ' then 3
        else 0  //No change
        end                                                       as Criticality,
      //Expose Association
      @Consumption.filter.hidden: true
      _currencyValueHelp
}

Now lets create the Metadata extension view. We can even have the UI annotations mentioned in the metadata extension view as a part of Consumption view but as a good practice we keep both of them separate. The metadata view will be responsible for the look and feel of our list report application:

Metadata extension view for SPFLI(Header Table)

@Metadata.layer: #CORE
//Add default search bar
@Search.searchable: true
annotate view ZDEMO_DDL_SPFLI with
{
  //ZDEMO_DDL_SPFLI
  //Data Come under Separate Tab
  @UI.facet: [
       {
           label: 'Basic Information',
           id : 'GeneralInfo',
           purpose: #STANDARD,
           type : #COLLECTION,
           position: 10
       },
         { type: #IDENTIFICATION_REFERENCE ,
             label : 'General Information',
             parentId: 'GeneralInfo',
             id: 'idIdentification' ,
             position: 10 },
       {
           label: 'From Data',
           id : 'FromData',
           purpose: #STANDARD,
           parentId : 'GeneralInfo',
           type : #FIELDGROUP_REFERENCE,
           targetQualifier : 'FromData',
           position: 20
       },
       {
           label: 'To Data',
           id : 'ToData',
           purpose: #STANDARD,
           parentId : 'GeneralInfo',
           type : #FIELDGROUP_REFERENCE,
           targetQualifier : 'ToData',
           position: 30
       },
              {
           label: 'Flight Info',
           id : 'FlightInfo',
           purpose: #STANDARD,
           type : #COLLECTION,
           position: 20
       },
       {
           label: 'FlightInfoFG',
           id : 'FG',
           purpose: #STANDARD,
           parentId : 'FlightInfo',
           type : #FIELDGROUP_REFERENCE,
           targetQualifier : 'FG',
           position: 10
       },
       {
           label: 'FlightData',
           id : 'FlightData',
           type : #LINEITEM_REFERENCE,
           targetElement: '_flight' ,
           position: 10
       }
       ]
  //Adding a Filter Bar for Field-Specific Selection
  @UI.selectionField:[{position: 10}]
  //Positioning and Prioritizing UI Elements
  @UI: { lineItem: [{ position: 10,label:'Airline Carrier Code', importance: #HIGH } ,
                  //Action Button
                  { type: #FOR_ACTION, position: 0, dataAction: 'BOPF:Copy',  label: 'Copy Data' }]
        }

  //Positioning on the Details Page
  @UI.identification: [{ position: 10, label:'Airline Carrie Code on Detail Page',importance: #HIGH }]
  //Apply Default Fuzzy Search
  @Search: { defaultSearchElement: true, fuzzinessThreshold: 0.8 ,ranking:#HIGH }
  AirlineCode;
  //Clubing Annotations
  @Search: { defaultSearchElement: true, fuzzinessThreshold: 0.7 ,ranking:#MEDIUM }
  @UI:{selectionField:[{position: 20}],
       lineItem:[{position: 20, importance: #HIGH}]
       }
  @UI.identification: [{ position: 20, importance: #HIGH }]
  @EndUserText.quickInfo: 'Flight Connection Id'
  FlightConnectionNo;
  @UI:{selectionField:[{position: 30}],
    lineItem:[{position: 30, importance: #HIGH}]
    }
  @UI.fieldGroup: [ { qualifier: 'FromData', position: 10,importance: #MEDIUM} ]
  CountryFrom;
  @UI.fieldGroup: [ { qualifier: 'FromData', position: 20,importance: #MEDIUM} ]
  CityFrom;
  @UI.fieldGroup: [ { qualifier: 'FromData', position: 30,importance: #MEDIUM} ]
  AirportFrom;
  @UI:{selectionField:[{position: 40}],
  lineItem:[{position: 40, importance: #HIGH}]
  }
  @UI.fieldGroup: [ { qualifier: 'ToData', position: 10,importance: #MEDIUM} ]
  CountryTo;
  @UI.fieldGroup: [ { qualifier: 'ToData', position: 20,importance: #MEDIUM} ]
  CityTo;
  @UI.fieldGroup: [ { qualifier: 'ToData', position: 30,importance: #MEDIUM} ]
  AirportTo;
  //  @UI.identification: [{ position: 30, importance: #LOW }]
  //  FlightTime;
  @UI.identification: [{ position: 40, importance: #HIGH }]
  DepartureTime;
  @UI.identification: [{ position: 50, importance: #HIGH }]
  ArrivalTime;
  @UI.hidden: true
  Distance;
  @UI.hidden: true
  @UI.fieldGroup: [ {  qualifier: 'FG',position: 10,importance: #MEDIUM} ]
  DistanceId;
  @UI.identification: [{ position: 60, importance: #HIGH }]
  FlightType;
  @UI.hidden: true
  Period;
  @Search: { defaultSearchElement: true, fuzzinessThreshold: 0.7 ,ranking:#HIGH }
  CarrierName;
}

Similarly we will create Metadata extension view for SFLIGHT. I have created it such that we can use it to have a independent List report Application:

@Metadata.layer: #CORE
//Add default search bar
@Search.searchable: true
annotate view ZDEMO_DDL_SFLIGHT with
{
  //Data Come under Separate Tab
  @UI.facet: [
       {
           label: 'Basic Information',
           id : 'GeneralInfo',
           purpose: #STANDARD,
           type : #COLLECTION,
           position: 10
       },
         { type: #IDENTIFICATION_REFERENCE ,
             label : 'General Information',
             parentId: 'GeneralInfo',
             id: 'idIdentification' ,
             position: 10 },
       {
           label: 'Flight Data',
           id : 'FlightData',
           purpose: #STANDARD,
           parentId : 'GeneralInfo',
           type : #FIELDGROUP_REFERENCE,
           targetQualifier : 'FlightData',
           position: 20
       },             
       {
           label: 'Seats Data',
           id : 'SeatsData',
           purpose: #STANDARD,
           parentId : 'GeneralInfo',
           type : #FIELDGROUP_REFERENCE,
           targetQualifier : 'SeatsData',
           position: 30
       },
              {
           label: 'Flight Info',
           id : 'FlightInfo',
           purpose: #STANDARD,
           type : #COLLECTION,
           position: 20
       }]
  //Adding a Filter Bar for Field-Specific Selection
  @UI.selectionField:[{position: 10}]
  //Positioning and Prioritizing UI Elements
  @UI.lineItem: [{ position: 10, label:'Airline Carrier Code', importance: #HIGH}]
  //Positioning on the Details Page
  @UI.identification: [{ position: 10, label:'Airline Carrie Code on Detail Page',importance: #HIGH }]
  //Apply Default Fuzzy Search
  @Search: { defaultSearchElement: true, fuzzinessThreshold: 0.8 ,ranking:#HIGH }
  AirlineCode;
  //Clubing Annotations
  @Search: { defaultSearchElement: true, fuzzinessThreshold: 0.7 ,ranking:#MEDIUM }
  @UI:{selectionField:[{position: 20}],
       lineItem:[{position: 20, importance: #HIGH}]
       }
  @UI.identification: [{ position: 20, importance: #HIGH }]
  @EndUserText.quickInfo: 'Flight Connection Id'
  FlightConnectionNo;
  @UI:{selectionField:[{position: 30}],
       lineItem:[{position: 30, importance: #HIGH}]
       }
  @UI.identification: [{ position: 30, importance: #HIGH }]
  @EndUserText.quickInfo: 'Date of Flight'
  FlightDate;
  @UI.lineItem:[{position: 50, importance: #HIGH}]
  @UI.fieldGroup: [ { qualifier: 'FlightData', position: 20,importance: #MEDIUM} ]
  Airfare;
  @UI.fieldGroup: [ { qualifier: 'FlightData', position: 30,importance: #MEDIUM } ]
  LocalCurrency;
  @UI.lineItem:[{position: 40, importance: #HIGH,criticality: 'Criticality'}]
 //If both below Annotations are kept then it will repeat in both Field Group  
//  @UI.identification: [{ position: 40, importance: #HIGH,criticality: 'Criticality' }]
  @UI.fieldGroup: [ { qualifier: 'FlightData', position: 10,importance: #HIGH,criticality: 'Criticality' } ]
  AircraftType;
  @UI.lineItem:[{position: 60, importance: #MEDIUM,label: 'Total Seats'}]
  //Below Annotation added as the field label not changed in filter bar
  @EndUserText.label: 'Total Seats'
  total_max_seat;
  @UI.lineItem:[{position: 70, importance: #MEDIUM,label: 'Available Seats'}]
  @EndUserText.label: 'Available Seats'
  total_avail_seat;
  //To group similar type of fields
  @UI.fieldGroup: [ { qualifier: 'SeatsData', position: 10 } ]
  SeatsMaxEco;
  @UI.fieldGroup: [ { qualifier: 'SeatsData', position: 20 } ]
  SeatsOccEco;
  //If both Annotations are kept then it will repeat in both Field Group
  //  @UI.identification: [{ position: 90, importance: #LOW }]
  @UI.fieldGroup: [ { qualifier: 'SeatsData', position: 30,importance: #LOW } ]
  SeatsMaxBus;
  @UI.fieldGroup: [ { qualifier: 'SeatsData', position: 40,importance: #LOW } ]
  SeatsOccBus;
  //Will not appear in filter fields
  @UI.hidden: true
  @UI.identification: [{ position: 110, importance: #LOW }]
  SeatsMaxFirst;
  @UI.hidden: true
  SeatsOccFirst;
  @Search: { defaultSearchElement: true, fuzzinessThreshold: 0.8 ,ranking:#HIGH }
  CarrierName;
}

No comments:

Post a Comment