Skip to Content
0
Aug 21, 2018 at 10:08 AM

B1 Service Layer Script Engine extensions development issues

610 Views Last edit Aug 21, 2018 at 12:03 PM 2 rev

Dear Experts,

Developing SE extensions I often face errors I don't really understand why. I have examined all documentation I could have found and spent a lot of time googling, but some of points are still not clear for me. I have prepared several examples, so, I hope community (or even someone from SAP support or solution architects team) can clarify the reasons of that behaviour. All of this examples were tested on SAP B1 9.3 PL 03

Example 1:

That is probably the most weird issue for me. In all examples on the forum and in the documentation you can find the following row at the beginning of your script:

var ServiceLayerContext = require('ServiceLayerContext.js');

After you just need to instantiate this object and it works fine. And so does the following:

var slContext = require('ServiceLayerContext.js');

But if I call this variable "serviceLayerContext" I get the weird error after instantiating and calling a method "startTransaction()": Script error: call function 'POST' failure [TypeError: this.contextHandle.startTransaction is not a function].

Here is my code where this error is reproduced:

var http = require('HttpModule.js'); 
// Require with the name different from ServiceLayerContext
var serviceLayerContext = require('ServiceLayerContext.js');

function GET()
{
}

function DELETE() {
}

function PATCH() {
}

function POST()
{
    // Instantiate
    var slObj = new serviceLayerContext();

    // Start transaction
    slObj.startTransaction();
    // Rollback immediately
    if (slObj.isInTransaction()) slObj.rollbackTransaction();

    /* SET HTTP RESPONSE */
    http.response.setContentType(http.ContentType.APPLICATION_JSON);
    http.response.setStatus(http.HttpStatus.HTTP_OK);
    http.response.setContent({status: 'success'});
    http.response.send();
}

If I call this script I get the following response body:

{
    "error": {
        "code": 512,
        "message": {
            "lang": "en-us",
            "value": "Script error: call function 'POST' failure [TypeError: this.contextHandle.startTransaction is not a function]."
        }
    }
}

If I rename serviceLayerContext context to ServiceLayerContext (or slContext for example) everything goes smooth.

Is that expected behaviour or I do anything wrong?

Example 2:

In all examples on the forum and in the documentation only entry point functions are used. Is there a limitation to write additional function in uploaded JS files? Cause I face unexpected behaviour (sometimes it forces an error, sometimes not) if I write a function after method functions. Mostly this errors about compilation and unexpected tokens (I got errors about all tokens including ILLEGAL)

Here is a code example where issue is reproduced:

var http = require('HttpModule.js'); 

function GET() {
}

function PATCH() {
}

function DELETE() {
}

function POST() {
    // Test object to log
    var obj =
    {
	Property1: 1,
	Property2: true,
	Property3: 'Test',
	Property4: function (value) { return true; },
	Property5: function (value) { return true; },
	Property6: function (value) { return true; },
	Property7: function (value) { return true; },
	Property8: function (value) { return true; },
	Property9: function (value) { return true; },
	Property10: function (value) { return true; },
	Property11: function (value) { return true; },
	Property12: function (value) { return true; },
	Property13: function (value) { return true; }
    };
    
    logObject('Test', obj);
	
    /* SET HTTP RESPONSE */
    http.response.setContentType(http.ContentType.APPLICATION_JSON);
    http.response.setStatus(http.HttpStatus.HTTP_OK);
    http.response.setContent({result: 'success'});
    http.response.send();
}

// Logs object in log files. If property is a function - logs it as 'Func'
function logObject(objName, obj)
{
	var replacer = function (key, value)
	{
		// Get 'Func' value if the property is a function
		if (typeof value === 'function')
		{
			return 'Func';  
		}
		return value;
	};


	var stringToLog = '';
	try
	{
		stringToLog = JSON.stringify(obj, replacer, 2);
	}
	catch (exc)
	{
		stringToLog = 'Cannot serialize object';
	}


	// Log string
	console.log(objName + ': ' + stringToLog);
}


If I call this script I get the following response:

{
    "error": {
        "code": 511,
        "message": {
            "lang": "en-us",
            "value": "Script error: compile error [SyntaxError: Unexpected identifier]."
        }
    }
}

If I move logic of the function inside POST function everything goes smooth.

Example 3:

Probably this error is about functions and variables out of entry points but I was really surprised with the results. Here is my script text:

// Require necessary entities
// Required to handle http calls
var http = require('HttpModule.js'); 
// Required to call SL entities/actions
var ServiceLayerContext = require('ServiceLayerContext.js');
// Entity proxy objects
var Customer = require('EntityType/BusinessPartner');

// Result Object
var result = { 
	IsSuccess: true,
	ErrorMsg: '',
	BookingSapCode: '',
	CustomerSapCode: ''
};

// Validator for an input object
var inputValidator = 
{
	BookingId: function (value) { return true; },
	BookingSapCode: function (value) { return true; },
	IsCanceled: function (value) { return true; },
	Date: function (value) { return isNaN(Date.parse(value)); },
	Email: function (value) { return true; },
	Phone: function (value) { return true; },
	CustomerId: function (value) { return true; },
	Title: function (value) { return true; },
	Name: function (value) { return true; },
	Surname: function (value) { return true; },
	CustomerSapCode: function (value) { return true; },
	CustomerSapSeries: function (value) { return true; },
	Details: function (value)
	{
		// Check if it's an array
		if (!Array.isArray(value)) return false;

		// Instantiate SL Context
		var slContext = new ServiceLayerContext();

		// For each ItemCode check if this item exists
		value.forEach(function (element)
		{
			if (!checkItemCode(element.SapItemCode, slContext)) return false;
		});

		return true;
	}
};

// ====================================	ENTRY POINTS ================================================== //

function GET() {

}

function DELETE() {

}


function PATCH() {

}


function POST() {
	console.log('Execution started');

	// Get the body of the request
	logObject('val', inputValidator);
	var inputObject = getBody(inputValidator);
	logObject('inputObj', inputObject); // Log input object to check data

    /* SET HTTP RESPONSE */
    http.response.setContentType(http.ContentType.APPLICATION_JSON);
    http.response.setStatus(http.HttpStatus.HTTP_OK);
    http.response.setContent(result);
    http.response.send();
}


// ====================================	PRIVATE FUNCTIONS ================================================== //


function checkItemCode(itemCode, slContext)
{
	return true;
}


// ====================================	COMMON FUNCTIONS ================================================== //


// Gets body and validates structure via 'validator' passed. 
// Validator presents all properties as validation functions
function getBody(validator)
{
	// Try to get data from the body
	var jsonObj = http.request.getJsonObj();
	if (!jsonObj) 
	{
        throw http.ScriptException(http.HttpStatus.HTTP_BAD_REQUEST, "Fail to get the content of JSON format from the request payload");
	}


	// Only if there is the variable
    if (typeof validator !== 'undefined')
    {
    	var errors = validateObject(jsonObj, validator);
		logObject('errors', errors);
    	if (errors.length > 0)
    	{
			throw http.ScriptException(http.HttpStatus.HTTP_BAD_REQUEST, "Input object is invalid: " + errors.join(' | '));
    	}
    }


	// Return the deserialized body
	return jsonObj;
}


// Validates object by it's schema
function validateObject(object, schema) {
	var errors = Object.keys(schema)
		.map(function (property) 
		{
			var validator = {}; // Validation function for a property
			var isMissing = false;


			if (object[property] === undefined) // If object doesn't have this property - function always returns false
			{
				validator = function (value) { return false };
				isMissing = true;
			}
			else
			{
				validator = schema[property]; // Get validation function
			}


			return { property: property, isValid: validator(object[property]), isMissing: isMissing };
		})
		.reduce(function (errors, propertyCheck) 
		{
			if (propertyCheck.isValid === false) 
			{
				errors.push(propertyCheck.property + (propertyCheck.isMissing ? " is missing." : " is invalid."));
			}
    
			return errors;
		}, []);
	return errors;
}


// Logs object in {SL Installation Path}/logs/script/*
function logObject(name, obj)
{
	var replacer = (key, value) => {  
		// If we get a function, give us the code for that function  
		if (typeof value === 'function')
		{
			return 'Func';  
		}
		return value;
	};

	// Check if there is no serialization errors
	var stringToLog = '';
	try
	{
		stringToLog = name + ': ' + JSON.stringify(obj, replacer, 2);
	}
	catch (exc)
	{
		stringToLog = name + ': cannot be serialized - ' + exc;
	}

	// Log string
	console.log(stringToLog);
}

So, the idea is that I want to verify my request body. To do that I declare a blueprint object with methods instead of properties. This methods check the data of my request object.

But, I always get exceptions that some of fields are missing. Here is my reply:

{
    "error": {
        "code": 600,
        "message": {
            "lang": "en-us",
            "value": "Input object is invalid: CustomerSapSereries is missing."
        }
    }
}

But I don't have CustomerSapSereries property in my schema object. So, during the tests, when I changed some pieces of code (In my humble opinion unrelated to this issue) I got some of properties renamed (that was Titltle instead of Title and CustomerSerieses instead of CustomerSeries). That really confuses me and I would appreciate any clarifications of reasons of the following behaviour.

Update:

I guess that the problem with serviceLayerContext is because this variable is used in engine.

function getContextHandle(){
        return serviceLayerContext;
    }