Enterprise Resource Planning Blogs by SAP
Get insights and updates about cloud ERP and RISE with SAP, SAP S/4HANA and SAP S/4HANA Cloud, and more enterprise management capabilities with SAP blog posts.
cancel
Showing results for 
Search instead for 
Did you mean: 
TNegash
Advisor
Advisor
The goal of this blog post is to guide you through the tasks that are required to build the first extension application in the SAP CP Cloud Foundry environment that consumes ByD OData APIs.

The application that we are going to develop in this blog post is a simple sales order, which is made up of root and item nodes. The application will comprise the following features:



    1. A data model with persistence in the SAP HANA database

    2. A service model that comprises action for performing rudimentary product availability check and a remote service to access product data from the SAP ByD tenant

    3. SAP Fiori UIs that enable the creation, reading, updating and deleting of the sales order instances

    4. Custom logic made up of the following building blocks:

      • A Java implementation to read data from the SAP ByD system using an OData API

      • A java implementation to carry out product availability check, this implementation writes a random quantity between 0 and the requested quantity in the confirmed quantity fields.






Create an Extension Application Project


This topic will guide you on how to create your first SAP Cloud Application Programming Model project in the SAP CP Cloud Foundry environment.

  1. Launch SAP Web IDE using the URL that you added to your favorites during development environment configuration.

  2. In SAP Web IDE, go to File → New → Project from Template.

  3. On the screen shown below, select the tile SAP Cloud Platform Business Application and click Next.




  1. Enter a project name on the following Basic Information screen and click Next.




  1. On the Template Customization screen, enter a description, select Use HTML5 Application Repository and click Next.




  1. On the Project Details screen, leave everything as it is. Don’t enable user authentication and authorization (UAA). Securing extension application using SAP CP User Authentication and Authorization (XSUAA) service in combination with SAP CP IAS will be handled in a separated blog post in this series in the future.

  2. Click on the Finish button in the lower part of the Project Details screen. This will lead to the creation of a new project with variety of files, among others Core Data & Services (CDS) templates for data model, service model and Multi-Target Application (MTA) file. The MTA file specifies the configuration, composition and dependencies between modules and services of the application.



Define Data Model


In this topic, we show the steps that are required to define a data model for our simple Sales Order Processing application.

  1. Go to the file WorkSpace/SalesOrderProcessing/db/data-model.cds and double click the file.




  1. Replace the content of the data.model.cds with the following CDS model. This data model will later be deployed to the HANA database to create the database schema and database tables.


namespace my.app;
entity Salesorder{
Key salesOrderID : String;
customer : String;
orderDate : DateTime;
Salesorderitem : Composition of many Salesorderitem on Salesorderitem.salesOrder = $self;
}

entity Salesorderitem{
Key itemID : Integer;
Key salesOrder : Association to Salesorder @Common.Label: 'Sales Order ID' @Common.FieldControl: #Hidden;
productID : String;
productDescription : String;
quantity : Decimal(12,3);
quantityUOM : String;
confirmedQuantity : Decimal(12,3);
confirmedQuantityUOM : String;
price: Decimal(12,3);
currency: String(3);
}


  1. Go to File → Save All to save your data model and complete your data model maintenance activities.



Define Services


In this topic, we will learn how to add a simple service that exposes the entities from the previously created data model to the UIs of our Sales Order Processing application.

  1. Go to the file WorkSpace/SalesOrderProcessing/srv/my-service.cds and double click the file.

  2. Replace the content of my-service.cds with the following CDS model:


using my.app from '../db/data-model';

service Salesorderhandling {

@insertonly entity Salesorder as projection on app.Salesorder;

@insertonly entity Salesorderitem as projection on app.Salesorderitem{
*
} actions {
action CheckATP( ) returns String(255);
};


}


  1. Go to File → Save All to save your service model.

  2. Right click on the project SalesOrderProcessing then select Build → Build CDS. This will lead to generation of OData EDMX metadata xml file.



Add Remote Service


In this section we will see how a remote service (SAP ByD OData API) is added to the Sales Order Processing application. This SAP ByD OData API will be used to read product data from the SAP ByD tenant. This product data will then be entered in the sales order item.

  1. In SAP ByD system, create Product OData APIs with a metadata specification like the XML file shown below. Details on how to create OData APIs can be found under SAP Business ByDesign OData Service Modeler.


<edmx:Edmx xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/edmx" Version="1.0">
<edmx:DataServices xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" m:DataServiceVersion="1.0">
<Schema xmlns="http://schemas.microsoft.com/ado/2008/09/edm" xmlns:sap="http://www.sap.com/Protocols/SAPData" xmlns:c4c="http://www.sap.com/Protocols/C4CData" Namespace="cust">
<EntityType Name="Material">
<Key>
<PropertyRef Name="ObjectID"/>
</Key>
<Property Name="ObjectID" Type="Edm.String" Nullable="false" MaxLength="70" FixedLength="true" sap:creatable="false" sap:updatable="false" sap:filterable="true"/>
<Property Name="UUID" Type="Edm.Guid" Nullable="true" sap:creatable="false" sap:updatable="false" sap:filterable="true"/>
<Property Name="InternalID" Type="Edm.String" Nullable="true" MaxLength="40" FixedLength="true" sap:creatable="true" sap:updatable="true" sap:filterable="true"/>
<Property Name="Description" Type="Edm.String" Nullable="true" MaxLength="40" FixedLength="true" sap:creatable="true" sap:updatable="true" sap:filterable="true"/>
<Property Name="DescriptionlanguageCode" Type="Edm.String" Nullable="true" MaxLength="2" FixedLength="true" sap:creatable="true" sap:updatable="true" sap:filterable="true" c4c:value-help="MaterialDescriptionlanguageCodeCollection" sap:text="DescriptionlanguageCodeText"/>
<Property Name="DescriptionlanguageCodeText" Type="Edm.String" Nullable="true" sap:creatable="false" sap:updatable="false" sap:filterable="true"/>
<Property Name="BaseMeasureUnitCode" Type="Edm.String" Nullable="true" MaxLength="3" FixedLength="true" sap:creatable="true" sap:updatable="true" sap:filterable="true" c4c:value-help="MaterialBaseMeasureUnitCodeCollection" sap:text="BaseMeasureUnitCodeText"/>
<Property Name="BaseMeasureUnitCodeText" Type="Edm.String" Nullable="true" sap:creatable="false" sap:updatable="false" sap:filterable="true"/>
</EntityType>
<EntityType Name="CodeList">
<Key>
<PropertyRef Name="Code"/>
</Key>
<Property Name="Code" Type="Edm.String" Nullable="false" FixedLength="false" sap:creatable="false" sap:updatable="false" sap:filterable="true"/>
<Property Name="Description" Type="Edm.String" Nullable="false" FixedLength="false" sap:creatable="false" sap:updatable="false" sap:filterable="true"/>
</EntityType>
<EntityContainer Name="byd_product" m:IsDefaultEntityContainer="true">
<EntitySet Name="MaterialBaseMeasureUnitCodeCollection" EntityType="cust.CodeList" sap:creatable="false" sap:updatable="false" sap:deletable="false" sap:semantics="fixed-values"/>
<EntitySet Name="MaterialCollection" EntityType="cust.Material" sap:creatable="true" sap:updatable="true" sap:deletable="true"/>
<EntitySet Name="MaterialDescriptionlanguageCodeCollection" EntityType="cust.CodeList" sap:creatable="false" sap:updatable="false" sap:deletable="false" sap:semantics="fixed-values"/>
</EntityContainer>
</Schema>
</edmx:DataServices>
</edmx:Edmx>


  1. Right click on WorkSpace/SalesOrderProcessing/srv and select New → Data Model from External Service.




  1. On the ‘Data Connection’ screen select under ‘Sources’ the entry ‘Service URL’ and click on ‘create new data sources’




  1. On the following screen populate the fields as specified below and click Create.














































Parameter Value
User ID User ID of your SAP CP NEO subaccount
Password Password of your SAP CP NEO subaccount
Name Name of Destination e.g. ByDSyS1
Description Description of the Destination (optional)
Proxy Type Internet
URL https://my<ByD tenantname>.sapbydesign.com
Authentication BasicAuthentication
Username User ID of your SAP ByD tenant
Password Password of your SAP ByD tenant



  1. Next, enter the relative URL of the OData API and press Test. ‘/sap/byd/odata/cust/v1/byd_product/$metadata’ is an example of relative URL of the OData API. If the result looks like in the screen shot below click Next.




  1. On the ‘Confirmation’ screen select ‘Generate Virtual Data Model Classes’ and click Finish. This will lead, among others, to the generation of service definitions (e.g. byd_product.json and byd_product.xml) in the folder WorkSpace/SalesOrderProcessing/srv/external/



Additionally Java virtual data model (VDM) classes are generated. These classes provide fluent and type-safe java APIs to build queries and access SAP ByD OData endpoints.




  1. Reference the remote service definition (SAP ByD OData API) to the service model by enhancing the file my-service.cds as shown below and save the changes. We will see how to add custom handler, that access data by calling SAP ByD OData APIs in the section Add Custom Logic.


using my.app from '../db/data-model';
using cust as cust_API_product from './external/csn/byd_product';
service Salesorderhandling {
@insertonly entity Salesorder as projection on app.Salesorder;
@insertonly entity Salesorderitem as projection on app.Salesorderitem{
*
} actions {
action CheckATP( ) returns String(255);
};
@cds.persistence.skip
Entity Products as projection on cust_API_product.Material {
key ObjectID as ObjectID,
InternalID as InternalID,
Description as Description
};

}

Add Custom Logic


The goal of this section is to implement a Java application that comprises custom handler that reads product data from the SAP ByD tenant using an OData API, a rudimentary Available to Promise check (ATP check), which allocates a random quantity between 0 and the requested quantity as confirmed quantity and a work around to write the sales order root instance key as a foreign key into sales order item instance.

The custom logic outlined below is very basic. For detailed information concerning the usage of SAP java libraries to add, among others, resilience and caching to custom logic, refer to the blog post SAP S/4HANA Cloud SDK.

  1. Add a Java class to query product data from the SAP ByD system

    • Right click on WorkSpace/SalesOrderProcessing/srv/src/main/java/my/company/ and select New→ Java Class.

    • On the next screen, populate the Name field with ProductRemoteHandler and click Next.

    • On the Confirmation screen, click Finish.

    • Go to the file WorkSpace/SalesOrderProcessing/srv/src/main/java/my/company/ ProductRemoteHandler.java and double click on the java class. In the next step we will add Java coding in the right pane.








    • Add the following coding to the Java class ProductRemoteHandler.




package my.company;

import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.sap.cloud.sdk.odatav2.connectivity.ODataException;
import com.sap.cloud.sdk.s4hana.connectivity.ErpConfigContext;
import com.sap.cloud.sdk.service.prov.api.operations.Query;
import com.sap.cloud.sdk.service.prov.api.request.QueryRequest;
import com.sap.cloud.sdk.service.prov.api.response.QueryResponse;
import com.sap.cloud.sdk.service.prov.api.response.ErrorResponse;

import vdm.services.DefaultBydProductService;
import vdm.namespaces.bydproduct.Material;

public class ProductRemoteHandler {
private static final Logger logger = LoggerFactory.getLogger(ProductRemoteHandler.class);
private static final String DESTINATION_NAME = "bydsystem";

@Query(serviceName = "Salesorderhandling", entity = "Products")
public QueryResponse queryProducts(QueryRequest qryRequest) {

try {
List<Material> products = new DefaultBydProductService()
.withServicePath("/sap/byd/odata/cust/v1/byd_product").getAllMaterial()
.select(Material.OBJECT_ID, Material.INTERNAL_ID, Material.DESCRIPTION)
.execute(new ErpConfigContext(DESTINATION_NAME));

QueryResponse queryResponse = QueryResponse.setSuccess().setData(products).response();
return queryResponse;
} catch (final ODataException e) {
logger.error("==> Exception calling backend OData V2 service for Query of Products: " + e.getMessage(), e);
logger.error("==> Exception calling backend additional data: " + e.getLocalizedMessage());
ErrorResponse errorResponse = ErrorResponse.getBuilder()
.setMessage("There is an error. Check the logs for the details.").setStatusCode(500).setCause(e)
.response();
QueryResponse queryResponse = QueryResponse.setError(errorResponse);
return queryResponse;
}

}

}

Note:

The destination (DESTINATION_NAME) used in this Java code is bound to the Java application in the section Add Destination Resource to Extension Application and Bind it to Java Module.

  1. Add a Java class ATPCheckHandler. We will implement in this class a simple product availability check.

    • Add the class ATPCheckHandler in the folder WorkSpace/SalesOrderProcessing/srv/src/main/java/my/company/.

    • Add the following coding to Java class ATPCheckHandler




package my.company;

import java.util.Map;
import java.util.Random;
import java.util.Arrays;

import com.sap.cloud.sdk.service.prov.api.exception.*;
import com.sap.cloud.sdk.service.prov.api.EntityData;
import com.sap.cloud.sdk.service.prov.api.DataSourceHandler;
import com.sap.cloud.sdk.service.prov.api.ExtensionHelper;
import com.sap.cloud.sdk.service.prov.api.annotations.Action;
import com.sap.cloud.sdk.service.prov.api.request.OperationRequest;
import com.sap.cloud.sdk.service.prov.api.response.OperationResponse;

public class ATPCheckHandler {

@Action(Name = "Salesorderitem_CheckATP", serviceName = "Salesorderhandling")
public OperationResponse checkATP(OperationRequest actionRequest, ExtensionHelper extensionHelper) {

Map<String, Object> keys = actionRequest.getParameters();
DataSourceHandler handler = extensionHelper.getHandler();

try {

// Get Item Data
EntityData entityData = handler.executeRead("Salesorderitem", keys,
Arrays.asList("itemID", "salesOrder_salesOrderID", "productID", "quantity", "quantityUOM",
"confirmedQuantity", "confirmedQuantityUOM", "price", "currency"));

// Calculate confirmed quantity (Random value between 0 and Requested Quantity)
double requestedQuantity = Double.parseDouble(entityData.getElementValue("quantity").toString());
double confirmedQuantity = new Random().nextInt((int) requestedQuantity + 1);
String confirmedQuantityUom = entityData.getElementValue("quantityUOM").toString();

// Write confirmed quantity
entityData = EntityData.getBuilder(entityData).removeElement("confirmedQuantity")
.addElement("confirmedQuantity", confirmedQuantity).removeElement("confirmedQuantityUOM")
.addElement("confirmedQuantityUom", confirmedQuantityUom).buildEntityData("Salesorderitem");
handler.executeUpdate(entityData, keys, false);

String result = entityData.getElementValue("salesOrder_salesOrderID").toString() + "-"
+ entityData.getElementValue("itemID").toString();
OperationResponse response = OperationResponse.setSuccess().setPrimitiveData(Arrays.asList(result))
.response();
;

return response;

} catch (DatasourceException e) {

return null;
}
}
}

Add UIs


In this section we will illustrate how to add SAP Fiori UIs to our simple extension application.

  1. Add HTML5 module

    • Right click on the project SalesOrderProcessing then select New → HTML5 Module.

    • On the following screen select the template List Report Application, then press Next.








    • On the next screen, populate the mandatory fields as shown in the screen shot below, and click Next.





    • On the Data Connection screen, choose a service from the source Current Project and click Next. Services could be language-specific, hence the list might contain several entries with the same content. In this case, select the first entry.





    • On the Annotation Selection screen, select the annotation file and then click Next.

    • On the next screen, bind your UI to the OData collection by selecting the OData collection Salesorder from the drop-down list.

    • Populate the field OData Navigation with the value Salesorderitem from the drop-down list.





    • Press Finish to complete the HTML5 creation process.





  1. Add UI Annotation


SAP Fiori UIs are defined by OData Annotations. In this section, we will see how to add UI annotations using CDS models. However, UI annotations can also be added using the annotation modeler of SAP Web IDE.






    • Right click on WorkSpace/SalesOrderProcessing/srv/ and select New → File

    • On the popup screen, enter the File Name ‘fiori-annotations.cds’ and press ok.

    • Go to the file WorkSpace/SalesOrderProcessing/srv/fiori-annotations.cds and double click the file.

    • Add the following code lines to the file fiori-annotations.cds.




using Salesorderhandling from './my-service';
annotate Salesorderhandling.Products with {
ObjectID
@Common.Label: 'Node ID'
@Common.FieldControl: #Hidden;
InternalID
@title : 'Product ID';
Description
@title : 'Description';
};

annotate Salesorderhandling.Products with @(

UI.Identification:
[ {$Type: 'UI.DataField', Value: InternalID},
{$Type: 'UI.DataField', Value: Description},
]
);

annotate Salesorderhandling.Salesorder with {
salesOrderID
@Common.Label : 'Sales Order ID';
customer
@Common.Label : 'Customer';
orderDate
@Common.Label : 'Ordered On';
};
annotate Salesorderhandling.Salesorder with @(
UI.LineItem: [
{$Type: 'UI.DataField', Value: salesOrderID},
{$Type: 'UI.DataField', Value: customer},
{$Type: 'UI.DataField', Value: orderDate},
],

UI.HeaderInfo: {
Title: { Value: salesOrderID },
TypeName:'Sales Order',
TypeNamePlural:'Sales Orders'
},

UI.Identification:
[
{$Type: 'UI.DataField', Value: salesOrderID},
{$Type: 'UI.DataField', Value: customer},
{$Type: 'UI.DataField', Value: orderDate}
],

UI.Facets:
[
{
$Type:'UI.CollectionFacet',
Facets: [
{ $Type:'UI.ReferenceFacet', Label: 'General Info', Target: '@UI.Identification' }
],
Label:'Order Details',
},
{$Type:'UI.ReferenceFacet', Label: 'Sales Order Items', Target: 'Salesorderitem/@UI.LineItem'},
]
);

annotate Salesorderhandling.Salesorderitem with {

itemID
@Common.Label: 'Item ID';
productID
@Common.Label : 'Product ID'
@sap.value.list: 'Products'
@Common.ValueList: {
entity: 'Products',
Label: 'Products',
SearchSupported: 'true',
Parameters: [
{ $Type: 'Common.ValueListParameterInOut', LocalDataProperty: 'productID', ValueListProperty: 'InternalID', Label: 'Product ID' },
{ $Type: 'Common.ValueListParameterInOut', LocalDataProperty: 'productDescription', ValueListProperty: 'Description', Label: 'Description'},
]
};
productDescription
@Common.Label : 'Product Description'
@Common.FieldControl: #ReadOnly;
quantity
@Common.Label: 'Quantity ';
quantityUOM
@Common.Label: 'Quantity UoM ';
confirmedQuantity
@Common.Label: 'Confirmed Quantity '
@Common.FieldControl: #ReadOnly;
confirmedQuantityUOM
@Common.Label: 'Confirmed Quantity UoM '
@Common.FieldControl: #ReadOnly;
price
@Common.Label: 'Price';
currency
@Common.Label: 'Currency';
};

annotate Salesorderhandling.Salesorderitem with @(
UI.LineItem: [
{$Type: 'UI.DataFieldForAction', Action: 'Salesorderhandling.EntityContainer/Salesorderitem_CheckATP', Label: 'Check ATP', Visible, Enabled},
{$Type: 'UI.DataField', Value: salesOrder_salesOrderID },
{$Type: 'UI.DataField', Value: itemID},
{$Type: 'UI.DataField', Value: productID},
{$Type: 'UI.DataField', Value: productDescription},
{$Type: 'UI.DataField', Value: quantity},
{$Type: 'UI.DataField', Value: quantityUOM},
{$Type: 'UI.DataField', Value: price},
{$Type: 'UI.DataField', Value: currency},
{$Type: 'UI.DataField', Value: confirmedQuantity},
{$Type: 'UI.DataField', Value: confirmedQuantityUOM},

],

UI.HeaderInfo: {
Title: { Value: itemID },
TypeName:'Sales Order Item',
TypeNamePlural:'Sales Order Items'
},

UI.Facets: [
{
$Type:'UI.CollectionFacet',
Facets:[
{ $Type: 'UI.ReferenceFacet', Label: 'General Info', Target: '@UI.FieldGroup#GeneralInfo' },
{ $Type: 'UI.ReferenceFacet', Label: 'Quantity Info', Target: '@UI.FieldGroup#Quantity' },
{ $Type: 'UI.ReferenceFacet', Label: 'Price Info', Target: '@UI.FieldGroup#Price' },
],
Label:'Item Details',
}
],
UI.FieldGroup#GeneralInfo: {
Label: 'General Info',
Data: [
{$Type: 'UI.DataField', Value: salesOrder_salesOrderID },
{$Type: 'UI.DataField', Value: itemID},
{$Type: 'UI.DataField', Value: productID},
{$Type: 'UI.DataField', Value: productDescription}
]
},

UI.FieldGroup#Quantity: {
Label: 'Quantity Info',
Data: [
{$Type: 'UI.DataField', Value: quantity},
{$Type: 'UI.DataField', Value: quantityUOM},
{$Type: 'UI.DataField', Value: confirmedQuantity},
{$Type: 'UI.DataField', Value: confirmedQuantityUOM},
]
},

UI.FieldGroup#Price: {
Label: 'Price Info',
Data: [
{$Type: 'UI.DataField', Value: price},
{$Type: 'UI.DataField', Value: currency},
]
},

);

Add Destination Resource to Extension Application and Bind it to Java Module


In this section, we will see how the HTTP destination that we have created in the blog post Configuring Connectivity Between SAP CP and SAP ByD with Single Sign-On and Principal Propagation is consumed in the extension application.

  1. Go to the file WorkSpace/SalesOrderProcessing/mta.yaml and double click the file.

  2. Add the following lines under the Resource section of the mta.yaml.




  1. Bind the destination resource ‘bydsystem’ to the Java module as shown below:



Build and Deploy



  1. Deploy data model

    • Right click on the project SalesOrderProcessing then select Build → Build CDS. This will lead to the regeneration of OData EDMX metadata xml files.

    • Right click on the project WorkSpace/SalesOrderProcessing/db then select Build → Build. This will lead to the deployment of the data model to the HANA DB.

    • After successful deployment of the data model to the HANA DB, follow the steps below to see the data model deployment in the Database Explorer.

      • Click on Database Explorer.

      • Add the database to the database explorer by clicking on the plus sign (+) in the right pane.












      • On the popup screen, select the entry under HDI Containers and press OK.

      • Next, double click on Tables in the content pane of the database explorer.

      • Afterwards, click on the DB table instances listed under the content pane to display details of the DB tables.








  1. Deploy Java Application

    • Right click on the project WorkSpace/SalesOrderProcessing/srv then select Run → Java Application. This will start the deployment of the Java backend application.

    • Once the deployment of the Java application is complete, click on the URL on the upper part of the run console to test the application.








    • On the following screen click on the OData endpoints and add /$metadata to the URL to display the metadata of the endpoint.






  1. Deploy the UI Application

    • Right click on the project WorkSpace/SalesOrderProcessing/Sales_Order_App then select Run → Run as Web Application. This will start the deployment of the UI Application. If you are running the application for the first time, you will be requested to choose between flpSandBox and flpSandboxMockServer. Select flpSandBox and press OK.

    • Once the deployment is complete, a launch pad with the Sales_Order_App tile as shown below will appear.








    • Double click on the tile Sales_Order_App to launch the application.

    • On the following screen, you can start testing the capabilities of the application that you have developed.







Conclusion


The overall development experience and efficiency of building and deploying cloud-native SAP ByD extension applications in SAP CP Cloud Foundry environment can be tremendously improved by applying the SAP Cloud Application Programming Model.  The SAP Cloud Application Programming Model offers a tool set and methodology that helps developers to focus on their domain logic, while building and deploying SAP ByD extension applications in SAP Cloud Platform. The blog post demonstrates how the Core Data & Services (CDS) can be used to simplify the modeling and generation of the persistence layer, the application service layer and the UI layer.  In addition, it exemplifies the ease of consumption of SAP ByD OData APIs by using the SAP S/4HANA Cloud SDK libraries and the virtual data model. Moreover, it shows the ease of consumption of platform services such as connectivity and security services in the ByD extension application.

Further Reading


Follow the link below to go to the next blog post in the blog post series:

SAP Business ByDesign Side-by-Side Extensions on SAP Cloud Platform: Consumption of SAP Business ByD...

Follow the link below to go the overview blog post of the blog post series:

SAP Business ByDesign Side-by-Side Extensions on SAP Cloud Platform