Appearance
Lesson 23 — Final Project: S/4HANA Side-by-Side Stock Check Extension
Guided learning: 3 hours
Independent project: 3 hours (see separate Practice)
Environment: SAP BTP ABAP Trial + Eclipse ADT
Prerequisites: An SAP BTP ABAP Trial project in Eclipse ADT, permission to create repository objects, and basic ABAP/RAP familiarity. No previously created Service Consumption Model or console class is needed. This guide walks you through creating both. The remote portion additionally requires a BTP Trial tenant where the shared SAP S/4HANA demo communication scenario ZBTP_TRIAL_SAP_COM_0309 has been provisioned.
This is the final hands-on project. You will build an application that records stock checks locally in BTP and lets users select actual S/4HANA product IDs. You will not change master data in S/4HANA: the connection to S/4HANA is read-only.
1. What is the business application?
Warehouse staff need to record occasional spot checks. Each check has a product, warehouse, counted quantity, status and notes. The check records are our application's data; the product IDs come from S/4HANA.
text
Fiori Elements Stock Check app (on SAP BTP)
| |
| Save local checks | Product value help
v v
Managed RAP BO CDS Custom Entity
| |
v v
Your own BTP table IF_RAP_QUERY_PROVIDER class
|
v
OData V2 Client Proxy
|
v
S/4HANA Product APIWhy is this an extension? Your app adds a new stock-check process in BTP, without modifying the S/4HANA product master. SAP's published API supplies the product choices. Users can create and update their own stock-check entries locally.
Definition of done
| Feature | Demonstration |
|---|---|
| Local stock checks | Create and save a stock-check record in Fiori Elements |
| Product ID field | Stores a product ID with 40-character capacity |
| Remote value help | Select a product returned by S/4HANA, not a hard-coded list |
| Search results | Show Product, Product Type and Product Group |
| Safe data flow | Changes update only the BTP stock-check table |
| Diagnostics | Distinguish broken remote connection from a local RAP save issue |
What this project will not do: update actual inventory quantities in S/4HANA. CountedQty is an observation stored in BTP, not an official S/4HANA stock posting.
2. Initial checks and naming rules
Name your own objects using your initials
Every ## in this handout is a placeholder for your TWO uppercase initials. For example, Alex Brown uses AB: ZPKG_L23_## becomes ZPKG_L23_AB and ZSC_L23_PROD_## becomes ZSC_L23_PROD_AB. Replace ## consistently in all ABAP and CDS source code, object names, @ObjectModel.query.implementedBy, and string-based proxy model keys before activation. Do not include literal # characters in an object name.
If two people share the same initials, coordinate a unique permitted two-character suffix with your instructor. Names shown below are student objects; the SAP-defined API name and shared BTP communication scenario must stay unchanged.
| Object to create | Name with student placeholder |
|---|---|
| Development package | ZPKG_L23_## |
| Local stock-check table | ZL23_STOCK_## |
| OData Service Consumption Model and generated class | ZSC_L23_PROD_## |
| Connection test class | ZCL_L23_API_TEST_## |
| Query-provider class | ZCL_L23_PRODUCTS_## |
| Custom CDS entity | ZCE_L23_PROD_## |
| Query exception class | ZCX_L23_QUERY_## |
What is external and cannot be created by this exercise?
The shared backend communication scenario ZBTP_TRIAL_SAP_COM_0309 is SAP/tenant-provided configuration. It was observed working in one ABAP Trial setup, but not every tenant is guaranteed to have it. You will create your own SCM from the EDMX, but you cannot invent the remote endpoint or credentials if the communication arrangement is absent.
You will first build and save stock checks locally. If the remote scenario is unavailable, you can still complete that milestone and inspect the rest of the design, but you must mark remote product value help as not verified.
3. Create a new package for your final project
- In Eclipse, open Window → Perspective → Open Perspective → Other… → ABAP, if necessary.
- In Project Explorer, expand your SAP BTP ABAP Trial project.
- Choose a writable package area within your allowed software component (often
ZLOCAL). - Right-click the package area → New → ABAP Package. If the menu differs, use New → Other ABAP Repository Object… → ABAP Package.
- Name your package
ZPKG_L23_##, descriptionSide by side stock check project. - Choose the transport/package settings offered by your Trial system and click Finish.
Replace every ## placeholder with your two uppercase initials in Eclipse before creating or activating anything. Do not change the shared SAP scenario name.
4. Create the local database table
We create only one new persistence table. The RAP generator will create the transactional BO and Fiori service artifacts, saving us a large amount of repetitive setup.
Step 4.1 — Create the table repository object
- Right-click
ZPKG_L23_##. - Select New → Other ABAP Repository Object….
- Search for Database Table, select it, and click Next.
- Name:
ZL23_STOCK_##; Description:Local warehouse stock checks. - Complete the wizard and replace the generated table definition with the full source below.
Step 4.2 — Paste the complete table definition
abap
@EndUserText.label : 'Warehouse Stock Check'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table zl23_stock_## {
key client : abap.clnt not null;
key check_uuid : sysuuid_x16 not null;
product_id : abap.char(40);
warehouse : abap.char(10);
counted_qty : abap.int4;
check_status : abap.char(1);
notes : abap.char(120);
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;
}Press Ctrl+S, then Ctrl+F3 to activate. Before activation, replace the table name suffix with your initials.
CheckUuid is the technical RAP key. Product ID is a business field, not the primary key. The administrative fields support the generated RAP behavior, draft and concurrency handling. We will not write low-level SQL modifications to this table.
5. Generate a complete managed RAP/Fiori starter application
This is the fastest supported way to build the familiar RAP layers. Instead of copying a BDEF that may not match the generated artifacts, use the actual ADT generator.
Step 5.1 — Start the generator
- In Project Explorer, right-click database table
ZL23_STOCK_##(not the package). - Select Generate ABAP Repository Objects….
- Choose OData UI Service in the Cloud generator (or the longer ABAP RESTful Application Programming Model: UI Service label shown by some ADT versions).
- Click Next and verify package
ZPKG_L23_##. - On Configure Generator, leave Artifacts Prefix and Artifacts Suffix blank if you are entering complete names. Expand Business Object → Data Model, Business Object → Behavior, Service Projection, and Business Service, then enter the names below. Some ADT versions organize the tree slightly differently.
Step 5.2 — Enter the generator names
| Generator field | Use this example name |
|---|---|
| Root data definition | ZI_L23_STOCK_## |
| Entity alias | StockCheck |
| Behavior implementation class | ZBP_I_L23_STOCK_## |
| Draft table | ZL23_STKD_## |
| CDS projection | ZC_L23_STOCK_## |
| Projection behavior implementation class (if offered) | ZBP_C_L23_STOCK_## |
| Service definition | ZUI_L23_STOCK_## |
| Service binding | ZUI_L23_STOCK_O4_## |
| Service binding type | OData V4 – UI |
The generator may propose slightly different defaults or require names in a different order. Check every generated name on the review page before clicking Finish. SAP's current generator produces a draft-enabled managed RAP stack; some releases expose fewer configurable fields. Record the actual names and do not mix them with these examples. If ADT reports a conflicting or invalid object name, choose a valid unique name and carry it consistently through later steps.
Step 5.3 — Generate and activate
- Click Next to review objects.
- Choose a transport if required, then click Finish.
- Expand your package; you should now see a root data model, base behavior, behavior implementation, projection, service definition and service binding, plus a draft table.
- Activate any inactive objects; address generator errors before proceeding.
- Open the Service Binding
ZUI_L23_STOCK_O4_##(or the name actually generated). - Click Publish if the editor shows the service as unpublished.
- Select the
StockCheckentity and choose Preview to open the generated Fiori Elements application.
Step 5.4 — Verify local RAP without S/4HANA
In the Fiori preview:
- Click Create.
- Fill
ProductId = A001,Warehouse = WH01,CountedQty = 8,CheckStatus = N, andNotes = First guided check(use the field labels your generator exposes). - Save/activate the draft using the UI's Create/Save action as applicable.
- Return to the list. Confirm you can see the check you just created.
At this stage A001 is manually typed. We have verified local RAP persistence, not yet remote product selection. If the generator labels the field differently, inspect the generated CDS projection: we will need the ProductId field name next.
Checkpoint: Your local stock-check app works before you introduce the external connection.
6. Create the S/4HANA connection and SCM within this project
You now have a working local Stock Check app. The next steps are completely self-contained: you will download the Product OData metadata, create a new SCM in your package, inspect the generated ABAP class, and run a test console. There is no need to open or reuse objects from any previous lesson.
Step 6.1 — Confirm the tenant-provided communication arrangement exists
The shared training scenario is ZBTP_TRIAL_SAP_COM_0309. It is used by the provided SAP BTP ABAP Trial configuration for a read-only S/4HANA Product API. This scenario is not a student-created naming placeholder.
You normally cannot browse a complete communication-arrangement setup from an ordinary ABAP class editor. The easiest check in this environment is to run the API test class in Step 6.5. If the destination lookup fails, your BTP Trial tenant may not provide this arrangement; ask for an approved connection rather than making up a URL or password.
Step 6.2 — Download the Product API EDMX from SAP
- Open your browser and go to SAP Business Accelerator Hub — Product Master Data Including Classification (Read).
- On that API page, find API Specification. The layout and login prompts may vary.
- Download the OData EDMX specification and save it to your computer as
API_CLFN_PRODUCT_SRV.edmx(or keep the downloaded.edmxfilename). - Verify that the saved file ends in
.edmxand contains XML metadata. It must not be a JSON payload, PDF, or shortcut to the Hub page.
This EDMX describes the API's entities and fields. It contains no live product records. Actual records arrive only when your ABAP class sends a request.
Step 6.3 — Create a new Service Consumption Model in Eclipse ADT
- In Eclipse Project Explorer, right-click your
ZPKG_L23_##package. - Choose New → Other ABAP Repository Object….
- Under Business Services, select Service Consumption Model, then Next.
- Set Name to
ZSC_L23_PROD_##(after replacing##) and Description toProduct API for Stock Check. - Choose Remote Consumption Mode = OData, then Next.
- At Service Metadata File, click Browse… and select your downloaded
API_CLFN_PRODUCT_SRV.edmx. - If the wizard asks for a generated class name, use
ZSC_L23_PROD_##where allowed. Record the actual generated class name from your wizard, even if it differs. - Proceed to Components of OData Service, locate the Product entity set
A_ClfnProductand ensure it is included. Keep the appropriate generated Product entity/type components selected. - If the wizard shows an ETag Support page, keep the defaults. This is a read-only exercise.
- Review the object-generation summary and select a transport request if prompted. Click Finish.
- Refresh the package using F5. Open the SCM and activate any inactive generated objects using Ctrl+F3 (or Ctrl+Shift+F3 when activating related inactive objects together).
ADT version note: Newer Service Consumption Model wizards may display the generated class name and component selection on slightly different pages. Always use the values shown in your actual model editor.
Step 6.4 — Record your SCM's Model Passport
- Double-click your Service Consumption Model.
- Select the
A_ClfnProductentity set and its Read List example. - Copy the generated sample to a temporary text file, and find
create_v2_remote_proxy,is_proxy_model_key, the product entity-set ID, and the generated row type. - Open the generated ABAP class in Project Explorer; locate its public TYPES declarations.
For the Product EDMX previously tested in BTP Trial, these were the values:
| Model property | Example (check your own SCM) |
|---|---|
repository_id | DEFAULT |
proxy_model_id | ZSC_L23_PROD_## |
proxy_model_version | 0001 |
create_resource_for_entity_set | A_CLFN_PRODUCT |
| Generated ABAP row type | ZSC_L23_PROD_##=>TYS_A_CLFN_PRODUCT_TYPE |
| Useful ABAP fields | product, product_type, product_group, base_unit |
Replace example class, type, model ID, version and entity-set ID with the actual generated identifiers if yours differ. The remote EDMX entity name A_ClfnProduct and ABAP proxy identifier A_CLFN_PRODUCT are related but are not spelled identically.
Step 6.5 — Create and run the complete connection test class
- Right-click your package → New → ABAP Class (or New → Other ABAP Repository Object… → ABAP Class).
- Enter name
ZCL_L23_API_TEST_##and descriptionTest S4HANA Product API. - Click Finish and replace the entire global class source with the code below.
- Perform Replace All for
##using your two initials.
abap
CLASS zcl_l23_api_test_## DEFINITION
PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
ENDCLASS.
CLASS zcl_l23_api_test_## IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
DATA lt_products TYPE STANDARD TABLE OF
zsc_l23_prod_##=>tys_a_clfn_product_type
WITH EMPTY KEY.
TRY.
DATA(lo_destination) =
cl_http_destination_provider=>create_by_comm_arrangement(
comm_scenario = 'ZBTP_TRIAL_SAP_COM_0309' ).
DATA(lo_http_client) =
cl_web_http_client_manager=>create_by_http_destination(
i_destination = lo_destination ).
DATA(lo_proxy) =
/iwbep/cl_cp_factory_remote=>create_v2_remote_proxy(
is_proxy_model_key = VALUE #(
repository_id = 'DEFAULT'
proxy_model_id = 'ZSC_L23_PROD_##'
proxy_model_version = '0001' )
io_http_client = lo_http_client
iv_relative_service_root = '' ).
"The communication-arrangement destination already points to
"the Product service root: no second service path is needed.
DATA(lo_read) =
lo_proxy->create_resource_for_entity_set(
'A_CLFN_PRODUCT' )->create_request_for_read( ).
lo_read->set_top( 5 ).
DATA(lo_response) = lo_read->execute( ).
lo_response->get_business_data(
IMPORTING et_business_data = lt_products ).
out->write( |Remote products: { lines( lt_products ) }| ).
LOOP AT lt_products INTO DATA(ls_product).
out->write(
|{ ls_product-product } / | &&
|{ ls_product-product_type } / | &&
|{ ls_product-product_group }| ).
ENDLOOP.
CATCH cx_root INTO DATA(lx_error).
out->write( |Remote API error: { lx_error->get_text( ) }| ).
ENDTRY.
ENDMETHOD.
ENDCLASS.- Save with Ctrl+S and activate with Ctrl+F3.
- With the class editor active, press F9 or choose Run As → ABAP Application (Console).
- Inspect the ABAP Console. A working shared Trial test previously returned products such as
A001andB001, but your tenant may have different data and order.
Checkpoint: A nonempty list of product IDs proves your own SCM and tenant's remote connectivity work. If you receive an error, read it before changing code: a missing communication arrangement, an incorrect model version, and an incorrect service root are different problems.
Stop point for remote prerequisites: If this step fails because
ZBTP_TRIAL_SAP_COM_0309is absent, the remaining Fiori integration cannot be verified in that tenant. Continue to demonstrate the already-working local Stock Check app, and report the remote result as not executed / blocked, rather than hard-coding authentication or pretending manual data came from S/4HANA.
7. Create a CDS Custom Entity for remote products
A CDS custom entity does not directly select from a database table. Instead it delegates requests to an ABAP class that implements IF_RAP_QUERY_PROVIDER. This is a supported way to expose data provided by another OData API. https://help.sap.com/docs/abap-cloud/abap-rap/using-cds-custom-entity-for-data-modeling
Step 7.1 — Create the exception and query classes
- Create the exception class
ZCX_L23_QUERY_##using the complete source in Section 8.1, then activate it. - Right-click package
ZPKG_L23_##→ New → ABAP Class. - Name
ZCL_L23_PRODUCTS_##; descriptionS4 remote products query provider. - Paste the complete class in Section 8.2 and replace
##with your initials. - Activate the query class after checking your Model Passport from Section 6.
Step 7.2 — Create a Data Definition
- Right-click the package → New → Other ABAP Repository Object….
- Search for Data Definition under Core Data Services.
- Enter name
ZCE_L23_PROD_##and descriptionProducts from S4HANA. - Click Next through the transport screen to select the template.
- Choose Define Custom Entity with Parameters, then Finish. If your ADT offers a parameterless custom-entity template, that works too.
- Replace the entire generated source, including removing the
with parameters ...placeholder.
Step 7.3 — Paste the complete custom entity
abap
@EndUserText.label: 'Remote S4HANA Products'
@ObjectModel.query.implementedBy: 'ABAP:ZCL_L23_PRODUCTS_##'
define custom entity ZCE_L23_PROD_##
{
@EndUserText.label: 'Product'
@UI.lineItem: [{ position: 10 }]
key Product : abap.char(40);
@EndUserText.label: 'Product Type'
@UI.lineItem: [{ position: 20 }]
ProductTypeName : abap.char(4);
@EndUserText.label: 'Product Group'
@UI.lineItem: [{ position: 30 }]
ProductGroupName : abap.char(9);
}The query class name in @ObjectModel.query.implementedBy must match your ABAP class. The Product field is CHAR(40) to match the key length published by the remote service. We deliberately use ProductTypeName and ProductGroupName to distinguish CDS field names from the remote OData property names.
Activation order: The query class implements the required interface and supplies fields with the same names/types as this entity. Create and activate the exception class in Section 8.1, then activate the query class and the CDS custom entity. This avoids a circular dependency. A temporary inactive reference while the class is not yet implemented is normal.
8. Complete query-provider ABAP class
This class receives a Fiori query, delegates it to the OData client proxy you created in Section 6, and returns matching data to Fiori. It includes count, paging, and sorting of supported properties, as well as filters for the displayed fields. These are important: a value-help dialog is not just a single GET request.
Step 8.1 — Required exception class
To ensure remote failures are reported as failures rather than silently shown as an empty list, first create a dedicated exception type. This uses the standard ABAP Class wizard, setting a suitable exception superclass.
- Right-click package → New → ABAP Class.
- Enter name
ZCX_L23_QUERY_##and descriptionRAP Product Query Error. Click Next. - Where ADT requests Superclass, enter
CX_RAP_QUERY_PROVIDER; this is what makes the new global class a RAP query-provider exception subclass. Complete the transport wizard. - Replace its source with:
abap
CLASS zcx_l23_query_## DEFINITION
PUBLIC
INHERITING FROM cx_rap_query_provider
FINAL
CREATE PUBLIC.
ENDCLASS.
CLASS zcx_l23_query_## IMPLEMENTATION.
ENDCLASS.Activate it. This lets the query provider report unexpected API failures to the RAP runtime instead of pretending that no product exists.
Step 8.2 — Replace the full source of ZCL_L23_PRODUCTS_##
abap
CLASS zcl_l23_products_## DEFINITION
PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
" RAP calls this interface when the custom entity is queried.
" The connection to this class is declared in the custom entity's
" @ObjectModel.query.implementedBy annotation.
INTERFACES if_rap_query_provider.
ENDCLASS.
CLASS zcl_l23_products_## IMPLEMENTATION.
METHOD if_rap_query_provider~select.
" io_request describes what Fiori asks our local service to return.
" io_response is where we give RAP the data and count for Fiori.
" lo_read and lo_response below belong to the REMOTE API call.
" This method reads products; it does not save Stock Check records.
" 1. Prepare two data shapes: remote API rows and local CDS rows.
" The SCM generated this ABAP row type from the Product EDMX.
" A table of this type holds products returned by the client proxy.
TYPES ty_remote_rows TYPE STANDARD TABLE OF
zsc_l23_prod_##=>tys_a_clfn_product_type
WITH EMPTY KEY.
" Our custom entity exposes only these three fields. Their names
" and lengths must match ZCE_L23_PROD_##, not the remote row type.
TYPES: BEGIN OF ty_result,
Product TYPE c LENGTH 40,
ProductTypeName TYPE c LENGTH 4,
ProductGroupName TYPE c LENGTH 9,
END OF ty_result.
TYPES ty_result_rows TYPE STANDARD TABLE OF ty_result
WITH EMPTY KEY.
" lt_remote receives API data; lt_result holds the mapped CDS data.
DATA lt_remote TYPE ty_remote_rows.
DATA lt_result TYPE ty_result_rows.
TRY.
" 2. Inspect the query options supplied by Fiori.
" Free-text search ($search) differs from field filters.
" This example supports field filters, but rejects free-text
" search so it cannot accidentally return unfiltered results.
DATA(lv_search) = io_request->get_search_expression( ).
IF lv_search IS NOT INITIAL.
RAISE EXCEPTION TYPE zcx_l23_query_##.
ENDIF.
" Requested elements are the columns Fiori wants ($select).
" We read them for inspection, but do not use lt_requested to
" restrict the result: this example returns all three fields.
DATA(lt_requested) = io_request->get_requested_elements( ).
" Sorting specifies which fields to sort by and each direction.
DATA(lt_sorts) = io_request->get_sort_elements( ).
" 3. Prepare access to the remote Product API.
" The communication arrangement supplies the destination and
" authentication settings; we do not embed credentials here.
DATA(lo_destination) =
cl_http_destination_provider=>create_by_comm_arrangement(
comm_scenario = 'ZBTP_TRIAL_SAP_COM_0309' ).
" The HTTP client will carry requests to that destination.
DATA(lo_http_client) =
cl_web_http_client_manager=>create_by_http_destination(
i_destination = lo_destination ).
" The OData V2 proxy uses our generated SCM to translate ABAP
" requests and response data to/from the remote OData format.
" VALUE #( ... ) builds the model-key structure. Its fields
" identify the repository, model and model version from the SCM.
" '0001' is the model version, not the OData protocol version.
" For this Trial destination the service root is already set,
" so iv_relative_service_root adds no extra path.
DATA(lo_proxy) =
/iwbep/cl_cp_factory_remote=>create_v2_remote_proxy(
is_proxy_model_key = VALUE #(
repository_id = 'DEFAULT'
proxy_model_id = 'ZSC_L23_PROD_##'
proxy_model_version = '0001' )
io_http_client = lo_http_client
iv_relative_service_root = '' ).
" Select the Product collection using its generated proxy ID
" and prepare a read-list request. We configure it below;
" execute( ) later performs the remote product read.
DATA(lo_read) =
lo_proxy->create_resource_for_entity_set(
'A_CLFN_PRODUCT' )->create_request_for_read( ).
" 4. Translate local field filters into remote filter nodes.
" Each entry has a field name and a range table containing
" SIGN, OPTION, LOW and HIGH (for example I / EQ / SERV).
" I means include; EQ means equal; LOW holds the value SERV.
" HIGH holds the upper limit when the condition is an interval.
" Filters that cannot be expressed as ranges raise an exception,
" handled at the end of this method.
DATA(lt_ranges) = io_request->get_filter( )->get_as_ranges( ).
" The factory creates filter objects understood by the proxy.
DATA(lo_factory) = lo_read->create_filter_factory( ).
" lo_root will hold the combined filter. Initially it is unbound.
DATA lo_root TYPE REF TO /iwbep/if_cp_filter_node.
LOOP AT lt_ranges INTO DATA(ls_filter).
" RAP supplies uppercase local field names. SWITCH maps them
" to ABAP proxy property paths, not remote OData URL names.
" For example: ProductTypeName -> PRODUCT_TYPE -> ProductType.
DATA(lv_property) = SWITCH string( ls_filter-name
WHEN 'PRODUCT' THEN 'PRODUCT'
WHEN 'PRODUCTTYPENAME' THEN 'PRODUCT_TYPE'
WHEN 'PRODUCTGROUPNAME' THEN 'PRODUCT_GROUP'
ELSE '' ).
" An unknown field must fail rather than have its filter ignored.
IF lv_property IS INITIAL.
RAISE EXCEPTION TYPE zcx_l23_query_##.
ENDIF.
" Turn all range conditions for this field into one filter node.
DATA(lo_node) = lo_factory->create_by_range(
iv_property_path = lv_property
it_range = ls_filter-range ).
" The first field starts the filter; each later field is ANDed.
" Example: Product = 'A001' AND ProductType = 'SERV'.
IF lo_root IS NOT BOUND.
lo_root = lo_node.
ELSE.
lo_root = lo_root->and( lo_node ).
ENDIF.
ENDLOOP.
" Attach the filter only if one was built. With no field filters,
" the request reads the collection using the paging below.
IF lo_root IS BOUND.
lo_read->set_filter( lo_root ).
ENDIF.
" 5. Translate sorting and ensure a deterministic row order.
" The proxy expects its own sort-table type and property paths.
DATA lt_order TYPE /iwbep/if_cp_runtime_types=>ty_t_sort_order.
" Track whether Fiori already asked to sort by the unique key.
DATA lv_key_sorted TYPE abap_bool VALUE abap_false.
LOOP AT lt_sorts INTO DATA(ls_sort).
" Map the sort field using the same names as the filter mapping.
DATA(lv_sort_property) = SWITCH string( ls_sort-element_name
WHEN 'PRODUCT' THEN 'PRODUCT'
WHEN 'PRODUCTTYPENAME' THEN 'PRODUCT_TYPE'
WHEN 'PRODUCTGROUPNAME' THEN 'PRODUCT_GROUP'
ELSE '' ).
IF lv_sort_property IS INITIAL.
RAISE EXCEPTION TYPE zcx_l23_query_##.
ENDIF.
IF lv_sort_property = 'PRODUCT'.
lv_key_sorted = abap_true.
ENDIF.
" Preserve Fiori's sort priority and direction for each field.
APPEND VALUE #(
property_path = lv_sort_property
descending = ls_sort-descending ) TO lt_order.
ENDLOOP.
" Products can share a type/group. Sorting by Product last breaks
" those ties, giving stable pages when the underlying data is unchanged.
" With no requested sort, Product ascending becomes the default.
IF lv_key_sorted = abap_false.
APPEND VALUE #(
property_path = 'PRODUCT'
descending = abap_false ) TO lt_order.
ENDIF.
" A count-only request does not need sorted rows.
IF io_request->is_data_requested( ).
lo_read->set_orderby( lt_order ).
ENDIF.
" 6. Request the page and, separately, the total matching count.
" Fiori may request rows, a count, or both. Check each flag.
" Let S/4HANA apply paging instead of fetching all rows into ABAP.
IF io_request->is_data_requested( ).
DATA(lo_paging) = io_request->get_paging( ).
DATA(lv_page_size) = lo_paging->get_page_size( ).
IF lv_page_size = if_rap_query_paging=>page_size_unlimited.
" No finite page size was supplied. This teaching service
" rejects that request rather than fetching the whole catalog.
RAISE EXCEPTION TYPE zcx_l23_query_##.
ELSEIF lv_page_size > 0.
" top = maximum rows to return; skip = rows to pass over.
" Example: top 20 / skip 20 requests the second page.
" CONV i converts RAP paging values to the proxy's integer type.
lo_read->set_top( CONV i( lv_page_size ) ).
lo_read->set_skip(
CONV i( lo_paging->get_offset( ) ) ).
ELSE.
" A zero-size page asks for no product rows.
lo_read->request_no_business_data( ).
ENDIF.
ELSE.
" For a count-only query, suppress the remote product payload.
lo_read->request_no_business_data( ).
ENDIF.
" The count covers ALL matching products, before paging.
" A page might contain 20 rows while the matching count is 137.
IF io_request->is_total_numb_of_rec_requested( ).
lo_read->request_count( ).
ENDIF.
" 7. Execute the configured remote request and receive its response.
" Connection or API failures go to the exception handlers below.
DATA(lo_response) = lo_read->execute( ).
" 8. Transfer the remote result to RAP's local response.
" Return the remote total, not lines( lt_remote ), which would
" describe only the current page and mislead Fiori's paging.
IF io_request->is_total_numb_of_rec_requested( ).
io_response->set_total_number_of_records(
lo_response->get_count( ) ).
ENDIF.
IF io_request->is_data_requested( ).
" Copy the response's business rows into the SCM-typed table.
lo_response->get_business_data(
IMPORTING et_business_data = lt_remote ).
" Map each remote row to the three fields of our custom entity.
" ProductTypeName and ProductGroupName contain codes here,
" not descriptive texts, despite their local field names.
LOOP AT lt_remote INTO DATA(ls_remote).
APPEND VALUE #(
Product = ls_remote-product
ProductTypeName = ls_remote-product_type
ProductGroupName = ls_remote-product_group ) TO lt_result.
ENDLOOP.
" Give RAP the mapped rows to return to the Fiori value help.
" An empty table is valid when the query finds no matches.
io_response->set_data( lt_result ).
ENDIF.
" 9. Preserve failures as failures instead of returning an empty list.
CATCH zcx_l23_query_## INTO DATA(lx_query).
" Our explicit query exceptions already have the required type.
RAISE EXCEPTION lx_query.
CATCH cx_root INTO DATA(lx_error).
" Wrap other exceptions in the type accepted by the RAP query
" interface. previous retains the original cause for diagnosis.
RAISE EXCEPTION TYPE zcx_l23_query_##
EXPORTING previous = lx_error.
ENDTRY.
ENDMETHOD.
ENDCLASS.Before activating: If your new generated SCM class is not ZSC_L23_PROD_##, replace that reference with your own generated class. Likewise, use the model ID, version and product entity-set identifier from your SCM → A_ClfnProduct → Read List.
Important scope: This classroom implementation supports basic range filters, requested sorts on its three properties (plus Product key tie-breaking), bounded paging, and remote count requests. It rejects free-text search, unsupported sort/filter fields, and unbounded page requests instead of returning misleading data. It does not implement arbitrary advanced expressions or production authorization. This example is a teaching integration, not a production-ready value-help provider. Do not imply those operations are already supported. A production query provider must handle or explicitly reject every supported query option, protect access, and apply the full API contract.
Step 8.3 — Activate the dependent objects
- Save the query-provider class and custom entity.
- Ensure the exception class
ZCX_L23_QUERY_##is active first. - Activate the query provider
ZCL_L23_PRODUCTS_##(Ctrl+F3). - Activate the custom entity
ZCE_L23_PROD_##(Ctrl+F3). - Fix any model-name, syntax or type errors before continuing.
Checkpoint: Both the CDS custom entity and the ABAP query class are active. At this stage the remote query has not yet been exposed to Fiori.
9. Expose the product collection in your Fiori service
Step 9.1 — Extend the generated Service Definition
- In Project Explorer, open the generated Service Definition
ZUI_L23_STOCK_##(or your actual name). - Keep the existing stock-check exposure.
- Add this line before the closing brace:
abap
expose ZCE_L23_PROD_## as Product;For example, if your generated definition contains expose ZC_L23_STOCK_## as StockCheck;, it should now resemble:
abap
@EndUserText.label: 'Stock Check Service'
define service ZUI_L23_STOCK_## {
expose ZC_L23_STOCK_## as StockCheck;
expose ZCE_L23_PROD_## as Product;
}Caution: This is an illustrative complete service definition using the expected generator names. If your generator assigned different projection/exposure names, preserve those and only add the expose ZCE_L23_PROD_## as Product; line.
- Save and activate the service definition (Ctrl+F3).
Step 9.2 — Publish or refresh the Service Binding
- Open Service Binding
ZUI_L23_STOCK_O4_##. - Keep the existing binding type OData V4 – UI.
- Press Publish / Refresh as required by your ADT version.
- Confirm that both StockCheck and Product entity sets appear in the binding editor.
If Product does not appear, check whether your custom entity and service definition are both active, and refresh the service binding.
10. Add real S/4HANA product value help to the local form
Step 10.1 — Find the ProductId field in the projection
- In Project Explorer, open the generated CDS projection
ZC_L23_STOCK_##(or its actual name). - Find the field mapped from database column
product_id. - The generator will usually expose it as
ProductId. Verify this in your source; if different, place the annotation above the actual projection field name.
Step 10.2 — Add the value-help annotation
Immediately above the ProductId field in the projection view, add:
abap
@Consumption.valueHelpDefinition: [
{ entity: { name: 'ZCE_L23_PROD_##', element: 'Product' },
useForValidation: false }
]For example, the field declaration could then look like:
abap
@Consumption.valueHelpDefinition: [
{ entity: { name: 'ZCE_L23_PROD_##', element: 'Product' },
useForValidation: false }
]
ProductId,Do not paste this as a standalone CDS object. These lines belong inside your existing generated projection view. Keep all its other annotations, behaviors and fields unchanged.
- Activate the projection and service definition.
- Return to the Service Binding, refresh/publish if required, and reopen the Fiori Preview.
Step 10.3 — Test the value help
- Open the Fiori Stock Check list.
- Click Create.
- Find the Product ID input field and click its value-help icon.
- The dialog should query
ZCE_L23_PROD_##and retrieve products from S/4HANA. Use the field/filter controls for exact values; the provider does not implement free-text search, so a generic Search box may return a not-supported error. - Select
A001if present, or another actual product in the list. - Enter
Warehouse = WH01,CountedQty = 8,CheckStatus = Nand a note. - Save the record. Confirm it appears in your local Stock Check list.
Expected result if remote access is available: Fiori selects a remote S/4HANA Product ID, while the local RAP BO saves only your Stock Check data in BTP. This lesson uses useForValidation: false to leave value-help-based frontend input validation disabled. Setting it to true would enable that frontend check, but would not implement Product ID validation on the RAP server. Server-side product validity enforcement requires a separate business validation.
11. What did you actually build?
| Layer | Runs where? | Responsibility |
|---|---|---|
| S/4HANA Product API | Remote S/4HANA | Owns real product master data |
| Communication arrangement + SCM | BTP ABAP | Connects to and describes the external API |
| Custom Entity + Query Provider | BTP ABAP | Provides Fiori a local, read-only query over S/4HANA products |
| Stock Check RAP BO | BTP ABAP | Owns new custom transactional stock-check records |
| Fiori Elements | Browser + OData V4 UI service | Displays the form and remote product selection |
This is the Clean Core connection: custom workflow and persistence live beside S/4HANA on BTP, and S/4HANA master data is read through a supported published API rather than through internal SAP tables.
12. Troubleshooting by symptom
| What happens | Check first |
|---|---|
| Local Stock Check app fails before integration | Generator objects, draft behavior, binding publication, Fiori preview |
ZCL_L23_API_TEST_## cannot retrieve products | Check the new SCM Model Passport, scenario access and service root |
| Custom Entity activation fails | Query class name in @ObjectModel.query.implementedBy and IF_RAP_QUERY_PROVIDER |
TYS_A_CLFN_PRODUCT_TYPE unknown | Actual generated class and type from SCM Read List |
| Product value-help dialog is empty | First test the query provider and network; inspect filter name mapping and count handling |
| Wrong count or page | Check remote request_count and get_count, set_top, set_skip, sorted Product key |
| API failure looks like zero rows | Check exception propagation; do not catch-and-ignore the remote exception |
| Product field lacks value-help icon | Annotation is on the projection ProductId field, both entities are exposed in the service definition, binding is refreshed |
| Value help lists data but stock check cannot save | Diagnose the local RAP BO and draft service separately |
| Product ID gets truncated | Local product_id field must accommodate the remote Product key (CHAR(40)) |
Remote connection fallback
If the shared Trial S/4HANA API is not reachable, complete the local RAP/Fiori stock-check flow from Sections 3–5 and show that it saves local data. Explain the custom-entity implementation and identify the precise backend failure. Do not present manually typed product IDs as a working remote value help.
13. Guided completion checklist
Next: Open Lesson 23 Practice and complete the warehouse supervisor's improvement ticket independently.
References
SAP Help — Creating Service Consumption Models in Eclipse ADT
SAP Developer — Generate RAP UI service from a database table
SAP API — Product Master Data Including Classification (Read)
Verification boundary: The shared Trial Product API connection was observed working in a previous course-environment test. The SCM in this lesson is newly created and has not been tested in your tenant. The complete new project—especially this custom query provider, generated RAP app, value-help integration and Fiori Preview—has not been compiled or executed in a Trial tenant in this turn. Check activation/behavior in ADT before class. The code is written to follow SAP's documented APIs; no compilation or remote execution is claimed.