Atlassian uses cookies to improve your browsing experience, perform analytics and research, and conduct advertising. Accept all cookies to indicate that you agree to our use of cookies on your device.
Atlassian uses cookies to improve your browsing experience, perform analytics and research, and conduct advertising. Accept all cookies to indicate that you agree to our use of cookies on your device. Atlassian cookies and tracking notice, (opens new window)
Business Rule: Start ToC's – User & Configuration Guide
Purpose: Documents the ServiceNow Business Rule that starts the SAP Transport of Copies (ToC) and the two Script Includes it depends on (TMUtils, TMAction) – including setup steps, the full scripts, and every comment/message the rule can produce.
Updated on Jul 14, 2026: Page restructured for setup order. New TMUtils Script Include added; TMAction updated to call global.TMUtils; the previous "All application scopes" step is no longer required. The earlier version is available via the page history.
1. Overview
The Start ToCs Business Rule triggers a SAP Transport of Copies when a Change Request is moved to the configured target status (customer-specific; see section 3.1). It calls global.TMAction.startToC(current), which sends the SAP OData entity call TOC_StartSet through the RTC/TM gateway service.
Two Script Includes must exist before the rule works (see section 2): TMUtils (the SAP connection, settings and OData layer) and TMAction (the action layer the rule calls).
The rule reports back to the user in two ways: an activity-log comment on the record (always), and – for real errors – a red error banner on the form plus a cancelled state change. QA checks (checkQAS) are intentionally excluded in the current version (see section 5).
2. Prerequisites
Set these up before creating the Business Rule, in this order. The dependency chain is Business Rule → TMAction → TMUtils, so build from the bottom up.
2.1 TMUtils Script Include
TMAction calls new global.TMUtils(), so create this Script Include first. It holds the SAP settings/features lookup and the OData read/write layer (getSAPTRData, setSAPTRData).
Create the Script Include – go to All → System Definition → Script Include and create New in the Global application with:
The Business Rule calls new global.TMAction(). TMAction in turn uses global.TMUtils (from 2.1), so create it after TMUtils.
Create the Script Include – go to All → System Definition → Script Include and create New in the Global application with:
Field
Value
Field
Value
Name
TMAction
API Name
global.TMAction (set automatically)
Application
Global
Accessible from
This application scope only
Client callable
No
Active
Yes
📷 Screenshot: TMAction Script Include form –
Add the following code to the Script field:
var TMAction = Class.create();
TMAction.prototype = {
initialize: function() {
},
type: 'TMAction',
approveRequests: function(current, approvalConfig){
let taskId = "" + current.sys_id;
let taskNumber = "" + current.number;
let type = current.getRecordClassName();
//get controller
let tmUtils = new global.TMUtils();
let tmSettingsDetails = tmUtils.getTMSettingsForType(type);
if(tmSettingsDetails.tmEnabled){
let tmServerValue = tmSettingsDetails.tmServer;
let connectDetails = {
"tmServer": tmServerValue,
"loginAlias": tmSettingsDetails.loginAlias || "/sap/bc/gui/sap/its/webgui",
"logoffAlias": tmSettingsDetails.logoffAlias || "/sap/public/bc/icf/logoff",
"sapUserLogin": tmSettingsDetails.sapUserLogin,
"rtcHeaders": tmSettingsDetails.rtcHeaders,
"path": tmSettingsDetails.rtcAlias || "/sap/opu/odata/RTC/TM_GW_SRV",
"sapId": type,
"ExternalUser": gs.getUserName()
};
if (tmSettingsDetails.systemAlias) {
connectDetails["path"] = connectDetails["path"] + ";o=" + tmSettingsDetails.systemAlias;
}
let tmFeatures = tmUtils.getTMFeaturesForType(type);
let itsmName = tmFeatures["creation"]["itsmName"] || "";
let entityCall = approvalConfig.approve == 'A' ? "massApproval" : "massRevokal";
approvalConfig.group = approvalConfig.group || "";
let requestNumbers = this.getSAPTrsForApproval(taskId, approvalConfig);
let requestBody = {
"requests": `(Trkorr='${requestNumbers}')`,
"body": {
"Approve": approvalConfig.approve,
"ApprovalType": approvalConfig.type,
"LevelName": approvalConfig.levelName
}
};
if (approvalConfig.group) {
requestBody.body['Usrgroup'] = approvalConfig.group;
}
let response = tmUtils.setSAPTRData(JSON.stringify(connectDetails), entityCall, JSON.stringify(requestBody));
return response;
}
},
checkQAS: function(current){
let taskId = current.sys_id;
let type = current.getRecordClassName();
//get controller
let tmUtils = new global.TMUtils();
let tmSettingsDetails = tmUtils.getTMSettingsForType(type);
if(tmSettingsDetails.tmEnabled){
var tmServerValue = tmSettingsDetails.tmServer;
//call get SAP QAS
let trId = this.getSAPTrs(taskId);
let query = "?$expand=NavToCritObj,NavToSpecialAspects&$filter=Trkorr" + encodeURIComponent(" eq'" + trId +"' and Toc eq 'X'");
let connectDetails = {
"tmServer": tmServerValue,
"loginAlias": tmSettingsDetails.loginAlias || "/sap/bc/gui/sap/its/webgui",
"logoffAlias": tmSettingsDetails.logoffAlias || "/sap/public/bc/icf/logoff",
"sapUserLogin": tmSettingsDetails.sapUserLogin,
"rtcHeaders": tmSettingsDetails.rtcHeaders,
"path": tmSettingsDetails.rtcAlias || "/sap/opu/odata/RTC/TM_GW_SRV",
"sapId": type,
"ExternalUser": gs.getUserName()
};
if (tmSettingsDetails.systemAlias) {
connectDetails["path"] = connectDetails["path"] + ";o=" + tmSettingsDetails.systemAlias;
}
let request = {"entityCall": "QA_TM_TR_RELSet", "query": query, "connectDetails": connectDetails};
let response = tmUtils.getSAPTRData(JSON.stringify(request));
return response;
}
},
addComment: function(current, text){
let taskId = current.sys_id;
let type = current.getRecordClassName();
let task = new GlideRecordSecure(type);
if (task.get(taskId)) {
task.comments = text;
task.update();
}
},
startToC: function(current){
let taskId = "" + current.sys_id;
let taskNumber = "" + current.number;
let type = current.getRecordClassName();
//get controller
let tmUtils = new global.TMUtils();
let tmSettingsDetails = tmUtils.getTMSettingsForType(type);
if(tmSettingsDetails.tmEnabled){
let tmServerValue = tmSettingsDetails.tmServer;
let connectDetails = {
"tmServer": tmServerValue,
"loginAlias": tmSettingsDetails.loginAlias || "/sap/bc/gui/sap/its/webgui",
"logoffAlias": tmSettingsDetails.logoffAlias || "/sap/public/bc/icf/logoff",
"sapUserLogin": tmSettingsDetails.sapUserLogin,
"rtcHeaders": tmSettingsDetails.rtcHeaders,
"path": tmSettingsDetails.rtcAlias || "/sap/opu/odata/RTC/TM_GW_SRV",
"sapId": type,
"ExternalUser": gs.getUserName()
};
if (tmSettingsDetails.systemAlias) {
connectDetails["path"] = connectDetails["path"] + ";o=" + tmSettingsDetails.systemAlias;
}
let tmFeatures = tmUtils.getTMFeaturesForType(type);
let itsmName = tmFeatures["creation"]["itsmName"] || "";
let bodyReq = {
"ItsmId": itsmName,
"TicketId": taskNumber,
"TicketGuid": taskId,
"Trkorr": ""
};
let response = tmUtils.setSAPTRData(JSON.stringify(connectDetails), "TOC_StartSet", JSON.stringify(bodyReq));
return response;
}
},
checkAllImported: function(current, level, state){
let sapId = "" + current.sys_id;
//check SAP transports import
let imported = false;
let reqTable = new GlideRecordSecure('x_real3_app1_sap_transports');
reqTable.get(sapId);
let taskId = reqTable.item_sys_id;
let reqTable2 = new GlideRecordSecure('x_real3_app1_sap_transports');
reqTable2.addQuery('item_sys_id', taskId);
reqTable2.addQuery('type_technical', "T");
reqTable2.query();
while(reqTable2.next()){
let imports = reqTable2.getValue("imports") || [];
imports = typeof imports == "string" ? JSON.parse(imports) : imports;
for(let i = 0; i < imports.length; i++){
if(imports[i]["TM_LEVEL"] == level){
let rc = "" + imports[i]["RETURN_CODE"];
rc = rc.trim();
imported = rc.length > 0;
}
}
}
gs.info("IMPORTED end:" +imported);
//update incident
if(imported){
gs.info("UPDATED");
let gr = new GlideRecord('task');
gr.get(taskId);
gr.state = state;
gr.update();
}
},
getSAPTrs: function(sys_id){
let sapTrs = "";
let reqTable = new GlideRecordSecure('x_real3_app1_sap_transports');
reqTable.addQuery('item_sys_id', sys_id);
reqTable.addQuery('type_technical', "!=", "T");
reqTable.query();
while(reqTable.next()){
if(sapTrs.length){
sapTrs = sapTrs + ",";
}
sapTrs = sapTrs + reqTable.getValue("transport_no");
}
return sapTrs;
},
getSAPTrsForApproval: function(sys_id, approvalConfig){
let sapTrs = "";
let reqTable = new GlideRecordSecure('x_real3_app1_sap_transports');
reqTable.addQuery('item_sys_id', sys_id);
reqTable.addQuery('tm_level_technical', approvalConfig.levelName);
if(approvalConfig.approve == 'A'){
reqTable.addQuery('status_technical', "S");
} else {
reqTable.addQuery('status_technical', "!=", "Q");
}
reqTable.query();
let approvals = [];
while(reqTable.next()){
let sapTr = reqTable.getValue("transport_no");
let approvalsTech = [];
if(approvalConfig.type == "C"){
approvalsTech = reqTable.getValue("content_approvals_tech") ? JSON.parse(reqTable.getValue("content_approvals_tech")) : [];
} else if(approvalConfig.type == "T"){
approvalsTech = reqTable.getValue("technical_approvals_tech") ? JSON.parse(reqTable.getValue("technical_approvals_tech")) : [];
}
for(let i = 0; i < approvalsTech.length; i++){
if((approvalConfig.approve == 'A' && !approvalsTech[i]["SIGN_USER"]) ||
(approvalConfig.approve == 'R' && approvalsTech[i]["SIGN_USER"])){
approvalsTech[i]["GROUP"] = approvalsTech[i]["GROUP"] || "";
if(approvalConfig.group != approvalsTech[i]["GROUP"]){
continue;
}
if(sapTrs.length){
sapTrs = sapTrs + ",";
}
sapTrs = sapTrs + sapTr;
break;
}
}
}
return sapTrs;
}
};
3. Create the Business Rule “Start ToCs”
3.1 Business Rule configuration
Create the Business Rule – go to All → System Definition → Business Rules and create New in the Global application:
Select the Table (Change Request).
Check Advanced.
On the When to run tab, set the values below.
On the Advanced tab, add the script from section 3.2 – enable ECMAScript 2021 (ES12) mode.
Setting
Value
Setting
Value
Name
Start ToCs
Table
Change Request [change_request]
Application
Global
Active
Yes
Advanced
Yes
When
before
Order
100
Insert / Update / Delete / Query
Update only
Filter Condition
Stateis<customer target status>
Note: The target status is customer-specific. To be Tested is only an example – replace it with the status used in the customer's ServiceNow instance.
Key requirement: The rule runs before Update by design. current.setAbortAction(true) only prevents the state change in a before rule – in an after rule the new state is already saved and cannot be held back.
3.2 Script
Add this script on the Advanced tab of the Business Rule.
(function executeRule(current, previous /*null when async*/) {
try{
let tmAction = new global.TMAction();
/* --- QA-Checks excluded ---
let qas = tmAction.checkQAS(current);
qas = typeof qas == "string" ? JSON.parse(qas) : qas;
if(qas.type == "E"){
tmAction.addComment(current, qas.message);
} else if(qas.length){
if(qas[0]["HasCollisions"] || qas[0]["HasCriticalObj"] || qas[0]["HasDependency"] || qas[0]["HasSpecialAspects"]){
qas.shift();
tmAction.addComment(current, JSON.stringify(qas));
}
}
--- End of QA-Checks --- */
let response = tmAction.startToC(current); // start Transport of Copies
response = typeof response == "string" ? JSON.parse(response) : response;
if(response.type == "E"){
//tmAction.addComment(current, response.message);
// --- TOC failed > error returned by SAP ---
// "No data found" = no modifiable transports -> not a real error
if(response.message && response.message.indexOf("No data found") > -1){
// informational only: nothing to transport, allow the state change
tmAction.addComment(current, "ℹ️ ToC start skipped: no modifiable transports found.");
} else {
// real error -> keep state, inform user, abort
let msg = "❌ ToC start failed (SAP response): " + response.message
+ " – State was NOT changed.";
tmAction.addComment(current, msg);
gs.addErrorMessage(msg); // red banner on the form
current.setAbortAction(true); // prevent state change on error
}
} else {
// TOC started successfully
tmAction.addComment(current, "✅ ToC(s) successfully started.");
}
} catch(error){
gs.addErrorMessage(error); //add error message for user
current.setAbortAction(true); //abort state change action
}
})(current, previous);
4. Behaviour and messages
Case
Condition
Comment written
Banner
State change
Case
Condition
Comment written
Banner
State change
Success
type not "E"
✅ ToC(s) successfully started.
—
proceeds
No modifiable transports
"E" + message contains "No data found"
ℹ️ ToC start skipped: no modifiable transports found.
—
proceeds (info, not an error)
Real SAP error
"E" (other message)
❌ ToC start failed (SAP response): <SAP text> – State was NOT changed.
red banner (same text)
aborted
Exception
catch
—
error message
aborted
Why the prefix text? The message text itself already comes from SAP (e.g. Not all transport tasks were released (ex: SBDK901112)) and is shown verbatim. We deliberately add the ToC start failed (SAP response): prefix so that this SAP feedback is clearly visible and recognisable as surfaced by our rule.
Comment vs. abort:addComment persists via its own GlideRecord.update(), so the comment is saved even when the state change is aborted.
5. Optional: Enable QA checks
The checkQAS block (SAP entity QA_TM_TR_RELSet) is currently commented out. It only reads the QA/release status (collisions, critical objects, dependencies, special aspects) and, when active, writes a comment.
To re-enable QA checks: remove the /* --- QA checks excluded --- ... --- end QA checks --- */ wrapper.
If never needed: the block can be deleted entirely; the comment wrapper only exists for quick re-activation.
checkQAS is a read-only check and does not release or approve any transport.