SAP Home Learn Build Integrate Model Operate Extend with AI ConnectTutorial navigator Knowledge Graph API Devtoberfest Developer Advocates App Space

Manage my Account SAP Devs YouTube ↗ Learnings ↗ Community ↗ Provide Feedback ↗
Logout
โคข Open full site

Get the Business Partner API and Extend the Incident Management Application

Learn how to get the Business Partner API from SAP S/4HANA Cloud and extend the functionality of the Incident Management application.

Overview

🎓 intermediate 30 min. SAP Cloud Application Programming ModelIntermediateNode JsSAP Business Technology Platform

You will learn

  • โœ”How to get the Business Partner API from SAP S/4HANA Cloud.
  • โœ”How to extend the functionality of the Incident Management application.
Created by December 5, 2024
Contributors

Prerequisites

Prerequisites

OUT OF MAINTENANCE

This tutorial is no longer maintained. For an up-to-date version and support with any issues, refer to the Develop a Side-by-Side CAP-Based Extension Application Following the SAP BTP Developer’s Guide mission in the SAP Discovery Center.

Steps

Step 1 Get the Business Partner API
โ€”

SAP Business Accelerator Hub is a web application hosted by SAP where you can discover, explore, and test SAP and partner APIs (Application Programming Interfaces) that are required to build extensions or process integrations.

In the SAP Business Accelerator Hub, you can find the APIs that you need to complete your extension scenario.

For this scenario, you use the Business Partner API from SAP S/4HANA Cloud.

Step 2 Download the Business Partner API
+
Step 3 Add new business logic to the Incident Management application
+
  • In the srv folder, create a new file remote.cds and paste the following code in it:

    CDS
    using { API_BUSINESS_PARTNER as S4 } from './external/API_BUSINESS_PARTNER';
    
    service RemoteService {
      entity BusinessPartner as projection on S4.A_BusinessPartner {
        key BusinessPartner as ID,
        FirstName as firstName,
        LastName as lastName,
        BusinessPartnerName as name,
        to_BusinessPartnerAddress as addresses
      }
      entity BusinessPartnerAddress as projection on S4.A_BusinessPartnerAddress {
                BusinessPartner as ID,
                AddressID as addressId,
                to_EmailAddress as email,
                to_PhoneNumber as phoneNumber
      }
      entity EmailAddress as projection on S4.A_AddressEmailAddress {
          key AddressID as addressId,
          EmailAddress as email
      }
    
      entity PhoneNumber as projection on S4.A_AddressPhoneNumber {
          key AddressID as addressId,
          PhoneNumber as phone
      }
    }
  • Add application logic for reading and saving a business partner:

    1. Open the srv/services.js file.

    2. Set the init method to async:

      js[1]
      async init() {
        this.before("UPDATE", "Incidents", (req) => this.onUpdate(req));
        this.after("READ", "Incidents", (data) => this.changeUrgencyDueToSubject(data));
      
        return super.init();
      }
    3. Add a custom handler for READ of customers in the init() method:

      js[4]
      async init() {
        this.before("UPDATE", "Incidents", (req) => this.onUpdate(req));
        this.after("READ", "Incidents", (data) => this.changeUrgencyDueToSubject(data));
        this.on('READ', 'Customers', (req) => this.onCustomerRead(req));
      
        return super.init();
      }
    4. Add the custom handler implementation after the init() method:

      js[9-44]
      async init() {
        this.before("UPDATE", "Incidents", (req) => this.onUpdate(req));
        this.after("READ", "Incidents", (data) => this.changeUrgencyDueToSubject(data));
        this.on('READ', 'Customers', (req) => this.onCustomerRead(req));
      
        return super.init();
      }
      
      async onCustomerRead(req) {
        console.log('>> delegating to S4 service...', req.query);
        let { limit, one } = req.query.SELECT
        if(!limit) limit = { rows: { val: 55 }, offset: { val: 0 } } //default limit to 55 rows
      
        const { BusinessPartner } = this.remoteService.entities;
        const query = SELECT.from(BusinessPartner, bp => {
          bp('*');
          bp.addresses(address => {
            address('email');
            address.email(emails => {
              emails('email');
            });
          });
        }).limit(limit)
      
        if(one){
          // support for single entity read
          query.where({ ID: req.data.ID });
        }
        // Expands are required as the runtime does not support path expressions for remote services
        let result = await this.S4bupa.run(query);
      
        result = result.map((bp) => ({
          ID: bp.ID,
          name: bp.name,
          email: (bp.addresses[0]?.email[0]?.email || ''),
          firstName: bp.firstName,
          lastName: bp.lastName,
        }));
      
        // Explicitly set $count so the values show up in the value help in the UI
        result.$count = 1000;
        console.log("after result", result);
        return result;
      }

      What happens here?

      The highlighted snippet defines the onCustomerRead method to handle reading of customer data.

      The method:

      • logs the request query
      • parses the $top and $skip query options with default values
      • extracts the BusinessPartner entity from the remote service
      • fetches the business partner data from the remote service (including e-mail addresses)
      • maps the result to include only the necessary fields
      • sets the $count property
    5. Add a custom handler for CREATE and UPDATE of incidents. Add these code lines to the init() method:

      js[5-7]
      async init() {
        this.before("UPDATE", "Incidents", (req) => this.onUpdate(req));
        this.after("READ", "Incidents", (data) => this.changeUrgencyDueToSubject(data));
        this.on('READ', 'Customers', (req) => this.onCustomerRead(req));
        this.on(['CREATE','UPDATE'], 'Incidents', (req, next) => this.onCustomerCache(req, next));
        this.S4bupa = await cds.connect.to('API_BUSINESS_PARTNER');
        this.remoteService = await cds.connect.to('RemoteService');
      
        return super.init();
      }
    6. Add the custom handler implementation after the init() method:

      js[12-42]
      async init() {
        this.before("UPDATE", "Incidents", (req) => this.onUpdate(req));
        this.after("READ", "Incidents", (data) => this.changeUrgencyDueToSubject(data));
        this.on('READ', 'Customers', (req) => this.onCustomerRead(req));
        this.on(['CREATE','UPDATE'], 'Incidents', (req, next) => this.onCustomerCache(req, next));
        this.S4bupa = await cds.connect.to('API_BUSINESS_PARTNER');
        this.remoteService = await cds.connect.to('RemoteService');
      
        return super.init();
      }
      
      async onCustomerCache(req, next) {
        const { Customers } = this.entities;
        const newCustomerId = req.data.customer_ID;
        const result = await next();
        const { BusinessPartner } = this.remoteService.entities;
        if (newCustomerId && newCustomerId !== "") {
          console.log('>> CREATE or UPDATE customer!');
          // Expands are required as the runtime does not support path expressions for remote services
          const customer = await this.S4bupa.run(SELECT.one(BusinessPartner, bp => {
            bp('*');
              bp.addresses(address => {
                address('email', 'phoneNumber');
                  address.email(emails => {
                    emails('email')
                  });
                  address.phoneNumber(phoneNumber => {
                    phoneNumber('phone')
                  })
              })
          }).where({ ID: newCustomerId }));
      
          if(customer) {
            customer.email = customer.addresses[0]?.email[0]?.email;
            customer.phone = customer.addresses[0]?.phoneNumber[0]?.phone;
            delete customer.addresses;
            delete customer.name;
            await UPSERT.into(Customers).entries(customer);
          }
        }
        return result;
      }
      
      async onCustomerRead(req) {
        console.log('>> delegating to S4 service...', req.query);
        const top = parseInt(req._queryOptions?.$top) || 100;
        const skip = parseInt(req._queryOptions?.$skip) || 0;
      
        const { BusinessPartner } = this.remoteService.entities;
      
        // Expands are required as the runtime does not support path expressions for remote services
        let result = await this.S4bupa.run(SELECT.from(BusinessPartner, bp => {
          bp('*'),
            bp.addresses(address => {
              address('email'),
                address.email(emails => {
                  emails('email');
                });
            })
        }).limit(top, skip));
      
        result = result.map((bp) => ({
          ID: bp.ID,
          name: bp.name,
          email: bp.addresses[0]?.email[0]?.email
        }));
      
        // Explicitly set $count so the values show up in the value help in the UI
        result.$count = 1000;
        console.log("after result", result);
        return result;
      }

      What happens here?

      The highlighted snippet defines the onCustomerCache method to handle caching of customer data during CREATE or UPDATE events.

      The method:

      • extracts the Customers entity and the new customer ID from the request data
      • calls the next middleware or handler in the chain
      • extracts the BusinessPartner entity from the remote service
      • checks if the new customer ID is valid and if the event is either CREATE or UPDATE
      • fetches the customer data from the remote service (including e-mail and phone number)
      • if the customer data is found, updates the local Customers entity while also removing unnecessary fields
  • To run the tests, navigate to the tests/test.js file and replace line no.3 with the following highlighted line:

    js[3]
    const cds = require('@sap/cds/lib')
    const { default: axios } = require('axios') 
    const { GET, POST, DELETE, PATCH, expect } = cds.test(__dirname + '../../', '--with-mocks');
    
    axios.defaults.auth = { username: 'incident.support@tester.sap.com', password: 'initial' }
    
    jest.setTimeout(11111)
    ...
  • Run the tests:

    Shell
    npm run test
  • You’ve integrated the Business Partner API into your project and business logic to read the data from the back end system. New or changed customer data is stored in your application database.

    Resources

    Discussion

    Share feedback on this tutorial or join the conversation in SAP Community.

    Submit detailed feedback Discuss in Community
    Steps
    Step 1 of 3
    1. Get the Business Partner API 2. Download the Business Partner API 3. Add new business logic to the Incident Management application

    Learn more →