Implementing Service Layer’s Script Engine
It’s been a while since the Service Layer has been enhanced with a new feature called Script Engine, released together with the SAP Business One, version for SAP HANA 9.2 PL04. I’m sure you have already read the relevant blog posts talking about it from Trinidad here and Andy Bai here, as well as the SAP Note 2343691 – Service Layer supports script engine.
As you’ve learned in those posts, there are many benefits of using script engine such as better performance, as it reduces the calls from your application to the Service Layer; re-use of server-side customizations; and the implementation of global transaction for several operations.
Now that you are already familiar with the feature, let me guide you through two very simple examples on how to implement it:
Example A – Creating a Sales Order
So, here is the scenario: before creating a Sales Order, you want to validate whether the customer already exists in the system. If it doesn’t exist, then create it before the sales order, if it already does, just create the sales order. In a world without Script Engine (before 9.2 PL04), your application would have to do at least two requests to the Service Layer (GET BusinessPartners(id), and POST Orders). Since 9.2 PL04, you can do a single request to your own custom script (at the script engine level) in the Service Layer and it will threat other calls internally.
In this example I’m interested in demonstrate the use of global transactions and how to call a Service Layer entity from a custom script, using the script engine feature.
Here is all you’ll need: one javascript and one xml, packed together in a zip file.
The steps required to accomplish this development is described below:
- Create a javaScript file which will threat a POST request and perform two calls: one GET request to validate the business partner and a POST request to add the sales order to the given business partner;
- Create an xml file with the ARD extension, where you’ll basically reference your javaScript file as part of the Service Layer, as well as some other data such as partner name and namespace so that Business One can install it in the right place during the deployment of your script;
- Create a zip file containing both ARD and JS files above; – NOTE: the zip file must contain only the ARD and JS files, no folders/sub-folders or any other file.
- Run the deployment of this script by importing the zip file using the Extension Manager; and
- Test the new script calling it from a REST client.
Let’s now run it step-by-step.
1. Create a javaScript file, name it as you wish using the js extension, in this case it will be SalesOrders.js. Just open your favorite IDE and write the code below, note that it is fully commented:
// required to handle http calls
var http = require('HttpModule.js');
// required to call SL entities/actions
var ServiceLayerContext = require('ServiceLayerContext.js');
function POST() {
var ret = {};
// Instantiates SL context
var slContext = new ServiceLayerContext();
// Gets the body of the request
var salesOrderData = http.request.getJsonObj();
// Starting point for the Global Transaction
slContext.startTransaction();
//Gets Business Partner data
var res = slContext.BusinessPartners.get(salesOrderData.CardCode);
//If it doesn't exist then create it
if (res.status != 200){
var res = slContext.BusinessPartners.add(
{
"CardCode": salesOrderData.CardCode,
"CardName": "BP created using SL Script Engine",
"CardType": "C"
}
);
// If there's any problem creating Business Partner,
// roll back and throw the exception
if (!res.isOK()) {
slContext.rollbackTransaction();
throw http.ScriptException(http.HttpStatus.HTTP_BAD_REQUEST,
res.getErrMsg());
}
}
// This is to store and send back business partner details
ret.BusinessPartnerDetail = res;
//Create Sales Order
var res = slContext.Orders.add(salesOrderData);
// If there's any problem creating the Sales Order,
// roll back and throw the exception
if (!res.isOK()) {
slContext.rollbackTransaction();
throw http.ScriptException(http.HttpStatus.HTTP_BAD_REQUEST,
res.getErrMsg());
}
// This is to store and send back sales order details
ret.SalesOrderDetail = res;
//Everything went smooth then commit!
slContext.commitTransaction();
/* SET HTTP RESPONSE */
http.response.setContentType(http.ContentType.APPLICATION_JSON);
http.response.setStatus(http.HttpStatus.HTTP_OK);
http.response.setContent(ret);
http.response.send();
}
2. Now create an XML file, give it the same name as the previous javascript just changing the extension to ard, in this case SalesOrders.ard. Fill in the Partner Namespace, Partner Name, Script name and File Name, as you can see below:
<?xml version="1.0" encoding="utf-8"?>
<AddOnRegData xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
SlientInstallation=""
SlientUpgrade=""
Partnernmsp="saponenmsp"
SchemaVersion="3.0"
Type="ServiceLayerScript"
OnDemand=""
OnPremise=""
ExtName=""
ExtVersion="1.00"
Contdata=""
Partner="sapone"
DBType="HANA"
ClientType="S">
<ServiceLayerScripts>
<Script Name="SalesOrders" FileName="SalesOrders.js"></Script>
</ServiceLayerScripts>
<XApps>
<XApp Name="" Path="" FileName="" />
</XApps>
</AddOnRegData>
Note that this will define the URL you will call in your application, it is https://<hanaserver>:50000/b1s/v1/script/<Partner>/<Script Name>.
In this case, it will be a POST to the URL https://<hanaserver>:50000/b1s/v1/script/sapone/SalesOrders
3. Now generate a standard zip file containing both xml and js file above.
4. Open the Extension Manager at https://<hanaserver>:40000/ExtensionManager, select “Extensions” > “Import” > “Browse” > select the zip file and finish the wizard. Then assign the extension to a specific company: select “Company Assignment” > “Assign” > select the extension and finish the wizard.
5. Open your favorite REST client and test it by running a POST to the Login entity:
And a POST to the URL https://<hanaserver>:50000/b1s/v1/script/<Partner>/<Name> using the body such as the one below:
You should get a json object containing the business partner details and the sales order details.
Side note: there’s no mechanism in place at this moment to debug this kind of script in the server-side, but you can use the console.log command to keep track of the variables or steps you want to watch. The log files are recorded in the server side, under the path /<SAPBusinessOne Installation folder>/ServiceLayer/logs/script/, in this case /usr/sap/SAPBusinessOne/ServiceLayer/logs/script:
Example B – Identifying the current user logged in
It might be very useful to identify the current session properties such as the logged in user and company, mainly when there are many requests to the same script and you want to identify and log each call, for any reason.
While there is no standard Service Layer entity to do that so far, you can use the following script, but please note that this is a provisory solution – a new interface might be available in a future patch level.
var http = require('HttpModule.js');
function GET() {
var ret = {};
/* GET CURRENT SESSION PROPERTIES */
ret.user = session.getProperty('user');
ret.company = session.getProperty('company');
ret.version = session.getProperty('version');
ret.maxOperationsPerTransaction = session.getProperty('maxOperationsPerTransaction');
ret.server = session.getProperty('server');
ret.timeout = session.getProperty('timeout');
/* SET RESPONSE */
http.response.setContentType(http.ContentType.APPLICATION_JSON);
http.response.setStatus(http.HttpStatus.HTTP_OK);
http.response.setContent(ret);
http.response.send();
}
This is the call to the above script and the json returned:
Thanks to @Andy Bai for providing the information on how to get current session properties, and stay tuned to this blog where we will provide more details when available.
Hope you can enhance your apps by using all the power of the service layer. Enjoy!
Hello Thiago,
Is possible to cancel a payment with Service Layer Script Engine?, because in ServiceLayerContext.js only are the following functions "get","add", "update" and "remove".
Thanks
Hi Jorge,
Unfortunately the script engine does not support actions such as cancel/close.
Regards,
Thiago.
Dear Thiago Mendes,
You know update SSL for service layer SAP B1 (SQL), i find some one SAP note but it working for B1(hana), i try reinstall service layer but it not active ssl for Service layer. Please help hep me, if you have document or knowledge about it.
Hello,
Try to add this this, but no luck. We working with 9.3 PL0.
The test is working: /b1s/v1/script/test/test_items(‘ACM_101’)
But the new created is not working, but it is succeed importing in the Extension Manager.
also restart b1s and the servertools.
GET /b1s/v1/script/asecom/teun
Response:
Thiago Mendes Do you have some working examples, that we can ensure that we did nothing wrong.
Thanks
UPDATE: It's a bug in SAP 9.3 PL0
Hi,
I have import .zip by extension manager(9.2 PL0.8) and got the below error. Any setting problem?
Thanks!
Hi Amy,
Work with the console.log and check the log files to trace the error.
Regards,
Thiago.
Is this Issue resolved for you ??
Shouldn't there be a var BusinessPartner = require('EntityType/BusinessPartner.js'); in SalesOrders.js?
Hi Nils,
In this example I'm working with the json directly.
If you prefer to instantiate a variable to work with the Business Partner's properties than yes, declare the variable for it and instead of the json you can use the variable itself.
Regards,
Thiago.
is it possible to call stored procedure, view or query from database and add to response content?
Hi Fernando,
No, that's not possible.
Alternatively you can write an xsjs and implement that on HANA XSEngine.
Please, check this playlist for more information.
Regards,
Thiago.
Dear Fernando
Service Layer has a new Semantic Layer from which you can use this kind of stuff, its only available from 9.3 PL02 onwards
https://blogs.sap.com/2018/02/01/new-sap-business-one-semantic-layer-view-exposure/
Hope it helps
Regards
Jose
Hi Thiago,
Is there a way to replace the scipt files (xxxx.js) direclty on the linux server to avoid numerous uploads of the package via the SLD during development? Where to find the location?
Hi Cornee,
Yeap! You can place your script (e.g. boorsma.js) under /usr/sap/SAPBusinessOne/ServiceLayer/scripts/test and call it using /b1s/v1/script/test/boorsma.
Regards,
Thiago.
Dear Thiago
I'm having issues implementing this type of solution in Cloud environments, I get an "Error parsing ard file" upon installation. This is only for on-premise installations? or I need to do something else to the ard? I test it in on-premise and works great
Hoping to hear from you
Best Regards
Jose
Hello Jose,
Unfortunately the Script Engine is not supported in SAP Business One Cloud environments:
https://launchpad.support.sap.com/#/notes/1855972
Regards,
Thiago.
Oh, that's some really bad news, this feature it's super useful for integrations with 3rd party software, hope it will be available soon
Thank you very much for your quick response Thiago, as always it was on point
Regards
Jose
Hi Thiago,
Is there a forecast for this to be avaliable in CCC?
Regards,
Rodrigo
No that I am aware of.
Hello Thiago
If we need to do a transaction with more than 10 operations,is there a way to change the "maxOperationsPerTransaction" property ? or do you have an idea to do more than 10 operations with the Script Engine ?
Regards,
Romaric
Hi Romaric,
Yes, there’s a way, but we do not recommend as it might impact on performance.
However, you may change it at your own risk.
Open the file b1s.conf and add (or change, if it already exists) the entry MaxOperationsPerTransaction:10.
Best,
Thiago.
Hello.
I am trying to implement this fix, but it is not having any effects.
Does this work on SAP 9.2? Is a restart required?
Thank you
Edit: Silly me. It works.
Note: updating this does not update the number returned by
session.getProperty('maxOperationsPerTransaction')
Hi Thiago,
regarding this option: is it possible in some way (I'm referring to the latest version) to set MaxOperationsPerTransaction "by session", for example as an header value or similar?
It could be a good thing imho, because it could be acceptable a big number of operation per transaction if, for example, I had to update some values in an UDT or similar operations (and I want to have the table completly update or nothing).
Regards.
Dear Thiago Mendes
I’m using Service Layer Script Engine to perform an integration with complex transactions but hit a big problem in the process, seems that the create function that parses the incoming JSON fails to parse UDF
It does not matter if the UDF are from the document header or its lines the result is the same, fails to add it to the new object and therefore the document its created without them.
I saw some questions around the forum regarding the same problem but with no solutions yet, are you aware of some kind of workaround to this? Trying to assign the values by property name dosn’t work either
Hope you can help us
Regards
Jose
Hi Jose,
I have just tested with the UDF in the Sales Orders header and it works perfectly (test performed on 9.3 PL04):
Saludos,
Thiago
Dear Thiago thank you for your help
At the end we sorted out by removing this from our code
That method ignore UDF so your approach of passing the JSON directly to the add method is the only efficient way to do it
Best Regards
José
Hi, i'm having the error: missing ) after argument list
Full error:
{ "code": 511, "message": { "lang": "en-us", "value": "Script error: compile error [SyntaxError: missing ) after argument list]." } }
i think that as an error with enviroment, because after any change occour the same error.
Anyone pass for thus problem?