# Calling SAP Function Modules

Using SAP Remote Function Calls (RFC) type function modules is an easy way to retrieve data from SAP ABAP systems for use in Checks and also to perform actions and update data for use in Automations.

SAP provides a library of standard, released function modules called SAP Business Application Programming Interfaces, or BAPIs for short, to access and manipulate standard SAP data however your SAP ABAP development team will be able to create custom function modules that you can also call.

| Calling SAP Function Modules and access to the sap global object in particular is only available if the script is running for a system type of `SAP System` or `SAP Instance`.

## Code Walkthrough

The object RFCJCOFunction is the key object that represents a function module in SAP and provides the ability to set it’s parameters, to execute it and to retrieve the results.

### Getting a function module object

There are two ways to create an RFCJCOFunction,

1. By using the rfcConnection global object and calling the method getFunction(..) (which uses the default client) e.g.

       const myFunction = rfcConnection.getFunction('Z_TEST')

2. By using the sap global object to specify a client using the login(..) method before the getFunction(..) call. e.g.

       const myFunction = sap.login("100").getFunction('Z_TEST')

   | You can use the method getClients() to retrieve a list of clients on the SAP system to use with login(..)

### Setting parameters

SAP distinguishes between different types of parameters, Importing, Changing, Tables that can be used as inputs to a function module to control it’s behaviour. In Avantra JavaScript extensions, we can set all of these input parameters using the withParameter(..) or withParameters(..) methods on the RFCJCOFunction you have just retrieved.

For example:

    const myFunction = rfcConnection.getFunction('Z_TEST')
                            .withParameter("IV_INPUT", "TEST")
                            .withParameter("CV_CHANGING", "TEST2")

or this:

    const myFunction = rfcConnection.getFunction('Z_TEST')
                            .withParameters({
                                IV_INPUT: "TEST",
                                CV_CHANGING: "TEST2"
                            })

To set structure like parameters or tables of structures, Javascript maps and arrays are used.

    const myFunction = rfcConnection.getFunction('Z_TEST')
                            .withParameters({
                                // Structured importing parameter
                                IS_INPUT: {
                                    FIELD1: "TEST",
                                    FIELD2: 21.11,
                                    FIELD3: "X"
                                },
                                // Tables parameter T_TAB with 2 records
                                T_TAB: [
                                    // Row 1
                                    {
                                        VALUE1: "TEST"
                                        // ...
                                    },
                                    // ROW 2
                                    {
                                        VALUE1: "TEST2"
                                    }
                                ]
                            })

| Although parameters on function module in ABAP are case-insensitive, when using these functions in JavaScript you must use uppercase parameter names and field names in structures.

#### SAP RFC Parameter Types

In creating SAP RFCs, the importing, exporting, changing and tables parameters are defined using SAP types called data elements or domains. These contain information about their use (names, descriptions etc.) but more importantly for us, these also refer to a specific built in data type that defines the type of data expected.

When passing values from a script to the SAP API, it is important to know the data type of these parameters to pass the value in the correct format.

Here we’ve listed all the possible data types and therefore all the different types of data you can pass to an SAP RFC from a script in Avantra:

    const rfcInputs = {
        INT2_FIELD: 2, // or "2" (as a string)
        ACCP_FIELD: "202202", // for 2nd period of 2022
        TIMS_FIELD: "11:33:21",
        CLNT_FIELD: "001",
        DEC_FIELD: 22.121,
        CURR_FIELD: 27.5,
        INT1_FIELD: 1, // or "1" (as a string)
        UNIT_FIELD: "EA",
        FLTP_FIELD: 23.4413212321,
        INT4_FIELD: 4, // or "4" (as a string)
        RAW_FIELD: [10, 82, 57, -49, -32, 106, 30, -25, -118, -99, 26, 121, -86, 94, 8, 106],
        QUAN_FIELD: 100.123,
        CHAR_FIELD: "character field",
        PREC_FIELD: 123, // or "123" (as a string)
        DATS_FIELD: "20220224", // for 24th February 2022
        LANG_FIELD: "E",
        CUKY_FIELD: "GBP",
        NUMC_FIELD: "0123"
    }

### Executing and checking the results

Once the parameters are set, call execute( ) to run the function module on SAP.

Once a function module is executed, the return values from SAP, any Exporting, Changing and Tables parameters, are available through the result(..) and results( ) method calls.

For example:

    myFunction.execute();

    const error1 = myFunction.result("EV_ERROR");
    // or
    const error2 = myFunction.results().EV_ERROR;

If the function module returns structures or tables these are available as Javascript objects and arrays respectively. For example:

    // Retrieves the table parameter T_OUT_TAB
    const { T_OUT_TAB: myResults } = myFunction.results();

    // Print out the message component for each line in the results table
    myResults.forEach(result => console.log(result.message));

### Handling exceptions

Sometimes a RFC can throw an exception. This is reported by default as an error in Avantra, either the automation step will not be successful or the check will have an error associated with it.

Within your JavaScript coding, you can react to these exceptions and give your users a more meaningful error message or change the logic executed to handle this situation differently. For example, write a single check that supports multiple SAP versions where some function modules or behaviours have changed in later versions.

For example:

     try {
        // Check if the function exists
        // - This throws an exception if the function doesn't exist
        const fExists = rfcConnection.getFunction("FUNCTION_EXISTS")
                        .withParameter("FUNCNAME", "ZAVANTRA_CHECK123")
                        .execute();

        // If we are here, this function exists, we can call it
        const check = rfcConnection.getFunction("ZAVANTRA_CHECK123")
                        .withParameter("SOME_PARAM", "VALUE")
                        .execute();

        // Process the result...
        check.status = OK;

    } catch (e) {
        const exName = e.getMessage(); // ABAP exception name = FUNCTION_NOT_EXIST

        if (exName === "FUNCTION_NOT_EXIST") {
            check.status = DISABLED;
            check.message = "Check is disabled because function is not present";
        } else {
            check.status = CRITICAL;
            check.message = "An unknown error occurred: " + exName;
        }
    }

## Known Limitations of RFC Calls

Internally, Avantra uses the SAP Java Connector (SAP JCo) library to execute function modules on SAP. These function modules need to be marked as a "Remote-Enabled Module" in the Attributes tab of the function module definition.

When creating RFCs on SAP, it is important to take note of SAP’s limitations for this code. These can be found in the SAP NetWeaver documentation for your ABAP version. (for example https://help.sap.com/doc/abapdocu_755_index_htm/7.55/en-US/abenrfc_limitations.htm)

Additionally, prior to 21.11, a legacy API existed for calling function modules on SAP. This didn’t hide the complexities of the SAP JCo library from JavaScript and was difficult to use. This legacy API still exists for backward compatibility of your scripts but we strongly recommend you migrate to this new API to simplify your check coding.

## Examples

Example 1. Call an RFC function to count the number of work processes

This example simply calls RFC function module `TH_WPINFO`, evaluates the number of work processes, and displays them in a table. It also uses custom monitoring parameters and outputs custom performance data.

    // prepare result table
    check.addTableColumn("WP_INDEX");
    check.addTableColumn("WP_PID");
    check.addTableColumn("WP_ITYPE");
    check.addTableColumn("WP_TYP");

    // execute the function
    const func = rfcConnection
                    .getFunction("TH_WPINFO")
                    .execute();

    // read WPLIST table
    const wps = func.result("WPLIST")

    wps.forEach(wp => {
      check.newRow();
      check.addCell(wp.WP_INDEX);
      check.addCell(wp.WP_PID);
      check.addCell(wp.WP_ITYPE);
      check.addCell(wp.WP_TYP);
    });

    // make sure that custom monitoring parameter type cust.WorkProcessesWarnCount
    // is created with a default value
    const wpWarnLimit = Number(monitoredSystem.getMoniParam("cust.WorkProcessesWarnCount"));
    const wpCount = wps.length;

    if (wpWarnLimit && wpCount >= wpWarnLimit) {
      check.status = WARNING;
      check.message = wpCount + " work processes found (more than limit of " + wpWarnLimit + ")";
    } else {
      check.status = OK;
      check.message = wpCount + " work processes found";
    }

    // make sure custom performance counter Work Processes is created
    performance.writePerformanceValue("Work Processes", wpCount);

Example 2. Loop over clients and check if ABAP user SAP* user is intentionally locked

This example supplements the built-in security-related check with another common use case: to check if the ABAP user `SAP*` is locked in all clients. Please note that for this example to work, the Avantra RFC user needs to be defined in all clients with the same user and password as the one specified in the SAP system properties. This example also uses Javascript functions to modularise code and prevent repetitive coding.

    // setup check result table
    check.addTableColumn("User");
    check.addTableColumn("Client");
    check.addTableColumn("Groups");
    check.addTableColumn("Wrong Logons");
    check.addTableColumn("Locally Locked");
    check.addTableColumn("Globally Locked");
    check.addTableColumn("No User or Password");

    let userCnt = 0;
    let clientCnt = 0;
    let errors = "";

    const users = [ "SAP*" ];

    // "sap" is the central object. It is hosted by the Avantra Agent and simply
    // available in JavaScript.

    // "sap.clients" is an array of clients of this SAP system
    sap.clients.forEach(client => {
      users.forEach(username => {
        try {
          // sap.login() does a login to the client given in the Avantra
          //             properties of the SAP system and uses the defined
          //             username and password
          // sap.login(client) uses the defined username and password for
          // a given client
          const func = sap.login(client)
                          .getFunction("BAPI_USER_GET_DETAIL")
                          .withParameter("USERNAME", username)
                          .execute();

          check.newRow();
          check.addCell(username);
          check.addCell(client);

          const {
            GROUPS: groups,
            ISLOCKED: {
              WRNG_LOGON: wrongLogonsSAP,
              LOCAL_LOCK: localLockSAP,
              GLOB_LOCK: globalLockSAP,
              NO_USER_PW: noPasswordSAP
            }
          } = func.results();

          // Comma seperated list of groups:
          check.addCell(groups.map(group => group.USERGROUP).join(", "))

          // Locked due to wrong number of logins
          check.addCell(lockedCode2HumanReadable(wrongLogonsSAP).text);

          // Locked locally
          const localLock = lockedCode2HumanReadable(localLockSAP);
          check.addCell(localLock.text, localLock.locked ? OK : CRITICAL);

          // Locked globally
          const globalLock = lockedCode2HumanReadable(globalLockSAP);
          if (globalLock.locked) {
            // user is globally locked
            check.addCell(globalLock.text, OK);

          } else if (!localLock.locked) {
            // if not yet locked, mark this field CRITICAL as well
            check.addCell(globalLock.text, CRITICAL);

          } else {
            // if already locked locally, mark this one as neutral
            check.addCell(globalLock.text);
          }

          // Doesn't exist or no password
          check.addCell(lockedCode2HumanReadable(noPasswordSAP).text);

          if (!localLock.locked && !globalLock.locked) {
            errors += `User ${username} is not intentionally locked in client ${client}.\n`;
          }

          // Increment user count
          userCnt++;
        } catch (e) {
          console.log(e.stack);
          errors += e + ";";
        }
      });

      // Increment client count
      clientCnt++;
    });

    // write result status and message
    check.status = OK;
    let msg = userCnt + " user(s) in " + clientCnt +" client(s) tested.";
    if (errors !== "") {
      msg +=  "\n" + errors;
      check.status = CRITICAL;
    } else {
      msg += "\n" + "All users are locked."
    }

    check.message = msg;

    // Utility function to interpret SAP response
    function lockedCode2HumanReadable(lockCode) {
      let text = "";
      let locked = false;

      switch (lockCode) {
        case "L":
          text = "Locked";
          locked = true;
          break;
        case "U":
          text = "Unlocked";
          break;
        case "":
          text = "User does not exist";
          locked = true;
        break;
          default:
      }

      return {
        text,
        locked
      }
    }

## API Reference

### Global Variable rfcConnection

Global variable `rfcConnection` is an instance of RFCHost. Use this variable to call methods of this class directly in your code, e.g

    const myValue = rfcConnection.getFunction(..);

See RFCHost for further documentation on these methods.

### Global Variable sap

Global variable `sap` is an instance of SAPHost. Use this variable to call methods of this class directly in your code, e.g

    const myValue = sap.login(..);

See SAPHost for further documentation on these methods.

### Class RFCHost

This object provides quick access to the default client of the SAP system in order to call remote function modules (RFCs) and is accessed through global variable rfcConnection. Use method getFunction with the function name to then configure and execute an RFC.

Alternatively use global variable sap to create an instance of this object logged into a different SAP client.

#### bapiXmiLogin(..)

    bapiXmiLogin(interfaceName: string, version: string): boolean

Make an xmi login in order to execute xmi calls which require a login to the given interface. This function executes the RFC function BAPI_XMI_LOGON to login to the given interface.

    rfcConnection.bapiXmiLogin("XBP", "3.0");
    // your RFC calls here
    rfcConnection.bapiXmiLogoff("XBP");

| Please note that `Avantra` is passed for extcompany and `Avantra Agent` for extproduct.

Since

  23.0.4

Parameters

  - `interfaceName`: string

    the name of the XMI interface

  - `version`: string

    the version of the interface to use

Returns

  boolean

  true if the login to the xmi interface was successful, false if not

#### bapiXmiLogin(..)

    bapiXmiLogin(extcompany: string, extproduct: string, interfaceName: string, version: string): boolean

Make an xmi login in order to execute xmi calls which require a login to the given interface. This function executes the RFC function BAPI_XMI_LOGON to login to the given interface.

    rfcConnection.bapiXmiLogin("your_company_name", "your_product_name", "XBP", "3.0");
    // your RFC calls here
    rfcConnection.bapiXmiLogoff("XBP");

Since

  23.0.4

Parameters

  - `extcompany`: string

    extcompany attribute for BAPI_XMI_LOGON

  - `extproduct`: string

    extproduct attribute for BAPI_XMI_LOGON

  - `interfaceName`: string

    the name of the XMI interface

  - `version`: string

    the version of the interface to use

Returns

  boolean

  true if the login to the xmi interface was successful, false if not

#### bapiXmiLogoff(..)

    bapiXmiLogoff(interfaceName: string): void

Logoff from a specific xmi session that function bapiXmiLogin was used to login to.

Since

  23.0.4

Parameters

  - `interfaceName`: string

    the name of the XMI interface

#### beginContext( )

    beginContext(): void

Begins a stateful call sequence for calls on the given destination.

| This call is equivalent to the JCoContext call: `JCoContext.begin(JCoDestination destination);`

Since

  23.0.4

#### endContext( )

    endContext(): void

Ends a stateful call sequence for calls on the specified destination.

| This call is equivalent to the JCoContext call: `JCoContext.end(JCoDestination destination);`

Since

  23.0.4

#### getFunction(..)

    getFunction(rfcFunctionName: string): RFCJCOFunction

Create a reference to an RFC function module call to allow it to be configured and then executed on the SAP system.

Parameters

  - `rfcFunctionName`: string

    the name of the function module to call

Returns

  RFCJCOFunction

  the RFC function module object to manipulate parameters and execute

### Class RFCJCOFunction

The RFCJCOFunction class represents an SAP Remote Function Call (RFC) that can be executed on an SAP system. There are methods to set parameters (withParameters), methods to run the function (execute) and methods to get the return values (results).

Please see the method documentation for examples of using this API.

#### execute( )

    execute(): RFCJCOFunction

Call SAP to execute the function module given the parameters passed to the withParameter and withParameters methods.

This method call is synchronous and the results of executing the function can be accessed by the result and results methods.

Since

  21.11

Returns

  RFCJCOFunction

  this function object to allow for method chaining

#### result(..)

    result(parameterName: string): any

Return the named outputted parameter value that is passed to this method.

SAP function modules can return values from Exporting, Changing and Table parameter lists and this method will return the matching parameter value from any of these lists.

Also consider using the results method to return all values if you need to handle all results in your program.

    const func = rfcConnection.getFunction('Z_TEST_FM')
             // Supply any parameters needed here
             .execute();

    const myValue = func.result("EV_VALUE");
    // Where EV_VALUE is an exporting parameter on function module Z_TEST_FM

    const changingValue = func.result("CV_VALUE");
    // Where CV_VALUE is a changing parameter on function module Z_TEST_FM

    const table = func.result("T_VALUE")
    // where T_VALUE is a table parameter on function module Z_TEST_FM
    table.length; // Provides number of rows
    table[0].COLUMN; // Retrieves 1st records' value of field COLUMN

    const { COLUMN: aValue } = func.result("ES_STRUCT");
    // where ES_STRUCT is an exporting structure of function module Z_TEST_FM
    // aValue is then the same value as `func.result("ES_STRUCT").COLUMN`

Since

  21.11

Parameters

  - `parameterName`: string

    parameter name from result of the function module call

Returns

  any

  the value of the specified parameter

#### results( )

    results(): object

Return all the outputted parameter values from the execution of the function module.

SAP function modules can return values from Exporting, Changing and Table parameter lists and this method will return all values to the caller for further processing.

If there are many results that are not needed by the calling program, it might be more performant to use the result method to individually return specific parameter values.

    const func = rfcConnection.getFunction('Z_TEST_FM')
             // Supply any parameters needed here
             .execute();

    const allResults = func.results();
    // Returns all the 'outputted' values from executing a function module (exporting, changing & table parameters)

    const changingValue = allResults.CV_VALUE;  // Same as func.getResult("CV_VALUE")
    // Where CV_VALUE is a changing parameter on function module Z_TEST_FM

    // The benefit of func.results() is to utilise Javascript destructuring, e.g.
    const { CV_VALUE: changingValue, T_VALUE: tableValue } = func.results()
    // where T_VALUE is a table parameter on function module Z_TEST_FM
    // This gives you the same as func.result("CV_VALUE") and func.result("T_VALUE") in one line

    table.length; // Provides number of rows
    table[0].COLUMN; // Retrieves 1st records' value of field COLUMN

Since

  21.11

Returns

  object

  the value of all result parameters (exporting, changing, tables)

#### withParameter(..)

    withParameter(parameterName: string, value: any): RFCJCOFunction

Set a value for a specific parameter individually that is used to call the function module in execute.

SAP function modules accept Import, Changing and Tables parameters as input to control their logic. This method allows you to supply any of those parameters to the function module. An exception is thrown if the parameter doesn’t exist on any of the parameter lists.

    const func = rfcConnection.getFunction('Z_TEST_FM')
             // Primitive domain type importing parameter
             .withParameter("IV_PARAM", "TEST")
             // Flat table type importing parameter
             .withParameter("IT_PARAM", [
                     "ITEM 1",
                     "ITEM 2"
             ])
             // Table of structured importing parameter
             .withParameter("IT_PARAM_T", [
                     {
                         COLUMN_1: "VALUE"
                     }
             ])
             // Structured changing parameter
             .withParameter("CS_INPUT", {
                 FIELD1: "TEST",
                 FIELD2: 21.11,
                 FIELD3: "X"
             }),
             // Tables parameter T_TAB with 2 records
             .withParameter("T_TAB", [
                 // Row 1
                 {
                     VALUE1: "TEST",
                     // ... other fields
                 },
                 // ROW 2
                 {
                     VALUE1: "TEST2",
                     // other fields
                 }
             ]);

Multiple calls to this method are allowed to set different parameter values, or alternatively you can use withParameters to supply a map of different parameter values to set all at once. Multiple calls to the set the same parameter value simply overwrite the previous value, for example multiple calls to set a table parameter will only result in the value of the last call.

Since

  21.11

Parameters

  - `parameterName`: string

    the name of the parameter to set

  - `value`: any

    the value of the parameter

Returns

  RFCJCOFunction

  this function object to allow for method chaining

#### withParameters(..)

    withParameters(parameters: object): RFCJCOFunction

Set the value of one or multiple parameter values that are used to call the function module in execute.

SAP function modules accept Importing, Changing and Tables parameters as input to control their logic. This method allows you to supply any of those parameters to the function module. An exception is thrown if the parameter doesn’t exist on any of the parameter lists.

The example below is not exhaustive of the different combinations but you can see you can set importing, changing and tables parameters with one call and the value of these parameters are read from corresponding Javascript objects. Note that they are always capitalized.

    const func = rfcConnection.getFunction('Z_TEST_FM')
             .withParameters({
                 // Primitive domain type importing parameter
                 IV_PARAM: "TEST",
                 // Flat table type importing parameter
                 IT_PARAM: [
                     "ITEM 1",
                     "ITEM 2"
                 ],
                 // Table of structured importing parameter
                 IT_PARAM_T: [
                     {
                         COLUMN_1: "VALUE"
                     }
                 ],
                 // Structured changing parameter
                 CS_INPUT: {
                     FIELD1: "TEST",
                     FIELD2: 21.11,
                     FIELD3: "X"
                 },
                 // Tables parameter T_TAB with 2 records
                 T_TAB: [
                     // Row 1
                     {
                         VALUE1: "TEST",
                         // ... other fields
                     },
                     // ROW 2
                     {
                         VALUE1: "TEST2",
                         // other fields
                     }
                 ]
             });

Multiple calls to this method are allowed to set different parameter values, or alternatively you can use withParameter to supply parameters individually. Multiple calls to the set the same parameter value simply overwrite the previous value, for example multiple calls to set a table parameter will only result in the value of the last call.

Since

  21.11

Parameters

  - `parameters`: object

    the parameters to set to call the function module

Returns

  RFCJCOFunction

  this function object to allow for method chaining

### Class SAPHost

The SAPHost class represents an SAP system and provides methods for listing and logging into different SAP clients on that system. Method login returns an RFCHost object that allows remote function calls (RFCs) on SAP to be called to provide additional data for checks or enable automation processes.

#### getClients( )

    getClients(): string[]

Retrieve an array of clients in the system, for example `[ "000", "001", "100" ]`.

Use this, along with the login method, to call client specific function modules on the SAP ABAP system.

    // Call a function module (Z_TEST_FM) on each client in the system
    sap.clients.forEach(client => {
      const func = sap.login(client).getFunction('Z_TEST_FM')
               // Supply any parameters needed here
               .execute();

      // Do something with the function module results
    });

| To use this pattern, the Avantra user must exist in each client with the same password and sufficient authorisations to execute this function module.

Returns

  string[]

  the list of clients in the SAP system

See Also

  - clients

#### getSid( )

    getSid(): string

The SID of the ABAP system this check/automation runs on.

Since

  23.1

Returns

  string

  the SID of the ABAP system

See Also

  - sid

#### isRemote( )

    isRemote(): boolean

Check if system is remote.

Since

  24.3

Returns

  boolean

  true if system is remote

See Also

  - remote

#### isSapControlAvailable( )

    isSapControlAvailable(): boolean

Check if sap control is available.

Since

  24.3

Returns

  boolean

  true if sap control is available

See Also

  - sapControlAvailable

#### login(..)

    login(client: string): RFCHost

Connects to a non-default client (specified by the parameter) in order to call function modules on it.

    const func = sap.login("100").getFunction('Z_TEST_FM')
             // Supply any parameters needed here
             .execute();

    // Do something with the function module results

| To use this pattern, the Avantra user must exist in the target client with the same password and sufficient authorisations to execute this function module.

Parameters

  - `client`: string

    client to login to

Returns

  RFCHost

  an instance of RFCHost to call RFC function modules on

#### loginWith(..)

    loginWith(credentials: any): RFCHost

Uses the passed in credentials to connect to the SAP system in order to call function modules on it. The passed in credentials can system defined credentials or credentials which are passed in as input parameters to automation steps.

    const func = sap.loginWith("namespace.userx").getFunction('Z_TEST_FM')
             // Supply any parameters needed here
             .execute();

    // Do something with the function module results

Since

  21.11.6

Parameters

  - `credentials`: any

    passed in credentials

Returns

  RFCHost

  an instance of RFCHost to call RFC function modules on

#### clients

    readonly clients: string[]

Retrieve an array of clients in the system, for example `[ "000", "001", "100" ]`.

Use this, along with the login method, to call client specific function modules on the SAP ABAP system.

    // Call a function module (Z_TEST_FM) on each client in the system
    sap.clients.forEach(client => {
      const func = sap.login(client).getFunction('Z_TEST_FM')
               // Supply any parameters needed here
               .execute();

      // Do something with the function module results
    });

| To use this pattern, the Avantra user must exist in each client with the same password and sufficient authorisations to execute this function module.

See Also

  - getClients()

Type

  string[]

Modifiers

  Readonly

#### remote

    readonly remote: boolean

Check if system is remote.

See Also

  - isRemote()

Type

  boolean

Modifiers

  Readonly

Since

  24.3

#### sapControlAvailable

    readonly sapControlAvailable: boolean

Check if sap control is available.

See Also

  - isSapControlAvailable()

Type

  boolean

Modifiers

  Readonly

Since

  24.3

#### sid

    readonly sid: string

The SID of the ABAP system this check/automation runs on.

See Also

  - getSid()

Type

  string

Modifiers

  Readonly

Since

  23.1