cancel
Showing results for 
Search instead for 
Did you mean: 

SAP CAP: How to add query parameters to S/4 backend call

TobiT26
Explorer
425

Hello everyone,

I would like to consume an external S/4 backend service (odata) in my SAP CAP application. I have created the corresponding CDS schema and the calls also get through to the backend.

So far so good.

However, in addition to the normal parameters that are available in my CDS schema, there are also mandatory query parameters that I have to specify.

Hence the question: How can I add corresponding query parameters to my request?

I have already tried several possibilities and have actually found a workaround. However, I only see this option as a workaround and not as a permanent solution.

Basically, I build the URI for the call myself and execute the call via ext.send.

// connect to remote service
const ext = await cds.connect.to("ccp");

this.on("READ", "CustomerOpenItemSet", async (req) => {
    //*********************************** */
    //*********************************** */
    //*********************************** */
    //build url with query parameters
    //*********************************** */
    //*********************************** */
    //*********************************** */

    // Define the base URL path for the entity
    let urlWithParams = "/CustomerOpenItemSet?BackendUser=00010000000003&Scenario=D";

    // Manual addition of query parameters
    if (req._query) {
      // Extract the OData parameters from req.query and assemble them
      const queryParams = [];

      // Add further parameters such as $orderby, $select
      if (req._query.$select) {
        queryParams.push(`$select=${req._query.$select}`);
      }

      // for this command I get an error during the call
      // if (req._query.$count) {
      //   queryParams.push(`$count=${req._query.$count}`);
      // }

      if (req._query.$skip) {
        queryParams.push(`$skip=${req._query.$skip}`);
      }

      if (req._query.$top) {
        queryParams.push(`$top=${req._query.$top}`);
      }

      if (req._query.$filter) {
        queryParams.push(`$filter=${req._query.$filter}`);
      }

      if (req._query.$orderby) {
        queryParams.push(`$orderby=${req._query.$orderby}`);
      }

      // Combine the query parameters in the URL
      if (queryParams.length > 0) {
        urlWithParams += `&${queryParams.join("&")}`;
      }
    }

    console.log(urlWithParams);

    try {
      // Send the GET request with the dynamic parameters
      const result = await ext.send({
        method: "GET",
        path: urlWithParams,
      });

      //I need this so that the result is displayed in the frontend
      result.$count = result.length;

      return result;
    } catch (error) {
      console.error("Error when retrieving the data:", error);
      req.error({
        code: "EXTERNAL_SERVICE_ERROR",
        message: `Error when retrieving the data: ${error.message}`,
      });
    }

  });

 

Below are a few two other attempts, none of which led to the desired result.

  this.on("READ", "CustomerOpenItemSet_try1", async (req) => {
    //*********************************** */
    //*********************************** */
    //*********************************** */
    //add BackendUser as query-parameter
    //*********************************** */
    //*********************************** */
    //*********************************** */

    req._query.BackendUser = "00010000000003";
    const result1 = await ext.run(req.query);
    //OR
    req.query.BackendUser = "00010000000003";
    const result2 = await ext.run(req.query);

    return result1;
    //*********************************** */
    //--> BackendUser does not arrive in the backend as a query parameter
    //*********************************** */
  });
  this.on("READ", "CustomerOpenItemSet_try2", async (req) => {
    //*********************************** */
    //*********************************** */
    //*********************************** */
    //send-Request with query + headers
    //*********************************** */
    //*********************************** */
    //*********************************** */

    const result = await ext.send({
      query: req.query,
      headers: { BackendUser: "00010000000003" },
    });

    console.log(result);

    return result;

    //*********************************** */
    //--> BackendUser does not arrive in the backend as a query parameter
    //*********************************** */
  });


Do the CAP experts have any good ideas for my problem? 🙂 @gregorw @qmacro etc.

 

gregorw
Active Contributor
0 Kudos
Can you provide more details how the backend service is implemented? Are the parameters BackendUser and Scenario part of the OData Metadata? Maybe you could also provide more details what you want to achieve with the BackendUser and Scenario.

Accepted Solutions (0)

Answers (2)

Answers (2)

Dinu
Contributor
0 Kudos

I suppose these parameters are fixed for a connection. If so, you can add the following additional properties to the destination.

URL.queries.BackendUser=00010000000003
URL.queries.Scenario=D

The parameters  will get added to the URL as query parameters. 

gregorw
Active Contributor
0 Kudos
But this would only work if all users of the app should see the same data.
TobiT26
Explorer
0 Kudos
Yes, I know the option in the destination. But these are no fix values. In my example coding, I had only stored the values for test purposes.
Dinu
Contributor
0 Kudos
Is this service listed in api.sap.com?
TobiT26
Explorer
0 Kudos

Hi Gregor,

unfortunately BackendUser and Scenario are not part of the OData metadata.
Although there are the properties BPCustomerNumber and CompanyCode (BackendUser is made up of CompanyCode + BPCustomerNumber), the service requires the BackendUser and Scenario to be specified via query parameters.

Below is the metadata of the CustomerOpenItem entity from which I want to determine the data. There ist no BackendUser and Scenario.

<EntityType Name="CustomerOpenItem" sap:content-version="1">
<Key>
<PropertyRef Name="AccountingDocument"/>
<PropertyRef Name="FiscalYear"/>
<PropertyRef Name="CompanyCode"/>
<PropertyRef Name="BPCustomerNumber"/>
<PropertyRef Name="AccountingDocumentItem"/>
</Key>
<Property Name="AccountingDocument" Type="Edm.String" Nullable="false" MaxLength="10" sap:unicode="false" sap:label="Belegnummer"/>
<Property Name="BillingDocument" Type="Edm.String" MaxLength="10" sap:unicode="false" sap:label="Vertriebsbeleg"/>
<Property Name="ReferenceDocumentNumber" Type="Edm.String" MaxLength="10" sap:unicode="false" sap:label="Belegnummer"/>
<Property Name="FiscalYear" Type="Edm.String" Nullable="false" MaxLength="4" sap:unicode="false" sap:label="Geschäftsjahr"/>
<Property Name="CompanyCode" Type="Edm.String" Nullable="false" MaxLength="4" sap:unicode="false" sap:label="Buchungskreis"/>
<Property Name="BPCustomerNumber" Type="Edm.String" Nullable="false" MaxLength="10" sap:unicode="false" sap:label="Debitor"/>
<Property Name="AccountingDocumentItem" Type="Edm.String" Nullable="false" MaxLength="3" sap:unicode="false" sap:label="Position"/>
<Property Name="NetDueDate" Type="Edm.DateTime" Precision="0" sap:unicode="false" sap:label="Datum"/>
<Property Name="PostingDate" Type="Edm.DateTime" Precision="0" sap:unicode="false" sap:label="Datum"/>
<Property Name="TransactionCurrency" Type="Edm.String" MaxLength="5" sap:unicode="false" sap:label="Währung" sap:semantics="currency-code"/>
<Property Name="AmountInTransactionCurrency" Type="Edm.Decimal" Precision="23" Scale="4" sap:unicode="false" sap:label="Währungsbetrag"/>
<Property Name="OpenAmountInTransCrcy" Type="Edm.Decimal" Precision="23" Scale="4" sap:unicode="false" sap:label="Währungsbetrag"/>
<Property Name="DocumentDate" Type="Edm.DateTime" Precision="0" sap:unicode="false" sap:label="Belegdatum"/>
<Property Name="NetPaymentAmount" Type="Edm.Decimal" Precision="23" Scale="4" sap:unicode="false" sap:label="Währungsbetrag"/>
<Property Name="PaymentAmountInDisplayCrcy" Type="Edm.Decimal" Precision="23" Scale="4" sap:unicode="false" sap:label="Währungsbetrag"/>
<Property Name="DisplayCurrency" Type="Edm.String" MaxLength="5" sap:unicode="false" sap:label="Währung" sap:semantics="currency-code"/>
<Property Name="TotalAmountInDisplayCrcy" Type="Edm.Decimal" Precision="23" Scale="4" sap:unicode="false" sap:label="Währungsbetrag"/>
<Property Name="OpenAmountInDisplayCrcy" Type="Edm.Decimal" Precision="23" Scale="4" sap:unicode="false" sap:label="Währungsbetrag"/>
<Property Name="CashDiscountAmountInDspCrcy" Type="Edm.Decimal" Precision="23" Scale="4" sap:unicode="false" sap:label="Währungsbetrag"/>
<Property Name="CashDiscountDueDate" Type="Edm.DateTime" Precision="0" sap:unicode="false" sap:label="Datum"/>
<Property Name="PartnerFunctionIsPrintRelevant" Type="Edm.Boolean" sap:unicode="false" sap:label="Feld zum Ankreuzen"/>
<Property Name="FunctionIsXMLRelevant" Type="Edm.Boolean" sap:unicode="false" sap:label="Feld zum Ankreuzen"/>
<Property Name="SpecialGLCode" Type="Edm.String" MaxLength="1" sap:unicode="false" sap:label="Sonderhauptb.Kz"/>
<Property Name="PostingKey" Type="Edm.String" MaxLength="2" sap:unicode="false" sap:label="Buchungsschl."/>
<Property Name="PostingKeyName" Type="Edm.String" MaxLength="20" sap:unicode="false" sap:label="Bedeutung"/>
<Property Name="DocumentReferenceID" Type="Edm.String" MaxLength="16" sap:unicode="false" sap:label="Referenz"/>
<Property Name="DebitCreditCode" Type="Edm.String" MaxLength="1" sap:unicode="false" sap:label="Soll/Haben"/>
<Property Name="AccountingDocExternalReference" Type="Edm.String" MaxLength="40" sap:unicode="false" sap:label="RechnungNr."/>
<Property Name="AccountingDocumentItemRef" Type="Edm.String" MaxLength="6" sap:unicode="false" sap:label="Zeilennummer"/>
<Property Name="InvoiceProcessingStatus" Type="Edm.String" MaxLength="2" sap:unicode="false" sap:label="Status"/>
<Property Name="PartialPaymentIsEnabled" Type="Edm.Boolean" sap:unicode="false" sap:label="Keine Teilzahlung"/>
<Property Name="PaymentIsEnabled" Type="Edm.Boolean" sap:unicode="false" sap:label="Feld zum Ankreuzen"/>
<Property Name="PartialPaymentHistoryDesc" Type="Edm.String" MaxLength="8192" sap:unicode="false" sap:label="SLD Agent: Ersatz für Typ "String" in Releases vor 4.5"/>
<Property Name="PaymentCurrency" Type="Edm.String" MaxLength="5" sap:unicode="false" sap:label="Währung" sap:semantics="currency-code"/>
<Property Name="PaymentTerms" Type="Edm.String" MaxLength="4" sap:unicode="false" sap:label="Zahlungsbed"/>
<Property Name="OriginalCashDiscount1Days" Type="Edm.Decimal" Precision="3" Scale="0" sap:unicode="false" sap:label="Tage 1"/>
<Property Name="OriginalCashDiscount2Days" Type="Edm.Decimal" Precision="3" Scale="0" sap:unicode="false" sap:label="Tage 2"/>
<Property Name="BranchAccount" Type="Edm.String" MaxLength="10" sap:unicode="false" sap:label="Filiale"/>
<Property Name="AccountingDocumentHeaderText" Type="Edm.String" MaxLength="25" sap:unicode="false" sap:label="Belegkopftext"/>
<Property Name="DueCalculationBaseDate" Type="Edm.DateTime" Precision="0" sap:unicode="false" sap:label="Basisdatum"/>
</EntityType>

The service is based on the CL_API_EBPPOPLACCTGDOC_DPC_EXT class and the implementation can be found in the CUSTOMEROPENITEM_GET_ENTITYSET method.

As an example, I have the following FIORI Elements interface. There I filter for BPCustomerNumber and CompanyCode.

TobiT26_0-1726500045922.png

I have adapted the redefinition of onRead.CustomerOpenItemSet for the example as follows, so that the call is sent to the backend without ‘manipulation’.

TobiT26_1-1726500347442.png

The backend user is to be read from the query parameters of the URL in the ls_cust_param structure shown in the screenshot below.

TobiT26_2-1726500501805.png

TobiT26_3-1726500577993.png

TobiT26_4-1726500643756.png

TobiT26_5-1726500656977.png

The structure ls_odata_param contains, as expected, all the odata parameters like SORT, SELECT_COL, FILTER, ... The filter contains the filtered criterias from the FIORI surface.

TobiT26_7-1726501247126.pngTobiT26_8-1726501257456.png

 

The structure is then transferred to the function module EBPP_BD_GET_CUSTOMER_ITEMS. There is a condition whether the fields are filled. If this is not the case, the function module is no longer executed.

TobiT26_6-1726500762080.png

From my point of view, there is therefore no chance of using the ‘normal’ filter functionality to select the data.

If I manually fill the structure ls_cust_param in the debugger, I get the desired result. The filters from the ODATA model are also correctly taken.

gregorw
Active Contributor
What a mess is this standard implementation. The correct way to implement would be a view with parameters. That way the definition would also show up in the metadata. Is there an option to create your own wrapper OData Service that exposes this parameters as normal attributes that you can filter? That way you could fill the Attributes to the user of the app in Identity Authentication and use them as a server side filter in the CAP app.
TobiT26
Explorer
0 Kudos
Basically, you confirm my assumption - thank you very much for that. I have thought about the possibility of creating a wrapper. But that would only be an absolute emergency solution. I'll try to get in touch with the SAP team responsible for this again. I have already been in contact and was told that the transfer of query parameters is normal.
gregorw
Active Contributor
0 Kudos
If it would be "normal" then it should be reflected in the Metadata / EDMX as this should specify the full possibilities of the OData Service. This is a hack.
junwu
Active Contributor
0 Kudos
what's the version of your system? I didn't find that class in my s4 2022/2023