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.

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

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
Type

string[]

Modifiers

Readonly