BTP Fullstack Development using S4 RAP OData , BTP CAP Service and Angular Framework

Estimated read time 24 min read

Overview

This blog will help you understand end-to-end development of BTP applications. In this blog, I have used RAP based S4 service and exposed this service to BTP using Cloud Connector. Then on BTP, this service was exposed to CAP framework which can be easily accessed by UI framework on BTP. In this blog I have used Angular framework. It is an example of Side-by-Side extension or clean Core approach on BTP.

Prerequisites

S4 HANA Neatwever System 740+Cloud ConnectorBTP Subaccont XSUAA Services, Connectivity, Destination ServicesBAS or VS Code IDE , ABAP Development Tool

1. OData Development using RAP Framework

Here I will start with every step. I am using 2 Tables.

a.  Create SAP DB tables

 

 

 

 

@EndUserText.label : ‘Vechile DB’
@AbapCatalog.enhancement.category : #EXTENSIBLE_ANY
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table yvechile_rt {
key client : syst_mandt not null;
key vechile_id : uuid not null;
vechile_variant_id : uuid;
@EndUserText.label : ‘Registration Plate Number’
vechile_plate_number : abap.char(16);
@EndUserText.label : ‘Number of Wheels’
vechile_wheel_count : abap.numc(2);
@EndUserText.label : ‘Manufacturar Compnay Name’
manufacturar : abap.char(32);
@EndUserText.label : ‘Date of Purchase’
date_of_purchase : abap.dats;
@EndUserText.label : ‘Registartion Date’
registration_date : abap.dats;
@EndUserText.label : ‘Owner Name’
owner_name : abap.char(32);
local_created_by : abp_creation_user;
local_created_at : abp_creation_tstmpl;
local_last_changed_by : abp_locinst_lastchange_user;
local_last_changed_at : abp_locinst_lastchange_tstmpl;
last_changed_at : abp_lastchange_tstmpl;

}@EndUserText.label : ‘Vechile Service Records’
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table yvechile_serv_rt {
key client : abap.clnt not null;
@AbapCatalog.foreignKey.screenCheck : true
key vechile_id : uuid not null
with foreign key [0..*,1] yvechile_rt
where vechile_id = yvechile_serv_rt.vechile_id;
key service_count : abap.numc(3) not null;
service_date : abap.dats;
distance_travel : abap.numc(10);
issue_reported : abap.string(256);
total_cost : abap.fltp;
local_created_by : abp_creation_user;
local_created_at : abp_creation_tstmpl;
local_last_changed_by : abp_locinst_lastchange_user;
local_last_changed_at : abp_locinst_lastchange_tstmpl;
last_changed_at : abp_lastchange_tstmpl;

}

 

 

 

 

b. Create BASIC CDS View in Table.

 

 

 

 

@AbapCatalog.sqlViewName: ‘YVECINRT’
@AbapCatalog.compiler.compareFilter: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@VDM.viewType: #BASIC
@EndUserText.label: ‘Vechile Informations Basic’
@ObjectModel.usageType: { serviceQuality: #C, sizeCategory:#L , dataClass:#TRANSACTIONAL }
@ClientHandling.algorithm: #SESSION_VARIABLE
define view Y_VECH_INFO_BASE_RT as select from yvechile_rt
{
key vechile_id as VechileId,
vechile_variant_id as VechileVariantId,
vechile_plate_number as VechilePlateNumber,
vechile_wheel_count as VechileWheelCount,
manufacturar as Manufacturar,
date_of_purchase as DateOfPurchase,
registration_date as RegistrationDate,
owner_name as OwnerName
}@AbapCatalog.sqlViewName: ‘YVECSERRT’
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: ‘Vechile Service Record Basic’
@ObjectModel.usageType: { serviceQuality: #C, sizeCategory:#L , dataClass:#TRANSACTIONAL }
@ClientHandling.algorithm: #SESSION_VARIABLE
define view Y_VECH_SERVICE_REC_RT as select from yvechile_serv_rt
{
key vechile_id as VechileId,
key service_count as ServiceCount,
service_date as ServiceDate,
distance_travel as DistanceTravel,
issue_reported as IssueReported,
total_cost as TotalCost
}

 

 

 

 

c. Create Composite CDS Views on Basic CDS.

 

 

 

 

@AbapCatalog.sqlViewName: ‘YVECHILE’
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: ‘Vechile Information’
@VDM.viewType: #COMPOSITE
@ObjectModel.usageType: { serviceQuality: #C, sizeCategory:#L , dataClass:#TRANSACTIONAL }
@ClientHandling.algorithm: #SESSION_VARIABLE
define view YI_VECH_INFO_RT as select from Y_VECH_INFO_BASE_RT as _vechInfo
association [0..*] to Y_VECH_SERVICE_REC_RT as _service_record on $projection.VechileId = _service_record.VechileId
{
key VechileId,
VechileVariantId,
VechilePlateNumber,
VechileWheelCount,
Manufacturar,
DateOfPurchase,
RegistrationDate,
OwnerName,
//Association
_service_record
}

 

 

 

 

d. Create Transactional CDS Views on Composite Views.

 

 

 

 

@AccessControl.authorizationCheck: #NOT_REQUIRED
@VDM.viewType: #TRANSACTIONAL
@VDM.usage.type: [#TRANSACTIONAL_PROCESSING_SERVICE]
@ObjectModel.usageType: { serviceQuality: #C, sizeCategory:#L , dataClass:#TRANSACTIONAL }
@EndUserText.label: ‘Vechile Info RAP View’

define root view entity YR_VECH_INFO_RT as select from YI_VECH_INFO_RT
composition [*] of YR_VECH_SERVICE_REC_RT as _service_record
{
key VechileId,
VechileVariantId,
VechilePlateNumber,
VechileWheelCount,
Manufacturar,
DateOfPurchase,
RegistrationDate,
OwnerName,
_VariantName.VariantName,
_VariantName.Colour,
_VariantName.gear_type,
/* Associations */
_service_record

}@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: ‘Vechile Variant RAP’
@VDM.viewType: #TRANSACTIONAL
@VDM.usage.type: [#TRANSACTIONAL_PROCESSING_SERVICE]
@ObjectModel.usageType: { serviceQuality: #C, sizeCategory:#L , dataClass:#TRANSACTIONAL }
define view entity YR_VECH_SERVICE_REC_RT as select from Y_VECH_SERVICE_REC_RT
association to parent YR_VECH_INFO_RT as _vechInfo on $projection.VechileId = _vechInfo.VechileId
{
key VechileId,
key ServiceCount,
ServiceDate,
DistanceTravel,
IssueReported,
TotalCost,
_vechInfo
}

 

 

 

 

e. Create Consumption Views on Transactional Views

 

 

 

 

@ObjectModel: { usageType: { dataClass: #TRANSACTIONAL,
sizeCategory: #L,
serviceQuality: #C } }
@VDM: { viewType: #CONSUMPTION,
usage: { type: [ #TRANSACTIONAL_PROCESSING_SERVICE ] },
lifecycle.contract.type: #PUBLIC_LOCAL_API}
@AccessControl.authorizationCheck: #NOT_REQUIRED
@Metadata: { allowExtensions: true }
@EndUserText.label: ‘Vechile Information Consumption View’
define root view entity YC_VECH_INFO_RT
provider contract transactional_query
as projection on YR_VECH_INFO_RT

{
.lineItem: [{ position: 10, label: ‘Vechile ID’ }]
@EndUserText:{ label: ‘Vechile ID’, quickInfo: ‘Vechile ID’}
key VechileId,
.lineItem: [{ position: 20, label: ‘Vechile Variant ID’ }]
@EndUserText:{ label: ‘Vechile Variant ID’, quickInfo: ‘Vechile Variant ID’}
@Consumption.valueHelpDefinition: [{ entity: { name: ‘Y_VECH_VARIANT_VH’, element: ‘VechileVariantId’}
}]
VechileVariantId,
.lineItem: [{ position: 30, label: ‘Plate Number’ }]
@EndUserText:{ label: ‘Plate Number’, quickInfo: ‘Plate Number’}
VechilePlateNumber,
.lineItem: [{ position: 40, label: ‘No. of Wheels’ }]
@EndUserText:{ label: ‘No. of Wheels’, quickInfo: ‘No. of Wheels’}
VechileWheelCount,
.lineItem: [{ position: 50, label: ‘Manufacturar’ }]
@EndUserText:{ label: ‘Manufacturar’, quickInfo: ‘Manufacturar’}
Manufacturar,

DateOfPurchase,

RegistrationDate,

OwnerName,

VariantName,

gear_type,

Colour,
/* Associations */
_service_record : redirected to composition child YC_VECH_SERVICE_REC_RT
}@ObjectModel: { usageType: { dataClass: #TRANSACTIONAL,
sizeCategory: #L,
serviceQuality: #C } }
@VDM: { viewType: #CONSUMPTION,
usage: { type: [ #TRANSACTIONAL_PROCESSING_SERVICE ] },
lifecycle.contract.type: #PUBLIC_LOCAL_API}
@AccessControl.authorizationCheck: #NOT_REQUIRED
@Metadata: { allowExtensions: true }
@EndUserText.label: ‘Vechile Service Record Consumption View’
define view entity YC_VECH_SERVICE_REC_RT as projection on YR_VECH_SERVICE_REC_RT
{
.lineItem: [{ position: 10, label: ‘Vechile ID’ }]
@EndUserText:{ label: ‘Vechile ID’, quickInfo: ‘Vechile ID’}
key VechileId,
.lineItem: [{ position: 20, label: ‘Service Count’ }]
@EndUserText:{ label: ‘Service Count’, quickInfo: ‘Service Count’}
key ServiceCount,
.lineItem: [{ position: 30, label: ‘Service Date’ }]
@EndUserText:{ label: ‘Service Date’, quickInfo: ‘Service Date’}
ServiceDate,
.lineItem: [{ position: 40, label: ‘Distance Travel’ }]
@EndUserText:{ label: ‘Distance Travel’, quickInfo: ‘Distance Travel’}
DistanceTravel,
IssueReported,
TotalCost,
/* Associations */
_vechInfo : redirected to parent YC_VECH_INFO_RT
}

 

 

 

 

f. Metadata Extension 

 

 

 

 

@Metadata.layer: #CORE
@Search.searchable : true
@UI: {
headerInfo: { typeName: ‘VechileInformation’,
typeNamePlural: ‘Vechile Information’,
typeImageUrl: ‘sap-icon://Fiori5/F0830’,
title:
{ type: #STANDARD,
value:’VechileId’
}

}
}

@UI.presentationVariant: [
{
sortOrder: [
{by: ‘VechileId’, direction: #DESC },
{by: ‘VechileId’, direction: #DESC }
]
}
]
annotate view YC_VECH_INFO_RT
with
{

@UI.facet: [
{
id: ‘Vehicle’,
purpose: #STANDARD,
type: #IDENTIFICATION_REFERENCE,
label: ‘Vehicle’,
position: 10
},
{
id: ‘BasicData’,
purpose: #STANDARD,
type: #FIELDGROUP_REFERENCE,
label: ‘Vehicle’,
position: 10
},
{
label: ‘Service History’,
id: ‘VechSRecordChild1’,
type: #LINEITEM_REFERENCE,
targetElement: ‘_service_record’,
position: 20,
purpose: #STANDARD,
importance: #HIGH
}

]
@EndUserText.label: ‘Vechile ID’
: {
lineItem: [{ position: 10 ,hidden: false,importance: #HIGH } ],
fieldGroup: [{qualifier: ‘Header’, position: 10 , label: ‘Vechile ID’}],
textArrangement: #TEXT_FIRST,
selectionField: [ { position: 10 } ]
}
@Search: {
defaultSearchElement: true,
fuzzinessThreshold: 0.8,
ranking: #HIGH
}
VechileId;
@Search.defaultSearchElement: true
: {lineItem: [{ position: 20 ,hidden: false,importance: #HIGH }] ,
fieldGroup: [{qualifier: ‘BasicData’, position: 20 , label: ‘Vechile Variant ID’}],
identification: [{ position: 5 , label: ‘Vechile Variant ID’}],
selectionField: [ { position: 20 } ] }
@EndUserText: { label: ‘Vechile Variant ID’, quickInfo: ‘Vechile Variant ID’ }
VechileVariantId;
: {lineItem: [{ position: 25 ,hidden: false,importance: #HIGH }] ,
fieldGroup: [{qualifier: ‘BasicData’, position: 30 , label: ‘Variant Name’}] ,
selectionField: [ { position: 30 }]}
@EndUserText: { label: ‘Variant Name’ }
VariantName;
: {lineItem: [{ position: 30 ,hidden: false,importance: #HIGH }] ,
fieldGroup: [{qualifier: ‘BasicData’, position: 30 , label: ‘Vechile Plate Number’}] }
:{ identification: [{ position: 10 , label: ‘Vechile Plate Number’}] }
@EndUserText: { label: ‘Vechile Plate Number’, quickInfo: ‘Vechile Plate Number’ }
VechilePlateNumber;
: {lineItem: [{ position: 40 ,hidden: false,importance: #HIGH }] ,
fieldGroup: [{qualifier: ‘BasicData’, position: 40 , label: ‘Wheel Count’}] }
:{ identification: [{ position: 15 , label: ‘Wheel Count’}] }
@EndUserText: { label: ‘Wheel Count’, quickInfo: ‘Wheel Count’ }
VechileWheelCount;
: {lineItem: [{ position: 50 ,hidden: false,importance: #HIGH }] ,
fieldGroup: [{qualifier: ‘BasicData’, position: 50 , label: ‘Manufacturar’}] }
:{ identification: [{ position: 20 , label: ‘Manufacturar’}] }
@EndUserText: { label: ‘Manufacturar’, quickInfo: ‘Manufacturar’ }
Manufacturar;
: {lineItem: [{ position: 60 ,hidden: false,importance: #HIGH }] ,
fieldGroup: [{qualifier: ‘BasicData’, position: 60 , label: ‘Date Of Purchase’}] }
:{ identification: [{ position: 30 , label: ‘Date Of Purchase’}] }
@EndUserText: { label: ‘Date Of Purchase’, quickInfo: ‘Date Of Purchase’ }
DateOfPurchase;
: {lineItem: [{ position: 70 ,hidden: false,importance: #HIGH }] ,
fieldGroup: [{qualifier: ‘BasicData’, position: 70 , label: ‘Registration Date’}] }
:{ identification: [{ position: 40 , label: ‘Registration Date’}] }
@EndUserText: { label: ‘Registration Date’, quickInfo: ‘Registration Date’ }
RegistrationDate;
: {lineItem: [{ position: 80 ,hidden: false,importance: #HIGH }] ,
fieldGroup: [{qualifier: ‘BasicData’, position: 80 , label: ‘Owner Name’}] }
:{ identification: [{ position: 50 , label: ‘Owner Name’}] }
@EndUserText: { label: ‘Owner Name’, quickInfo: ‘Owner Name’ }
OwnerName;
}@Metadata.layer: #CORE
@UI: {
headerInfo: { typeName: ‘Vehicle Service History’,
typeNamePlural: ‘Vehicles Service History’,
title: { type: #STANDARD, label: ‘Vehicle Variants’, value: ‘VechileId’ } },
presentationVariant: [{ sortOrder: [{ by: ‘VechileId’, direction: #DESC }] }] }
annotate view YC_VECH_SERVICE_REC_RT
with
{
.facet: [ { id: ‘VehicleService’,
purpose: #STANDARD,
type: #IDENTIFICATION_REFERENCE,
label: ‘Vehicle Service Hostiry’,
position: 10 } ]
.hidden: true
VechileId;
@EndUserText.label: ‘Service Count’
.hidden: true
ServiceCount;
: { lineItem: [ { position: 10 } ],
identification: [ { position: 10 } ],
selectionField: [ { position: 10 } ]}

@EndUserText.label: ‘Service Date’
ServiceDate;
: { lineItem: [ { position: 20 } ],
identification: [ { position: 20 } ],
selectionField: [ { position: 20 } ]}

@EndUserText.label: ‘Distance Travel ‘
DistanceTravel;
: { lineItem: [ { position: 30 } ],
identification: [ { position: 30 } ],
selectionField: [ { position: 30 } ]}

@EndUserText.label: ‘Issue Reported ‘
IssueReported;
: { lineItem: [ { position: 40 } ],
identification: [ { position: 40 } ],
selectionField: [ { position: 40 } ]}

@EndUserText.label: ‘Total Cost’
TotalCost;

}

 

 

 

 

g. Create Behaviour Definition 

BDEF – YR_VECH_INFO_RT

 

 

 

 

managed implementation in class zcl_r_vech_info_rt unique;
strict ( 2 );
with draft;
extensible {
with determinations on modify;
with determinations on save;
with validations on save;
with additional save;
}

define behavior for YR_VECH_INFO_RT alias VechileInformation
persistent table yvechile_rt
draft table yvechile_rt_d
lock master
total etag VechileId
//authorization master ( instance )
authorization master ( instance )
etag master VechileId
extensible
{
draft action Edit;
draft action Activate optimized;
draft action Discard;
draft action Resume;
draft determine action Prepare extensible;
field ( numbering : managed , readonly ) VechileId;
create;
update;
delete;
association _service_record { create; }

determination VechileVariantId on save { field VechileVariantId ; }
validation Validate_variantId on save { field VechileVariantId ;}
// action ( features : instance ) CheckVariant ;
determine action Action_Vech extensible {
determination ( always ) VechileVariantId ;
validation ( always ) Validate_variantId;

}

//association _variant { create; }
mapping for yvechile_rt corresponding extensible {
VechileId = vechile_id;
VechileVariantId = vechile_variant_id ;
VechilePlateNumber = vechile_plate_number ;
VechileWheelCount = vechile_wheel_count ;
Manufacturar = manufacturar ;
DateOfPurchase = date_of_purchase ;
RegistrationDate = registration_date ;
OwnerName = owner_name ;
}
}

define behavior for YR_VECH_SERVICE_REC_RT alias VechileServiceRecord
persistent table yvechile_serv_rt
draft table yvechile_serv_d
lock dependent by _vechInfo
authorization dependent by _vechInfo
etag master VechileId
early numbering
{
field ( readonly ) ServiceCount;
update;
delete;
field ( readonly ) VechileId;
association _vechInfo;
mapping for yvechile_serv_rt{
VechileId = vechile_id ;
ServiceCount = service_count ;
ServiceDate = service_date ;
DistanceTravel = distance_travel ;
IssueReported = issue_reported ;
TotalCost = total_cost ;
}
}

 

 

 

 

BDEF – YC_VECH_INFO_RT

 

 

 

 

projection;
strict ( 2 );
extensible;

define behavior for YC_VECH_INFO_RT //alias <alias_name>
extensible
{
use create;
use update;
use delete;

use association _service_record { create; }
}

define behavior for YC_VECH_SERVICE_REC_RT //alias <alias_name>
{
use update;
use delete;

use association _vechInfo;
}

 

 

 

 

I. Behavioue Implementation 

Implement class – zcl_r_vech_info_rt in ADT.

 

 

 

 

CLASS zcl_r_vech_info_rt DEFINITION PUBLIC ABSTRACT FINAL FOR BEHAVIOR OF yr_vech_info_rt.
PUBLIC SECTION.

ENDCLASS.

CLASS zcl_r_vech_info_rt IMPLEMENTATION.
ENDCLASS.

 

 

 

 

Open Local Types and Copy the below code.

 

 

 

 

CLASS lhc_VechileInformation DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.

METHODS get_instance_authorizations FOR INSTANCE AUTHORIZATION
IMPORTING keys REQUEST requested_authorizations FOR VechileInformation RESULT result.
METHODS vechilevariantid FOR DETERMINE ON SAVE
IMPORTING keys FOR vechileinformation~vechilevariantid.
METHODS validate_variantid FOR VALIDATE ON SAVE
IMPORTING keys FOR vechileinformation~validate_variantid.

METHODS earlynumbering_cba_service_rec FOR NUMBERING
IMPORTING entities FOR CREATE vechileinformation_service_record.

ENDCLASS.

CLASS lhc_VechileInformation IMPLEMENTATION.

METHOD get_instance_authorizations.
ENDMETHOD.

METHOD earlynumbering_cba_Service_rec.
LOOP AT entities ASSIGNING FIELD-SYMBOL(<fs_vech_service>) .
SELECT MAX( service_count ) from yvechile_serv_rt into (lv_service_count) where vechile_id = @<fs_vech_service>-VechileId.
lv_service_count += 1.
LOOP AT <fs_vech_service>-%target ASSIGNING FIELD-SYMBOL(<fs_vech>) .
APPEND VALUE #( %cid = <fs_vech>-%cid
VechileId = <fs_vech>-VechileId
ServiceCount = lv_service_count
) TO mapped-vechileservicerecord .
ENDLOOP.
ENDLOOP.
ENDMETHOD.
ENDCLASS.

 

 

 

 

J. Service Definitions

Create UI_VECHILE_INFO_RT Service Definition and Copy below code.

 

 

 

 

@EndUserText.label: ‘Vechile Service Defination’
@AbapCatalog.extensibility: {extensible: true}
define service UI_VECHILE_INFO_RT {
expose YC_VECH_INFO_RT as VechileInformation;
expose YC_VECH_SERVICE_REC_RT as VechileServiceRecord;
expose YC_VECH_VARIANT_BASE_RT as VechileVariant;
}

 

 

 

 

K. Service Binding 

Create Service Binding – UI_VECHILE_INFO_RT using the above Service Definition.

Save, Activate, and Publish the service binding.

Select VechileInformation Node and Click the Preview Button to view the generated UI from Fiori Framework.

Object page:

Also exposed V4 Web Service using same Service Binding.

2. Exposure of OData using Cloud Connector and Destination Creation

For this POC I am using my BTP Trail account. Copy the Subaccount ID of BTP Account and Add the Subaccount in Cloud Connector.

Goto Cloud To On-Premise Section and Add System.

Add Resources.

Check Connection under Action, it should be green Reachable. 

Create Destination on BTP .

 

3. Exposure of S4 OData Services to BTP using CAP Framework

Login to BAS Instance and clone the below git project to your environment.

https://github.com/Love2Explore/BTP_Advance

Here all S4 OData entities have been exposed to CAP layer by using this code. It can be found here- 

BTP_Advance/srv/VechileCatalog.js

 

const cds = require(“@sap/cds”);
const {vechile} = require(‘./ConnectionHandler’)
module.exports = cds.service.impl(async function(){
//const {VechileInformation,VechileVariant,SafetyCertificate} = this.entities;

this.on(‘READ’,[‘VechileInformation’,’VechileVariant’],async (req)=>{
const backend = await cds.connect.to(‘vechile’)
const tx = backend.tx(req);
return tx.run(req.query);
})
})

 

4. Use CAP Services in Angular Framework

Here I have used the Angular framework which can be found here – https://github.com/Love2Explore/BTP_Advance/tree/main/app/vechile-app/src/app

CAP services are used in TS file, Eg: vechile/vechile.component.ts

 

import { Component ,OnInit} from ‘@angular/core’;
import {HttpClient} from ‘@angular/common/http’;
import { CommonModule } from ‘@angular/common’;
@Component({
selector: ‘app-vechile’,
standalone: true,
imports: [CommonModule],
templateUrl: ‘./vechile.component.html’,
styleUrl: ‘./vechile.component.css’
})
export class VechileComponent implements OnInit{
Vechiles:any = []
message:string = ”
constructor(private _httpclient:HttpClient){
this._httpclient.get(“./odata/v4/vechile-catalog/VechileInformation”)
.subscribe(res=>{
this.Vechiles = res;
console.log(this.Vechiles)
})
}
ngOnInit(): void {
}
deletedTodo(t:any){
console.log(t)
}
}

 

5. Deployment of Application to BTP Subaccount

Build mtar file using mta.yaml.

Login to BTP account from BAS using cf login.

Deploy .mtar file on BTP account.

 

#BTP #CAP #RAP #Angular #Destination @#Connectivity

 

 

​ OverviewThis blog will help you understand end-to-end development of BTP applications. In this blog, I have used RAP based S4 service and exposed this service to BTP using Cloud Connector. Then on BTP, this service was exposed to CAP framework which can be easily accessed by UI framework on BTP. In this blog I have used Angular framework. It is an example of Side-by-Side extension or clean Core approach on BTP.PrerequisitesS4 HANA Neatwever System 740+Cloud ConnectorBTP Subaccont XSUAA Services, Connectivity, Destination ServicesBAS or VS Code IDE , ABAP Development Tool1. OData Development using RAP FrameworkHere I will start with every step. I am using 2 Tables.a.  Create SAP DB tables    @EndUserText.label : ‘Vechile DB’
@AbapCatalog.enhancement.category : #EXTENSIBLE_ANY
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table yvechile_rt {
key client : syst_mandt not null;
key vechile_id : uuid not null;
vechile_variant_id : uuid;
@EndUserText.label : ‘Registration Plate Number’
vechile_plate_number : abap.char(16);
@EndUserText.label : ‘Number of Wheels’
vechile_wheel_count : abap.numc(2);
@EndUserText.label : ‘Manufacturar Compnay Name’
manufacturar : abap.char(32);
@EndUserText.label : ‘Date of Purchase’
date_of_purchase : abap.dats;
@EndUserText.label : ‘Registartion Date’
registration_date : abap.dats;
@EndUserText.label : ‘Owner Name’
owner_name : abap.char(32);
local_created_by : abp_creation_user;
local_created_at : abp_creation_tstmpl;
local_last_changed_by : abp_locinst_lastchange_user;
local_last_changed_at : abp_locinst_lastchange_tstmpl;
last_changed_at : abp_lastchange_tstmpl;

}@EndUserText.label : ‘Vechile Service Records’
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table yvechile_serv_rt {
key client : abap.clnt not null;
@AbapCatalog.foreignKey.screenCheck : true
key vechile_id : uuid not null
with foreign key [0..*,1] yvechile_rt
where vechile_id = yvechile_serv_rt.vechile_id;
key service_count : abap.numc(3) not null;
service_date : abap.dats;
distance_travel : abap.numc(10);
issue_reported : abap.string(256);
total_cost : abap.fltp;
local_created_by : abp_creation_user;
local_created_at : abp_creation_tstmpl;
local_last_changed_by : abp_locinst_lastchange_user;
local_last_changed_at : abp_locinst_lastchange_tstmpl;
last_changed_at : abp_lastchange_tstmpl;

}    b. Create BASIC CDS View in Table.    @AbapCatalog.sqlViewName: ‘YVECINRT’
@AbapCatalog.compiler.compareFilter: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@VDM.viewType: #BASIC
@EndUserText.label: ‘Vechile Informations Basic’
@ObjectModel.usageType: { serviceQuality: #C, sizeCategory:#L , dataClass:#TRANSACTIONAL }
@ClientHandling.algorithm: #SESSION_VARIABLE
define view Y_VECH_INFO_BASE_RT as select from yvechile_rt
{
key vechile_id as VechileId,
vechile_variant_id as VechileVariantId,
vechile_plate_number as VechilePlateNumber,
vechile_wheel_count as VechileWheelCount,
manufacturar as Manufacturar,
date_of_purchase as DateOfPurchase,
registration_date as RegistrationDate,
owner_name as OwnerName
}@AbapCatalog.sqlViewName: ‘YVECSERRT’
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: ‘Vechile Service Record Basic’
@ObjectModel.usageType: { serviceQuality: #C, sizeCategory:#L , dataClass:#TRANSACTIONAL }
@ClientHandling.algorithm: #SESSION_VARIABLE
define view Y_VECH_SERVICE_REC_RT as select from yvechile_serv_rt
{
key vechile_id as VechileId,
key service_count as ServiceCount,
service_date as ServiceDate,
distance_travel as DistanceTravel,
issue_reported as IssueReported,
total_cost as TotalCost
}    c. Create Composite CDS Views on Basic CDS.    @AbapCatalog.sqlViewName: ‘YVECHILE’
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: ‘Vechile Information’
@VDM.viewType: #COMPOSITE
@ObjectModel.usageType: { serviceQuality: #C, sizeCategory:#L , dataClass:#TRANSACTIONAL }
@ClientHandling.algorithm: #SESSION_VARIABLE
define view YI_VECH_INFO_RT as select from Y_VECH_INFO_BASE_RT as _vechInfo
association [0..*] to Y_VECH_SERVICE_REC_RT as _service_record on $projection.VechileId = _service_record.VechileId
{
key VechileId,
VechileVariantId,
VechilePlateNumber,
VechileWheelCount,
Manufacturar,
DateOfPurchase,
RegistrationDate,
OwnerName,
//Association
_service_record
}    d. Create Transactional CDS Views on Composite Views.    @AccessControl.authorizationCheck: #NOT_REQUIRED
@VDM.viewType: #TRANSACTIONAL
@VDM.usage.type: [#TRANSACTIONAL_PROCESSING_SERVICE]
@ObjectModel.usageType: { serviceQuality: #C, sizeCategory:#L , dataClass:#TRANSACTIONAL }
@EndUserText.label: ‘Vechile Info RAP View’

define root view entity YR_VECH_INFO_RT as select from YI_VECH_INFO_RT
composition [*] of YR_VECH_SERVICE_REC_RT as _service_record
{
key VechileId,
VechileVariantId,
VechilePlateNumber,
VechileWheelCount,
Manufacturar,
DateOfPurchase,
RegistrationDate,
OwnerName,
_VariantName.VariantName,
_VariantName.Colour,
_VariantName.gear_type,
/* Associations */
_service_record

}@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: ‘Vechile Variant RAP’
@VDM.viewType: #TRANSACTIONAL
@VDM.usage.type: [#TRANSACTIONAL_PROCESSING_SERVICE]
@ObjectModel.usageType: { serviceQuality: #C, sizeCategory:#L , dataClass:#TRANSACTIONAL }
define view entity YR_VECH_SERVICE_REC_RT as select from Y_VECH_SERVICE_REC_RT
association to parent YR_VECH_INFO_RT as _vechInfo on $projection.VechileId = _vechInfo.VechileId
{
key VechileId,
key ServiceCount,
ServiceDate,
DistanceTravel,
IssueReported,
TotalCost,
_vechInfo
}    e. Create Consumption Views on Transactional Views    @ObjectModel: { usageType: { dataClass: #TRANSACTIONAL,
sizeCategory: #L,
serviceQuality: #C } }
@VDM: { viewType: #CONSUMPTION,
usage: { type: [ #TRANSACTIONAL_PROCESSING_SERVICE ] },
lifecycle.contract.type: #PUBLIC_LOCAL_API}
@AccessControl.authorizationCheck: #NOT_REQUIRED
@Metadata: { allowExtensions: true }
@EndUserText.label: ‘Vechile Information Consumption View’
define root view entity YC_VECH_INFO_RT
provider contract transactional_query
as projection on YR_VECH_INFO_RT

{
.lineItem: [{ position: 10, label: ‘Vechile ID’ }]
@EndUserText:{ label: ‘Vechile ID’, quickInfo: ‘Vechile ID’}
key VechileId,
.lineItem: [{ position: 20, label: ‘Vechile Variant ID’ }]
@EndUserText:{ label: ‘Vechile Variant ID’, quickInfo: ‘Vechile Variant ID’}
@Consumption.valueHelpDefinition: [{ entity: { name: ‘Y_VECH_VARIANT_VH’, element: ‘VechileVariantId’}
}]
VechileVariantId,
.lineItem: [{ position: 30, label: ‘Plate Number’ }]
@EndUserText:{ label: ‘Plate Number’, quickInfo: ‘Plate Number’}
VechilePlateNumber,
.lineItem: [{ position: 40, label: ‘No. of Wheels’ }]
@EndUserText:{ label: ‘No. of Wheels’, quickInfo: ‘No. of Wheels’}
VechileWheelCount,
.lineItem: [{ position: 50, label: ‘Manufacturar’ }]
@EndUserText:{ label: ‘Manufacturar’, quickInfo: ‘Manufacturar’}
Manufacturar,

DateOfPurchase,

RegistrationDate,

OwnerName,

VariantName,

gear_type,

Colour,
/* Associations */
_service_record : redirected to composition child YC_VECH_SERVICE_REC_RT
}@ObjectModel: { usageType: { dataClass: #TRANSACTIONAL,
sizeCategory: #L,
serviceQuality: #C } }
@VDM: { viewType: #CONSUMPTION,
usage: { type: [ #TRANSACTIONAL_PROCESSING_SERVICE ] },
lifecycle.contract.type: #PUBLIC_LOCAL_API}
@AccessControl.authorizationCheck: #NOT_REQUIRED
@Metadata: { allowExtensions: true }
@EndUserText.label: ‘Vechile Service Record Consumption View’
define view entity YC_VECH_SERVICE_REC_RT as projection on YR_VECH_SERVICE_REC_RT
{
.lineItem: [{ position: 10, label: ‘Vechile ID’ }]
@EndUserText:{ label: ‘Vechile ID’, quickInfo: ‘Vechile ID’}
key VechileId,
.lineItem: [{ position: 20, label: ‘Service Count’ }]
@EndUserText:{ label: ‘Service Count’, quickInfo: ‘Service Count’}
key ServiceCount,
.lineItem: [{ position: 30, label: ‘Service Date’ }]
@EndUserText:{ label: ‘Service Date’, quickInfo: ‘Service Date’}
ServiceDate,
.lineItem: [{ position: 40, label: ‘Distance Travel’ }]
@EndUserText:{ label: ‘Distance Travel’, quickInfo: ‘Distance Travel’}
DistanceTravel,
IssueReported,
TotalCost,
/* Associations */
_vechInfo : redirected to parent YC_VECH_INFO_RT
}    f. Metadata Extension     @Metadata.layer: #CORE
@Search.searchable : true
@UI: {
headerInfo: { typeName: ‘VechileInformation’,
typeNamePlural: ‘Vechile Information’,
typeImageUrl: ‘sap-icon://Fiori5/F0830’,
title:
{ type: #STANDARD,
value:’VechileId’
}

}
}

@UI.presentationVariant: [
{
sortOrder: [
{by: ‘VechileId’, direction: #DESC },
{by: ‘VechileId’, direction: #DESC }
]
}
]
annotate view YC_VECH_INFO_RT
with
{

@UI.facet: [
{
id: ‘Vehicle’,
purpose: #STANDARD,
type: #IDENTIFICATION_REFERENCE,
label: ‘Vehicle’,
position: 10
},
{
id: ‘BasicData’,
purpose: #STANDARD,
type: #FIELDGROUP_REFERENCE,
label: ‘Vehicle’,
position: 10
},
{
label: ‘Service History’,
id: ‘VechSRecordChild1’,
type: #LINEITEM_REFERENCE,
targetElement: ‘_service_record’,
position: 20,
purpose: #STANDARD,
importance: #HIGH
}

]
@EndUserText.label: ‘Vechile ID’
: {
lineItem: [{ position: 10 ,hidden: false,importance: #HIGH } ],
fieldGroup: [{qualifier: ‘Header’, position: 10 , label: ‘Vechile ID’}],
textArrangement: #TEXT_FIRST,
selectionField: [ { position: 10 } ]
}
@Search: {
defaultSearchElement: true,
fuzzinessThreshold: 0.8,
ranking: #HIGH
}
VechileId;
@Search.defaultSearchElement: true
: {lineItem: [{ position: 20 ,hidden: false,importance: #HIGH }] ,
fieldGroup: [{qualifier: ‘BasicData’, position: 20 , label: ‘Vechile Variant ID’}],
identification: [{ position: 5 , label: ‘Vechile Variant ID’}],
selectionField: [ { position: 20 } ] }
@EndUserText: { label: ‘Vechile Variant ID’, quickInfo: ‘Vechile Variant ID’ }
VechileVariantId;
: {lineItem: [{ position: 25 ,hidden: false,importance: #HIGH }] ,
fieldGroup: [{qualifier: ‘BasicData’, position: 30 , label: ‘Variant Name’}] ,
selectionField: [ { position: 30 }]}
@EndUserText: { label: ‘Variant Name’ }
VariantName;
: {lineItem: [{ position: 30 ,hidden: false,importance: #HIGH }] ,
fieldGroup: [{qualifier: ‘BasicData’, position: 30 , label: ‘Vechile Plate Number’}] }
:{ identification: [{ position: 10 , label: ‘Vechile Plate Number’}] }
@EndUserText: { label: ‘Vechile Plate Number’, quickInfo: ‘Vechile Plate Number’ }
VechilePlateNumber;
: {lineItem: [{ position: 40 ,hidden: false,importance: #HIGH }] ,
fieldGroup: [{qualifier: ‘BasicData’, position: 40 , label: ‘Wheel Count’}] }
:{ identification: [{ position: 15 , label: ‘Wheel Count’}] }
@EndUserText: { label: ‘Wheel Count’, quickInfo: ‘Wheel Count’ }
VechileWheelCount;
: {lineItem: [{ position: 50 ,hidden: false,importance: #HIGH }] ,
fieldGroup: [{qualifier: ‘BasicData’, position: 50 , label: ‘Manufacturar’}] }
:{ identification: [{ position: 20 , label: ‘Manufacturar’}] }
@EndUserText: { label: ‘Manufacturar’, quickInfo: ‘Manufacturar’ }
Manufacturar;
: {lineItem: [{ position: 60 ,hidden: false,importance: #HIGH }] ,
fieldGroup: [{qualifier: ‘BasicData’, position: 60 , label: ‘Date Of Purchase’}] }
:{ identification: [{ position: 30 , label: ‘Date Of Purchase’}] }
@EndUserText: { label: ‘Date Of Purchase’, quickInfo: ‘Date Of Purchase’ }
DateOfPurchase;
: {lineItem: [{ position: 70 ,hidden: false,importance: #HIGH }] ,
fieldGroup: [{qualifier: ‘BasicData’, position: 70 , label: ‘Registration Date’}] }
:{ identification: [{ position: 40 , label: ‘Registration Date’}] }
@EndUserText: { label: ‘Registration Date’, quickInfo: ‘Registration Date’ }
RegistrationDate;
: {lineItem: [{ position: 80 ,hidden: false,importance: #HIGH }] ,
fieldGroup: [{qualifier: ‘BasicData’, position: 80 , label: ‘Owner Name’}] }
:{ identification: [{ position: 50 , label: ‘Owner Name’}] }
@EndUserText: { label: ‘Owner Name’, quickInfo: ‘Owner Name’ }
OwnerName;
}@Metadata.layer: #CORE
@UI: {
headerInfo: { typeName: ‘Vehicle Service History’,
typeNamePlural: ‘Vehicles Service History’,
title: { type: #STANDARD, label: ‘Vehicle Variants’, value: ‘VechileId’ } },
presentationVariant: [{ sortOrder: [{ by: ‘VechileId’, direction: #DESC }] }] }
annotate view YC_VECH_SERVICE_REC_RT
with
{
.facet: [ { id: ‘VehicleService’,
purpose: #STANDARD,
type: #IDENTIFICATION_REFERENCE,
label: ‘Vehicle Service Hostiry’,
position: 10 } ]
.hidden: true
VechileId;
@EndUserText.label: ‘Service Count’
.hidden: true
ServiceCount;
: { lineItem: [ { position: 10 } ],
identification: [ { position: 10 } ],
selectionField: [ { position: 10 } ]}

@EndUserText.label: ‘Service Date’
ServiceDate;
: { lineItem: [ { position: 20 } ],
identification: [ { position: 20 } ],
selectionField: [ { position: 20 } ]}

@EndUserText.label: ‘Distance Travel ‘
DistanceTravel;
: { lineItem: [ { position: 30 } ],
identification: [ { position: 30 } ],
selectionField: [ { position: 30 } ]}

@EndUserText.label: ‘Issue Reported ‘
IssueReported;
: { lineItem: [ { position: 40 } ],
identification: [ { position: 40 } ],
selectionField: [ { position: 40 } ]}

@EndUserText.label: ‘Total Cost’
TotalCost;

}    g. Create Behaviour Definition BDEF – YR_VECH_INFO_RT     managed implementation in class zcl_r_vech_info_rt unique;
strict ( 2 );
with draft;
extensible {
with determinations on modify;
with determinations on save;
with validations on save;
with additional save;
}

define behavior for YR_VECH_INFO_RT alias VechileInformation
persistent table yvechile_rt
draft table yvechile_rt_d
lock master
total etag VechileId
//authorization master ( instance )
authorization master ( instance )
etag master VechileId
extensible
{
draft action Edit;
draft action Activate optimized;
draft action Discard;
draft action Resume;
draft determine action Prepare extensible;
field ( numbering : managed , readonly ) VechileId;
create;
update;
delete;
association _service_record { create; }

determination VechileVariantId on save { field VechileVariantId ; }
validation Validate_variantId on save { field VechileVariantId ;}
// action ( features : instance ) CheckVariant ;
determine action Action_Vech extensible {
determination ( always ) VechileVariantId ;
validation ( always ) Validate_variantId;

}

//association _variant { create; }
mapping for yvechile_rt corresponding extensible {
VechileId = vechile_id;
VechileVariantId = vechile_variant_id ;
VechilePlateNumber = vechile_plate_number ;
VechileWheelCount = vechile_wheel_count ;
Manufacturar = manufacturar ;
DateOfPurchase = date_of_purchase ;
RegistrationDate = registration_date ;
OwnerName = owner_name ;
}
}

define behavior for YR_VECH_SERVICE_REC_RT alias VechileServiceRecord
persistent table yvechile_serv_rt
draft table yvechile_serv_d
lock dependent by _vechInfo
authorization dependent by _vechInfo
etag master VechileId
early numbering
{
field ( readonly ) ServiceCount;
update;
delete;
field ( readonly ) VechileId;
association _vechInfo;
mapping for yvechile_serv_rt{
VechileId = vechile_id ;
ServiceCount = service_count ;
ServiceDate = service_date ;
DistanceTravel = distance_travel ;
IssueReported = issue_reported ;
TotalCost = total_cost ;
}
}    BDEF – YC_VECH_INFO_RT    projection;
strict ( 2 );
extensible;

define behavior for YC_VECH_INFO_RT //alias <alias_name>
extensible
{
use create;
use update;
use delete;

use association _service_record { create; }
}

define behavior for YC_VECH_SERVICE_REC_RT //alias <alias_name>
{
use update;
use delete;

use association _vechInfo;
}    I. Behavioue Implementation Implement class – zcl_r_vech_info_rt in ADT.    CLASS zcl_r_vech_info_rt DEFINITION PUBLIC ABSTRACT FINAL FOR BEHAVIOR OF yr_vech_info_rt.
PUBLIC SECTION.

ENDCLASS.

CLASS zcl_r_vech_info_rt IMPLEMENTATION.
ENDCLASS.    Open Local Types and Copy the below code.    CLASS lhc_VechileInformation DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.

METHODS get_instance_authorizations FOR INSTANCE AUTHORIZATION
IMPORTING keys REQUEST requested_authorizations FOR VechileInformation RESULT result.
METHODS vechilevariantid FOR DETERMINE ON SAVE
IMPORTING keys FOR vechileinformation~vechilevariantid.
METHODS validate_variantid FOR VALIDATE ON SAVE
IMPORTING keys FOR vechileinformation~validate_variantid.

METHODS earlynumbering_cba_service_rec FOR NUMBERING
IMPORTING entities FOR CREATE vechileinformation_service_record.

ENDCLASS.

CLASS lhc_VechileInformation IMPLEMENTATION.

METHOD get_instance_authorizations.
ENDMETHOD.

METHOD earlynumbering_cba_Service_rec.
LOOP AT entities ASSIGNING FIELD-SYMBOL(<fs_vech_service>) .
SELECT MAX( service_count ) from yvechile_serv_rt into (lv_service_count) where vechile_id = @<fs_vech_service>-VechileId.
lv_service_count += 1.
LOOP AT <fs_vech_service>-%target ASSIGNING FIELD-SYMBOL(<fs_vech>) .
APPEND VALUE #( %cid = <fs_vech>-%cid
VechileId = <fs_vech>-VechileId
ServiceCount = lv_service_count
) TO mapped-vechileservicerecord .
ENDLOOP.
ENDLOOP.
ENDMETHOD.
ENDCLASS.    J. Service DefinitionsCreate UI_VECHILE_INFO_RT Service Definition and Copy below code.    @EndUserText.label: ‘Vechile Service Defination’
@AbapCatalog.extensibility: {extensible: true}
define service UI_VECHILE_INFO_RT {
expose YC_VECH_INFO_RT as VechileInformation;
expose YC_VECH_SERVICE_REC_RT as VechileServiceRecord;
expose YC_VECH_VARIANT_BASE_RT as VechileVariant;
}    K. Service Binding Create Service Binding – UI_VECHILE_INFO_RT using the above Service Definition.Save, Activate, and Publish the service binding.Select VechileInformation Node and Click the Preview Button to view the generated UI from Fiori Framework.Object page:Also exposed V4 Web Service using same Service Binding.2. Exposure of OData using Cloud Connector and Destination CreationFor this POC I am using my BTP Trail account. Copy the Subaccount ID of BTP Account and Add the Subaccount in Cloud Connector.Goto Cloud To On-Premise Section and Add System.Add Resources.Check Connection under Action, it should be green Reachable. Create Destination on BTP . 3. Exposure of S4 OData Services to BTP using CAP FrameworkLogin to BAS Instance and clone the below git project to your environment.https://github.com/Love2Explore/BTP_AdvanceHere all S4 OData entities have been exposed to CAP layer by using this code. It can be found here- BTP_Advance/srv/VechileCatalog.js const cds = require(“@sap/cds”);
const {vechile} = require(‘./ConnectionHandler’)
module.exports = cds.service.impl(async function(){
//const {VechileInformation,VechileVariant,SafetyCertificate} = this.entities;

this.on(‘READ’,[‘VechileInformation’,’VechileVariant’],async (req)=>{
const backend = await cds.connect.to(‘vechile’)
const tx = backend.tx(req);
return tx.run(req.query);
})
}) 4. Use CAP Services in Angular FrameworkHere I have used the Angular framework which can be found here – https://github.com/Love2Explore/BTP_Advance/tree/main/app/vechile-app/src/appCAP services are used in TS file, Eg: vechile/vechile.component.ts import { Component ,OnInit} from ‘@angular/core’;
import {HttpClient} from ‘@angular/common/http’;
import { CommonModule } from ‘@angular/common’;
@Component({
selector: ‘app-vechile’,
standalone: true,
imports: [CommonModule],
templateUrl: ‘./vechile.component.html’,
styleUrl: ‘./vechile.component.css’
})
export class VechileComponent implements OnInit{
Vechiles:any = []
message:string = ”
constructor(private _httpclient:HttpClient){
this._httpclient.get(“./odata/v4/vechile-catalog/VechileInformation”)
.subscribe(res=>{
this.Vechiles = res;
console.log(this.Vechiles)
})
}
ngOnInit(): void {
}
deletedTodo(t:any){
console.log(t)
}
} 5. Deployment of Application to BTP SubaccountBuild mtar file using mta.yaml.Login to BTP account from BAS using cf login.Deploy .mtar file on BTP account. #BTP #CAP #RAP #Angular #Destination @#Connectivity    Read More Technology Blogs by SAP articles 

#SAP

#SAPTechnologyblog

You May Also Like

More From Author

+ There are no comments

Add yours