Go to https://me.sap.com/app/sappassport, request your certificate and install it in your Browser. Working with the BTP is much more convenient now, as you don’t have to enter your credentials all the time.

Go to https://me.sap.com/app/sappassport, request your certificate and install it in your Browser. Working with the BTP is much more convenient now, as you don’t have to enter your credentials all the time.

In the documentation they always use a star in string literals like this o => o.`*` to select all fields, but when running this it always failed. However, after some tests I found that this format works with brackets o => o.('*')
https://cap.cloud.sap/docs/guides/providing-services/#–-deep-read
entity myEntity: cuid, managed {
field1 : String;
comp : Composition of one myComposition;
}
aspect myComposition: cuid, managed {
myCompField: String;
}
const result = await SELECT.from(myEntity)
.columns(e => {
e('*')
e.comp(c => c.myCompField) //expand composition, but only select 'myCompField'
})
Update 14.09.2023: is now fixed (#)
A Composition of one can be updated via the Parent Entity, like we would do it, when dealing with an Association to one.
entity myEntity: cuid, managed {
field1 : String;
comp : Composition of one myComposition;
}
aspect myComposition: cuid, managed {
myCompField: String;
}
const { myEntity } = srv.entities
const EntityComposition = srv.entities['myEntity.myComposition'] // composition
srv.after('CREATE', EntityComposition , async (comp, req) => {
await UPDATE(myEntity, comp.ID)
.set({
comp : [{ myCompField: 'test' }]
})
})
A Composition of many must be updated via the actual Composition Entity.
entity myEntity: cuid, managed {
field1 : String;
comp : Composition to many myComposition;
}
aspect myComposition: cuid, managed {
myCompField: String;
}
const EntityComposition = srv.entities['myEntity.myComposition'] // composition
srv.after('CREATE', EntityComposition , async (comp, req) => {
await UPDATE(EntityComposition, { up__ID: comp.up__ID, ID: comp.ID }).set({ myCompField: 'test' })
})
How to create an uploadSet in combination with a CAP backend: https://blogs.sap.com/2021/08/18/my-journey-towards-using-ui5-uploadset-with-cap-backend/
You can test the uploadSet CAP backend part using these http calls:
### Create file
# @name file
POST http://localhost:4004/v2/admin/Files
Authorization: Basic admin:
Content-Type: application/json
{
"mediaType": "image/png",
"fileName": "picture.png",
"size": 1000
}
### Fill Variable from Response
@ID = {{file.response.body.$.d.ID}}
### Upload Binary PNG content
PUT http://localhost:4004/v2/admin/Files({{ID}})/content
Authorization: Basic admin:
Content-Type: image/png
< ./picture.png
### Get uploaded png
GET http://localhost:4004/v2/admin/Files({{ID}})/content
Authorization: Basic admin:
The picture.png file must be in the same folder as the http test file.
"cds": {
"requires": {
"sfsf": {
"kind": "odata-v2",
"credentials": {
"destination": "<set during runtime>",
"path": "/odata/v2",
"requestTimeout": 18000000
}
}
}
},
/*
* Handover query to some external SF OData Service to fecth the requested data
*/
srv.on("READ", Whatever, async req => {
const sf_api_def = cds.env.requires['sfsf'] //defined in package.json
sf_api_def.credentials.destination = "myDestinationName" //set your Destination name, could come from a customizing table
const sfsfSrv = await cds.connect.to(sf_api_def)
return await sfsfSrv.run(req.query)
})
The provided example
destinations="[{\"name\": \"TESTINATION\", \"url\": \"http://url.hana.ondemand.com\", \"username\": \"DUMMY_USER\", \"password\": \"EXAMPLE_PASSWORD\"}]"
didn’t work for me. I had to change it to the following format:
destinations=[{"name": "TESTINATION", "url": "http://url.hana.ondemand.com", "username": "DUMMY_USER", "password": "EXAMPLE_PASSWORD"}]
You can check the current value using:
process.env.destinations
Related Reports:
https://nocin.eu/abap-download-transport-as-zip/
https://nocin.eu/abap-import-transport-from-zip/
*&---------------------------------------------------------------------*
*& Report Z_ZIP_TOC
*&---------------------------------------------------------------------*
*& For the givin transport, this report creates a transport of copies (ToC),
*& releases it, and then download the ToC as ZIP file to your filesystem.
*& The given request can be in sate unreleased!
*&---------------------------------------------------------------------*
REPORT Z_ZIP_TOC.
INITIALIZATION.
SELECTION-SCREEN BEGIN OF BLOCK bl01 WITH FRAME TITLE TEXT-t01.
PARAMETERS p_trkorr LIKE e070-trkorr OBLIGATORY.
PARAMETERS p_ttext TYPE as4text.
SELECTION-SCREEN END OF BLOCK bl01.
SELECTION-SCREEN BEGIN OF BLOCK bl02 WITH FRAME TITLE TEXT-t02.
PARAMETERS p_sapdir TYPE string LOWER CASE OBLIGATORY DEFAULT '/usr/sap/trans/'.
PARAMETERS p_lcldir TYPE string LOWER CASE OBLIGATORY DEFAULT 'C:\temp\'.
SELECTION-SCREEN END OF BLOCK bl02.
AT SELECTION-SCREEN ON VALUE-REQUEST FOR p_trkorr.
CALL FUNCTION 'TR_F4_REQUESTS'
EXPORTING
iv_trkorr_pattern = p_trkorr
IMPORTING
ev_selected_request = p_trkorr.
START-OF-SELECTION.
" Read description of provided transport
DATA ls_request TYPE trwbo_request.
CALL FUNCTION 'TR_READ_REQUEST'
EXPORTING
iv_read_e07t = 'X'
iv_trkorr = p_trkorr
CHANGING
cs_request = ls_request
EXCEPTIONS
error_occured = 1
no_authorization = 2
OTHERS = 3.
IF sy-subrc <> 0.
MESSAGE 'Could not read Transport description' TYPE 'E'.
ENDIF.
" Read all objects for provided transport
DATA: lt_objects TYPE tr_objects,
lt_keys TYPE tr_keys.
CALL FUNCTION 'TR_GET_OBJECTS_OF_REQ_AN_TASKS'
EXPORTING
is_request_header = VALUE trwbo_request_header( trkorr = p_trkorr )
iv_condense_objectlist = 'X'
IMPORTING
et_objects = lt_objects
et_keys = lt_keys
EXCEPTIONS
invalid_input = 1
OTHERS = 2.
IF sy-subrc <> 0.
MESSAGE 'Could not read Transport objects' TYPE 'E'.
ENDIF.
" Create new ToC
DATA ls_request_header TYPE trwbo_request_header.
CALL FUNCTION 'TR_INSERT_REQUEST_WITH_TASKS'
EXPORTING
iv_type = 'T'
iv_text = COND as4text( WHEN p_ttext IS INITIAL THEN ls_request-h-as4text
ELSE p_ttext )
iv_owner = sy-uname
iv_target = 'DUM'
IMPORTING
es_request_header = ls_request_header
EXCEPTIONS
insert_failed = 1
enqueue_failed = 2
OTHERS = 3.
IF sy-subrc <> 0.
MESSAGE 'Transport creation failed' TYPE 'E'.
ENDIF.
DATA(lv_trkorr_toc) = ls_request_header-trkorr.
" Add all object to the new ToC
CALL FUNCTION 'TRINT_APPEND_COMM'
EXPORTING
wi_exclusive = 'X'
wi_sel_e071 = 'X'
wi_sel_e071k = 'X'
wi_trkorr = lv_trkorr_toc
TABLES
wt_e071 = lt_objects
wt_e071k = lt_keys
EXCEPTIONS
e071k_append_error = 1
e071_append_error = 2
trkorr_empty = 3
OTHERS = 4.
IF sy-subrc <> 0.
MESSAGE 'Could not append objects to ToC' TYPE 'E'.
ENDIF.
" Release ToC Transport
DATA lt_messages TYPE ctsgerrmsgs.
CALL FUNCTION 'TRINT_RELEASE_REQUEST'
EXPORTING
iv_trkorr = lv_trkorr_toc
* IV_DIALOG = 'X'
* IV_AS_BACKGROUND_JOB = ' '
* IV_SUCCESS_MESSAGE = 'X'
* IV_WITHOUT_OBJECTS_CHECK = ' '
* IV_CALLED_BY_ADT = ' '
* IV_CALLED_BY_PERFORCE = ' '
* IV_WITHOUT_DOCU = ' '
iv_without_locking = 'X'
* IV_DISPLAY_EXPORT_LOG = 'X'
* IV_IGNORE_WARNINGS = ' '
* IV_SIMULATION = ' '
IMPORTING
et_messages = lt_messages
EXCEPTIONS
cts_initialization_failure = 1
enqueue_failed = 2
no_authorization = 3
invalid_request = 4
request_already_released = 5
repeat_too_early = 6
object_lock_error = 7
object_check_error = 8
docu_missing = 9
db_access_error = 10
action_aborted_by_user = 11
export_failed = 12
execute_objects_check = 13
release_in_bg_mode = 14
release_in_bg_mode_w_objchk = 15
error_in_export_methods = 16
object_lang_error = 17
OTHERS = 18.
IF sy-subrc <> 0.
MESSAGE 'Could not release ToC' TYPE 'E'.
cl_demo_output=>display( lt_messages ).
ENDIF.
" Download released ToC as ZIP
DATA lv_xcontent_k TYPE xstring.
DATA lv_xcontent_r TYPE xstring.
DATA(lv_transdir_k) = |{ p_sapdir }cofiles/K{ lv_trkorr_toc+4 }.{ lv_trkorr_toc(3) }|.
DATA(lv_transdir_r) = |{ p_sapdir }data/R{ lv_trkorr_toc+4 }.{ lv_trkorr_toc(3) }|.
TRY.
" K
OPEN DATASET lv_transdir_k FOR INPUT IN BINARY MODE.
READ DATASET lv_transdir_k INTO lv_xcontent_k.
CLOSE DATASET lv_transdir_k.
" R
OPEN DATASET lv_transdir_r FOR INPUT IN BINARY MODE.
READ DATASET lv_transdir_r INTO lv_xcontent_r.
CLOSE DATASET lv_transdir_r.
" Add to ZIP
DATA(lo_zipper) = NEW cl_abap_zip( ).
lo_zipper->add( name = |K{ lv_trkorr_toc+4 }.{ lv_trkorr_toc(3) }|
content = lv_xcontent_k ).
lo_zipper->add( name = |R{ lv_trkorr_toc+4 }.{ lv_trkorr_toc(3) }|
content = lv_xcontent_r ).
" Download ZIP
DATA(lv_xzip) = lo_zipper->save( ).
" Convert to raw data
DATA(lt_data) = cl_bcs_convert=>xstring_to_solix( iv_xstring = lv_xzip ).
" Set zip filename
DATA(lv_zip_name) = COND #( WHEN p_ttext IS INITIAL THEN |{ ls_request-h-as4text }_{ lv_trkorr_toc }|
ELSE |{ p_ttext }_{ lv_trkorr_toc }| ).
" Replace every character that is not [a-zA-Z0-9_] with '_'.
REPLACE ALL OCCURRENCES OF REGEX '[^\w]+' IN lv_zip_name WITH '_'.
cl_gui_frontend_services=>gui_download( EXPORTING filename = p_lcldir && lv_zip_name && '.zip'
filetype = 'BIN'
CHANGING data_tab = lt_data ).
CATCH cx_root INTO DATA(e_text).
MESSAGE e_text->get_text( ) TYPE 'E'.
ENDTRY.
MESSAGE |{ lv_zip_name }.zip created and downloaded to { p_lcldir }| TYPE 'S'.
While working on this topic, there are currently two chapters in the CAP docs regarding this topic:
And some SCN posts: here, here and here
But all this didn’t help me to complete the task. Got the solution which finally worked for me from this SAP Sample Susaas (two snippets: here and here).
server.js
const cds = require("@sap/cds")
cds.on('served', async () => {
const { 'cds.xt.SaasProvisioningService': provisioning } = cds.services
// Add provisioning logic only if multitenancy is there..
if (provisioning) {
let tenantProvisioning = require('./provisioning')
provisioning.prepend(tenantProvisioning)
} else {
console.log(">>> There is no service, therefore does not serve multitenancy!")
}
})
module.exports = cds.server
provisioning.js
const xsenv = require('@sap/xsenv')
xsenv.loadEnv()
module.exports = (service) => {
service.on('dependencies', async (req, next) => {
let dependencies = await next()
const services = xsenv.getServices({
registry: { tag: 'SaaS' },
destination: { tag: 'destination' }
})
dependencies.push({ xsappname: services.destination.xsappname }) //adds the subscriber destination as dependency
console.log(">>> SaaS Dependencies:", JSON.stringify(dependencies))
return dependencies
})
}
It injects the destination dependency by manually reading it using xsenv package and returning it in a dependencies callback handler.
Prerequisite, you have registered an SAP SuccessFactors system in your Global Account (see here). Creating the sap-successfactors-extensibility service can be done via command line:
#Created the service instance
#An HTTP destination on a subaccount level with the same name as the service instance name is automatically generated
cf create-service sap-successfactors-extensibility api-access myInstanceName -c '{"systemName": "SFCPART000000","technicalUser": "sfadmin"}'
#Bind the instance to an application
cf bind-service myApp-srv myInstanceName
Find an explanation of the parameters here: https://help.sap.com/docs/btp/sap-business-technology-platform/authentication-type-json-file
This service instance will result in creating:
The technicalUser parameter can be specified only during creation. There is no possibility to provide it afterwards using cf update-service. It may be possible to manually update the technicalUser in the destination, which got automatically created. But I did not test this yet.
Of course, the same service creation can also be done via mta.yaml.
resources:
#####################################################################################################################
# SuccessFactors Extensibility Service
#####################################################################################################################
- name: myInstanceName
type: org.cloudfoundry.managed-service
#type: org.cloudfoundry.existing-service
parameters:
service: sap-successfactors-extensibility
service-plan: api-access
config:
systemName: SFCPART000000 # <-- Provide your system name
technicalUser: sfadmin
For initial deployment, you need the line type: org.cloudfoundry.managed-service. For all further deployments, you have to comment that line out and comment in the next line type: org.cloudfoundry.existing-service. Else you will receive an error. Read more about that behavior here:https://github.com/SAP-samples/successfactors-extension-calculate-employee-seniority/issues/2
Related Reports:
https://nocin.eu/abap-import-transport-from-zip/
https://nocin.eu/abap-create-toc-for-a-given-transport-release-it-and-download-it-as-zip/
This report can be handy, especially since S/4HANA 2023 seems to have restricted the “classic” import way by using TCode CG3Y and CG3Z. See note 1949906, where it is recommended to create a custom report.
*&---------------------------------------------------------------------*
*& Report ZIP_TRANSPORT
*&---------------------------------------------------------------------*
*&
*&---------------------------------------------------------------------*
REPORT zip_transport.
SELECTION-SCREEN BEGIN OF BLOCK bl01 WITH FRAME TITLE TEXT-t01.
PARAMETERS p_trkorr LIKE e070-trkorr OBLIGATORY.
PARAMETERS p_ttext TYPE as4text.
SELECTION-SCREEN END OF BLOCK bl01.
SELECTION-SCREEN BEGIN OF BLOCK bl02 WITH FRAME TITLE TEXT-t02.
PARAMETERS p_sapdir TYPE string LOWER CASE OBLIGATORY DEFAULT '/usr/sap/trans/'.
PARAMETERS p_lcldir TYPE string LOWER CASE OBLIGATORY DEFAULT 'C:\temp\'.
SELECTION-SCREEN END OF BLOCK bl02.
START-OF-SELECTION.
" Check if Transport is released
DATA ls_request TYPE trwbo_request.
CALL FUNCTION 'TR_READ_REQUEST'
EXPORTING
iv_read_e070 = abap_true
iv_read_e07t = abap_true
iv_trkorr = p_trkorr
CHANGING
cs_request = ls_request
EXCEPTIONS
error_occured = 1
no_authorization = 2
OTHERS = 3.
IF ls_request-h-trstatus <> 'R'.
MESSAGE 'Transport not yet released' TYPE 'E'.
ENDIF.
" Read released Transport
DATA lv_xcontent_k TYPE xstring.
DATA lv_xcontent_r TYPE xstring.
DATA(lv_transdir_k) = |{ p_sapdir }cofiles/K{ p_trkorr+4 }.{ p_trkorr(3) }|.
DATA(lv_transdir_r) = |{ p_sapdir }data/R{ p_trkorr+4 }.{ p_trkorr(3) }|.
TRY.
" K
OPEN DATASET lv_transdir_k FOR INPUT IN BINARY MODE.
READ DATASET lv_transdir_k INTO lv_xcontent_k.
CLOSE DATASET lv_transdir_k.
" R
OPEN DATASET lv_transdir_r FOR INPUT IN BINARY MODE.
READ DATASET lv_transdir_r INTO lv_xcontent_r.
CLOSE DATASET lv_transdir_r.
" Add to ZIP
DATA(lo_zipper) = NEW cl_abap_zip( ).
lo_zipper->add( name = |K{ p_trkorr+4 }.{ p_trkorr(3) }|
content = lv_xcontent_k ).
lo_zipper->add( name = |R{ p_trkorr+4 }.{ p_trkorr(3) }|
content = lv_xcontent_r ).
" Download ZIP
DATA(lv_xzip) = lo_zipper->save( ).
" Convert to raw data
DATA(lt_data) = cl_bcs_convert=>xstring_to_solix( iv_xstring = lv_xzip ).
" Set zip filename
DATA(lv_zip_name) = COND #( WHEN p_ttext IS INITIAL THEN |{ ls_request-h-as4text }_{ p_trkorr }|
ELSE |{ p_ttext }_{ p_trkorr }| ).
" Replace every character that is not [a-zA-Z0-9_] with '_'.
REPLACE ALL OCCURRENCES OF REGEX '[^\w]+' IN lv_zip_name WITH '_'.
cl_gui_frontend_services=>gui_download( EXPORTING filename = p_lcldir && lv_zip_name && '.zip'
filetype = 'BIN'
CHANGING data_tab = lt_data ).
CATCH cx_root INTO DATA(e_text).
MESSAGE e_text->get_text( ) TYPE 'E'.
ENDTRY.
MESSAGE |{ lv_zip_name }.zip created and downloaded to { p_lcldir }| TYPE 'S'.