How to create Bussiness Rules

How to create Bussiness Rules

  • Go to All → System Definition → Business Rules

  • Create New in Application “Global”:

    • Select “Table”

    • Check “Advanced”

    • Select “When to run”

      image-20260701-054234.png
    • Go to “Advanced” and add the code to be executed (Turn on ECMAScript 2021 (ES12) mode)

(function executeRule(current, previous /*null when async*/) { try{ let tmAction = new global.TMAction(); //uncomment if Quality Checks should be enabled /*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)); } }*/ let response = tmAction.startToC(current); response = typeof response == "string" ? JSON.parse(response) : response; if(response.type == "E"){ tmAction.addComment(current, response.message); } } catch(error){ gs.addErrorMessage(error); //add error message for user current.setAbortAction(true); //abort state change action } })(current, previous);

 

  • Go to All → System Definition → Business Rules

  • Create New in Application “Global”:

    • Select “Table”

    • Check “Advanced”

    • Select “When to run”

      image-20260701-091938.png
    • Go to “Advanced” and add the code to be executed

(function executeRule(current, previous /*null when async*/) { try{ let tmAction = new global.TMAction(); let response = tmAction.approveRequests(current, {"levelName": "Production", "approve": "A", "type": "C", "group": "CONS"}); /*{"levelName": TM LEVEL, "approve": A to approve, R to revoke, "type": C or T, "group": TM Group }*/ //BUG FIX for response parsing response = typeof response == "string" && response.startsWith("{") ? JSON.parse(response) : response; if(response != "SUCCESS" && response.type == "E"){ tmAction.addComment(current, response.message); } } catch(error){ gs.addErrorMessage(error.message); //add error message for user current.setAbortAction(true); //abort state change action } })(current, previous);
(function executeRule(current, previous /*null when async*/) { let tmAction = new global.TMAction(); try{ const config = { "levelName": "Quality", "approve": "R", "type": "C", "group": "", "includeTOCs": true, "strictMode": false }; let updated = tmAction.runMassApprovals(current, config); if(updated){ let msg = `\u2705 SAP ${config.approve == 'A' ? "approval" : "revokal"} performed successfully. Level: ${config.levelName}, Type: ${config.type == 'C' ? "Content" : "Technical"}`; tmAction.addComment(current, msg); //add comment inside the current task } } catch(error){ let msg = error.message; gs.info(`ERROR Mass approval: ${msg}`); tmAction.addComment(current, msg); //add comment inside the current task gs.addErrorMessage(msg); //add error message for user current.setAbortAction(true); //abort state change action } })(current, previous);

 

TMAction Script Include

  • Go to All → System Definition → Script Include

  • Create New in “Global” Application

image-20260701-060417.png
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.userLogin, "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.userLogin, "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.userLogin, "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; } };

 

var TMAction = Class.create(); TMAction.prototype = { initialize: function() { }, type: 'TMAction', _current: {}, _config: {}, _requestNumbers: "", approveRequests: function(){ let taskId = "" + this._current.sys_id; let taskNumber = "" + this._current.number; let type = this._current.getRecordClassName(); //get controller let tmSettingsUtils = new x_real3_app1.TMSettingsUtils(); let tmSettingsDetails = tmSettingsUtils.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.userLogin, "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 tmFeaturesUtils = new x_real3_app1.TMFeaturesUtils(); let tmFeatures = tmFeaturesUtils.getTMFeaturesForType(type); let itsmName = tmFeatures["creation"]["itsmName"] || ""; let entityCall = this._config.approve == 'A' ? "massApproval" : "massRevokal"; this._config.group = this._config.group || ""; if(!this._requestNumbers){ this._requestNumbers = this.getSAPTrs(this._current.sys_id); } if(!this._requestNumbers){ return false; } let requestBody = { "requests": `(Trkorr='${this._requestNumbers}')`, "body": { "Approve": this._config.approve, "ApprovalType": this._config.type, "LevelName": this._config.levelName } }; if (this._config.group) { requestBody.body['Usrgroup'] = this._config.group; } let sapTransations = new x_real3_app1.SAPTransactions(); let response = sapTransations.setSAPTRData(JSON.stringify(connectDetails), entityCall, JSON.stringify(requestBody)); response = typeof response == "string" && response.startsWith("{") ? JSON.parse(response) : response; if(response == "SUCCESS"){ return true; } return response; } }, runMassApprovals: function(current, config){ this._current = current; this._config = config; //if strict mode then check vor invalid transports if(config.strictMode){ let invalidMsg = this.validateApprovals(); gs.info("runMassApprovals strictMode: " + invalidMsg); if (invalidMsg) { let msg = "\u274C SAP approval validation failed: " + invalidMsg; throw new Error(msg); } if(!this._requestNumbers){ return false; } } let response = this.approveRequests(); if(response?.type == "E"){ // SAP returned an error -> keep state, inform user, abort the change let msg = "\u274C SAP approval failed (SAP response): " + response.message; throw new Error(msg); } return response; }, validateApprovals: function(){ gs.info("Validating SAP Transports for massApproval BS"); let invalidMessage = ""; let sapTrs = ""; let reqTable = new GlideRecordSecure('x_real3_app1_sap_transports'); reqTable.addQuery('item_sys_id', this._current.sys_id); if(!this._config?.includeTOCs){ reqTable.addQuery('type_technical', "!=", "T"); } reqTable.query(); let approvals = []; while(!invalidMessage && reqTable.next()){ let sapTr = reqTable.getValue("transport_no"); //if it is not released into TM let currentTMLevel = reqTable.getValue("tm_level_technical"); if(!currentTMLevel){ invalidMessage = `SAP transport (${sapTr}) is not released into TM!`; continue; } else if (reqTable.getValue("tm_history")){ gs.info(`Ignoring History SAP Transport ${sapTr}.`); continue; } //check for level, showld be at least the levelName if(currentTMLevel != this._config.levelName){ let imports = reqTable.getValue('imports') ? JSON.parse(reqTable.getValue('imports')) : []; let isHigherLevel = this._isHigherLevel(imports, currentTMLevel, this._config.levelName); if(!isHigherLevel){ invalidMessage = `SAP transport (${sapTr}) is on lower level '${currentTMLevel}' but should be on '${this._config.levelName}'.`; continue; } else if(this._config.approve == 'R'){ invalidMessage = `SAP transport (${sapTr}) is on higher level '${currentTMLevel}' but should be on '${this._config.levelName}'.`; continue; } else { gs.info(`Ignoring SAP Transport ${sapTr} on higher level '${currentTMLevel}'.`); continue; } } //if approval/revokal is already done, then skipt the checks //check for approval type available let approvalsTech = []; if(this._config.type == "C"){ approvalsTech = reqTable.getValue("content_approvals_tech") ? JSON.parse(reqTable.getValue("content_approvals_tech")) : []; } else if(this._config.type == "T"){ approvalsTech = reqTable.getValue("technical_approvals_tech") ? JSON.parse(reqTable.getValue("technical_approvals_tech")) : []; } let approvalFound = false; let isDone = false; let i = 0; while(!approvalFound && i < approvalsTech.length){ let group = approvalsTech[i]["GROUP"] || ""; if(this._config.group == group){ if(this._config.approve == 'A'){ approvalFound = !approvalsTech[i]["SIGN_USER"]; isDone = !approvalFound; } else if(this._config.approve == 'R'){ approvalFound = approvalsTech[i]["SIGN_USER"]; isDone = !approvalFound; } } i++; } if(isDone){ gs.info(`Ignoring already done ${this._config.approve == "A" ? 'approval': 'revokal'} for SAP Transport ${sapTr}.`); continue; } if(!approvalFound){ invalidMessage = `SAP transport (${sapTr}) has no available ${this._config.approve == "A" ? 'approval': 'revokal'}!`; continue; } //check for status //approval type: "C" => status in {"S": for 'A,R'}, "T" => status in ["S": for 'A,R', "C": for R'] let status = reqTable.getValue("status_technical"); if(status != "S" && (this._config.type != "T" || (this._config.approve == "R" && status != "C"))){ invalidMessage = `SAP transport (${sapTr}) is on wrong TM status '${status}'.`; continue; } if(sapTrs.length){ sapTrs = sapTrs + ","; } sapTrs = sapTrs + sapTr; } this._requestNumbers = !invalidMessage ? sapTrs : ""; return invalidMessage; }, 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); if(!this._config?.includeTOCs){ reqTable.addQuery('type_technical', "!=", "T"); } reqTable.addQuery('tm_history', "NOT IN", "D,E"); 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; }, _isHigherLevel: function(imports, currentTMLevel, levelName){ const currentTMLevelLower = currentTMLevel.toLowerCase(); const levelNameLower = levelName.toLowerCase(); let currentLevelImp = -1; let indexOfLevel = -1; for (let i = 0; i < imports.length; i++) { const tmLevel = imports[i].TM_LEVEL.toLowerCase(); if (currentLevelImp === -1 && tmLevel === currentTMLevelLower) { currentLevelImp = i; } if (indexOfLevel === -1 && tmLevel === levelNameLower) { indexOfLevel = i; } if (currentLevelImp !== -1 && indexOfLevel !== -1) { break; } } return currentLevelImp > indexOfLevel; } };

TMUtil Script Include

  • Go to All → System Definition → Script Include

  • Create New in “Global” Application

    image-20260713-104328.png
var TMUtils = Class.create(); TMUtils.metaDataPatternsEntities = { "TR_ReleaseSet": "TR_Release", "Proj_DestiSet": "Proj_Desti", "CTS_ProjectSet": "CTS_Project", "QA_TM_TR_RELSet": "QA_TM_TR_REL", "QA_TM_TRSet": "QA_TM_TR", "Transport_RequestSet": "Transport_Request", "TR_AttributeSet": "TR_Attribute", "TR_TO_TMSet": "TR_TO_TM", "TR_ReassignSet": "TR_Reassign", "TR_AssignReleaseSet": "TR_AssignRelease", "TOC_StartSet": "TOC_Start", "TR_ApproveSet": "TR_Approve", "QA_Collision": "QA_Collision" }; TMUtils.prototype = { initialize: function() { }, type: 'TMUtils', _getExternalUser(){ var gr = new GlideRecord('sys_user'); gr.addQuery('sys_id', gs.getUserID()); gr.query(); gr.next(); return "" + gr.user_name; }, _getIfPropertyExists(propertyName, entityCall){ let propertyExists = false; if (!this._metadata) { return propertyExists; } let iter = this._metadata.getChildNodeIterator(); let entityName = TMUtils.metaDataPatternsEntities[entityCall]; let nodes = 0; while(iter.hasNext() || nodes < 100) { nodes++; let n = iter.next(); if(n.getNodeName() == "EntityType" && n.getAttribute("Name") == entityName){ let iter2 = n.getChildNodeIterator(); let propertyNodes = 0; while(iter2.hasNext() || propertyNodes < 50) { propertyNodes++; let n2 = iter2.next(); if(n2.getNodeName() == "Property" && n2.getAttribute("Name") == propertyName){ propertyExists = true; break; } } break; } } return propertyExists; }, _getIfNavigationPropertyExists(propertyName, entityCall){ let propertyExists = false; if (!this._metadata) { return propertyExists; } let iter = this._metadata.getChildNodeIterator(); let entityName = TMUtils.metaDataPatternsEntities[entityCall]; let nodes = 0; while(iter.hasNext() || nodes < 100) { nodes++; let n = iter.next(); if(n.getNodeName() == "EntityType" && n.getAttribute("Name") == entityName){ let iter2 = n.getChildNodeIterator(); let propertyNodes = 0; while(iter2.hasNext() || propertyNodes < 50) { propertyNodes++; let n2 = iter2.next(); if(n2.getNodeName() == "NavigationProperty" && n2.getAttribute("Name") == propertyName){ propertyExists = true; break; } } break; } } return propertyExists; }, _getTMMessage: function (response, url) { let errorStatus = response ? response.getStatusCode() : 500; let errorMessage = "Service '" + url + "' " + response.getErrorMessage(); let isCustomMessage = false; let responseBody = {}; try{ responseBody = response.getBody(); responseBody = typeof responseBody == 'string' ? JSON.parse(responseBody) : responseBody; }catch(error){ responseBody = {}; gs.info(error.message); } // Custom SAP BTP error handling if (responseBody?.error && responseBody?.message) { errorMessage = `${String(responseBody.error)}, ${String(responseBody.message)}`; isCustomMessage = true; } else if(errorStatus != 401){ // OData-style errorMessage = responseBody && responseBody.error && responseBody.error.message ? responseBody.error.message.value : errorMessage; } return JSON.stringify({"type": "E", "status": errorStatus, "message": errorMessage || "An unknown error occurred!", "customMessage": isCustomMessage}); }, _getOdataInfo: function (entityCall, propertyName, url, authHeaders) { let odataInfo = {"x-csrf-token": "", "set-cookie": "", "propertyExists": false}; authHeaders["X-CSRF-Token"] = "FETCH"; try{ let request = new sn_ws.RESTMessageV2(); request.setEndpoint(url + "/$metadata"); request.setHttpMethod('GET'); for(let key in authHeaders){ request.setRequestHeader(key, authHeaders[key]); } let metadataResp = request.execute(); let respHeaders = metadataResp.getHeaders("set-cookie"); for(let key2 in respHeaders){ if(key2 == "set-cookie"){ odataInfo["set-cookie"] = respHeaders[key2]; } else if(key2 == "x-csrf-token"){ odataInfo["x-csrf-token"] = respHeaders[key2]; } } if(metadataResp.getStatusCode() != 200){ return odataInfo; } let responseBody = {}; try{ responseBody = metadataResp.getBody(); let xmlDoc = new XMLDocument2(); xmlDoc.parseXML(responseBody); this._metadata = xmlDoc.getFirstNode("edmx:Edmx/edmx:DataServices/Schema"); }catch(error){ responseBody = {}; gs.info(error.message); } odataInfo["propertyExists"] = this._getIfPropertyExists(propertyName, entityCall); return odataInfo; } catch(ex){ let message = ex.getMessage(); gs.info("error getting metadata" + message); return odataInfo; } }, getTMSettingsForType: function(type){ var tmSettingsRecords = new GlideRecordSecure('x_real3_app1_settings'); tmSettingsRecords.addQuery('type', type); tmSettingsRecords.query(); var updateReqTable = tmSettingsRecords.next(); var devSystems = tmSettingsRecords.getDisplayValue("dev_systems"); devSystems = devSystems ? devSystems.split(",") : []; var tmSettings = {"tmServer": "", "secure": "", "server": "", "port": "", "systems": devSystems, "ctrlName": tmSettingsRecords.getDisplayValue("tm_controller_name"), "tmEnabled": "", "tmLoginBlank": false, "loginAlias": "", "logoffAlias": "", "rtcAlias": "", "systemAlias": "", "rtcHeaders": "{}", "userLogin": ""}; var tmCtrlRecords = new GlideRecordSecure('x_real3_app1_tm_controllers'); tmCtrlRecords.addQuery('tm_controller_id', tmSettings.ctrlName); tmCtrlRecords.query(); if(tmCtrlRecords.next() && tmCtrlRecords.getDisplayValue("server")){ tmSettings.tmEnabled = tmCtrlRecords.getValue("enabled") == 1; tmSettings.tmServer = tmCtrlRecords.getValue("secure") == 1 ? "https://" : "http://"; tmSettings.tmServer = tmSettings.tmServer + tmCtrlRecords.getDisplayValue("server"); tmSettings.secure = tmCtrlRecords.getValue("secure") == 1; tmSettings.server = tmCtrlRecords.getDisplayValue("server"); tmSettings.port = tmCtrlRecords.getDisplayValue("port"); tmSettings.tmLoginBlank = tmCtrlRecords.getValue("tm_login_new_tab") == 1; tmSettings.loginAlias = tmCtrlRecords.getDisplayValue("login_service_alias"); tmSettings.logoffAlias = tmCtrlRecords.getDisplayValue("logoff_service_alias"); tmSettings.rtcAlias = tmCtrlRecords.getDisplayValue("rtc_service_alias"); tmSettings.systemAlias = tmCtrlRecords.getDisplayValue("system_alias"); var rtcHeaders = tmCtrlRecords.getDisplayValue("rtc_headers") || "{}"; tmSettings.rtcHeaders = JSON.parse(rtcHeaders); tmSettings.userLogin = tmCtrlRecords.getValue("user_login") == 1; var port = tmCtrlRecords.getDisplayValue("port"); if(port){ tmSettings.tmServer = tmSettings.tmServer + ":" + port; } } return tmSettings; }, getTMFeaturesForType: function(type){ var tmFeaturesRecords = new GlideRecordSecure('x_real3_app1_features'); tmFeaturesRecords.addQuery('type', type); tmFeaturesRecords.query(); var tmFeatures = {"uiConfig": {}, "creation": {}, "approval": {}, "reassign": {}, "release": {}, "prodRelease": {}, "toc": {}}; if(tmFeaturesRecords.next()){ var toc = tmFeaturesRecords.getDisplayValue("toc") || ""; var prod_release = tmFeaturesRecords.getDisplayValue("prod_release") || ""; var ui_config = tmFeaturesRecords.getDisplayValue("ui_config") || ""; tmFeatures.uiConfig = ui_config ? JSON.parse(ui_config) : {}; tmFeatures.creation = JSON.parse(tmFeaturesRecords.getDisplayValue("creation") || "{}"); tmFeatures.approval = JSON.parse(tmFeaturesRecords.getDisplayValue("approval") || "{}"); tmFeatures.reassign = JSON.parse(tmFeaturesRecords.getDisplayValue("reassign") || "{}"); tmFeatures.release = JSON.parse(tmFeaturesRecords.getDisplayValue("release") || "{}"); tmFeatures.prodRelease = prod_release ? JSON.parse(prod_release) : {}, tmFeatures.toc = toc ? JSON.parse(toc) : {}; } return tmFeatures; }, getSAPTRData: function(req){ req = req || this.getParameter("sysparm_req"); req = JSON.parse(req) || {}; let entityCall = req.entityCall; let query = req.query; let connectDetails = req.connectDetails || {}; let tmServer = connectDetails.tmServer; let path = connectDetails.path; let sapId = req.sapId; let authHeaders = connectDetails.rtcHeaders || {}; if(connectDetails.sapUserLogin){ let session = gs.getSession(); let sapCookies = session.getClientData("SAPLogonCookies_"+ sapId); if(sapCookies && sapCookies.length){ authHeaders["Cookie"] = sapCookies; } } //FETCH odata info ["x-csrf-token", "set-cookie", "propertyExists"] let odataInfo = this._getOdataInfo(entityCall, "ExternalUser", tmServer + path, authHeaders); if(entityCall == "TR_AttributeSet"){ entityCall = "Transport_RequestSet('"+ query.trkorr +"')/TR_AttributeSet"; query = ""; } else if (entityCall == "Proj_DestiSet") { let odataInfoTRShort = this._getIfPropertyExists("TrShortDescr", entityCall); query = odataInfoTRShort ? query.path + ` and TrShortDescr eq '${query.currentShortText}'` : query.path; } if(query && entityCall != "QA_TM_TR_RELSet" && entityCall != "QA_TM_TRSet"){ query = "?$filter=" + encodeURIComponent(query); } if (odataInfo["propertyExists"]) { let externalUser = this._getExternalUser(); query = query ? query + encodeURIComponent(" and ") : "?$filter="; query = query + encodeURIComponent("ExternalUser eq '" + externalUser + "'"); } if(entityCall == "QA_TM_TRSet" && query.includes('{navToCollObj}')){ let navToCollObj = this._getIfNavigationPropertyExists("NavToCollisionObjects", "QA_Collision"); if(navToCollObj){ query = query.replace("{navToCollObj}", "NavToCollisionObjects"); } else { query = query.replace("/{navToCollObj}", ""); } } try{ let request = new sn_ws.RESTMessageV2(); request.setEndpoint(tmServer + path + "/" + entityCall + query); request.setHttpMethod('GET'); request.setRequestHeader("Accept", "application/json"); for(let key in authHeaders){ request.setRequestHeader(key, authHeaders[key]); } let response = request.execute(); let responseBody = {}; try{ responseBody = response.getBody(); responseBody = typeof responseBody == 'string' ? JSON.parse(responseBody) : responseBody; }catch(error){ responseBody = {}; gs.info(error.message); } if(response.getStatusCode() != 200){ return this._getTMMessage(response, tmServer + path + "/" + entityCall); } else { responseBody = typeof responseBody == "string" ? JSON.parse(responseBody) : (responseBody || {}); return JSON.stringify(responseBody.d ? responseBody.d.results : []); } } catch(ex){ let errorMessage = ex.getMessage(); gs.info(errorMessage); return JSON.stringify({"type": "E", "status": 500, "message": errorMessage || "An unknown error occurred!"}); } }, setSAPTRData: function(connect_details, entity_call, req_body){ let connectDetails = connect_details || this.getParameter("sysparm_connect_details"); connectDetails = JSON.parse(connectDetails); let entityCall = entity_call || this.getParameter("sysparm_entity_call"); let requestBody = req_body || this.getParameter("sysparm_req_body"); requestBody = requestBody.startsWith("{") ? JSON.parse(requestBody) : requestBody; let authHeaders = connectDetails.rtcHeaders || {}; let tmServer = connectDetails.tmServer; let path = connectDetails.path; let reqType = "POST"; let username = "SUCCESS"; let serviceQuery = ""; let externalUser = this._getExternalUser(); if(connectDetails.sapUserLogin){ let session = gs.getSession(); let sapCookies = session.getClientData("SAPLogonCookies_"+ connectDetails.sapId); if(sapCookies && sapCookies.length){ authHeaders["Cookie"] = sapCookies; username = session.getClientData("SAPLogonCookieUSER_"+ connectDetails.sapId); } } //FETCH odata info ["x-csrf-token", "set-cookie", "propertyExists"] if(entityCall == "massProdRelease"){ entityCall = "TR_AssignReleaseSet"; } else if(entityCall == "massApproval" || entityCall == "massRevokal"){ entityCall = "TR_ApproveSet"; serviceQuery = requestBody.requests; requestBody = requestBody.body; } else if(entityCall == "TR_ApproveSet"){ requestBody['UserName'] = ""; serviceQuery = "(Trkorr='" + requestBody.trkorr + "')"; } else if(entityCall == "Proj_DestiSet_Reassign"){ entityCall = "Proj_DestiSet"; serviceQuery = requestBody.serviceQuery; requestBody = requestBody.body; } let odataInfo = this._getOdataInfo(entityCall, "ExternalUser", tmServer + path, authHeaders); authHeaders["X-CSRF-Token"] = odataInfo["x-csrf-token"]; if(entityCall == "massRelease"){ authHeaders["Content-Type"] = "multipart/mixed;boundary=batch_test"; entityCall = "$batch"; //from url path it's mandatory requestBody = requestBody.replaceAll(",ExternalUser='$externalUser'", ",ExternalUser='"+ externalUser +"'"); if(odataInfo["propertyExists"]){ requestBody = requestBody.replaceAll("$externalUser", externalUser); } else { requestBody = requestBody.replaceAll(',\n"ExternalUser":"$externalUser"', ''); } } else { //update request if (["TR_TO_TMSet", "TR_ReassignSet", "TR_AssignReleaseSet", "TR_ApproveSet", "Proj_DestiSet"].includes(entityCall)) { reqType = "PUT"; if(entityCall == "TR_TO_TMSet"){ if(odataInfo["propertyExists"]){ entityCall = "TR_TO_TMSet(TransportReq='" + requestBody.trkorr + "',ExternalUser='" + externalUser + "',CrudMode='C')"; } else { entityCall = "TR_TO_TMSet(TransportReq='" + requestBody.trkorr + "',CrudMode='C')"; } } else if(entityCall == "TR_AssignReleaseSet"){ entityCall = "TR_AssignReleaseSet(RelId='" + requestBody.prodReleaseID + "')"; delete requestBody["prodReleaseID"]; } else if(entityCall == "TR_ReassignSet"){ entityCall = "TR_ReassignSet('" + requestBody.trkorr + "')"; } delete requestBody.trkorr; } if(odataInfo["propertyExists"]){ requestBody["ExternalUser"] = externalUser; } authHeaders["Accept"] = "application/json"; authHeaders["Content-Type"] = "application/json"; requestBody = JSON.stringify(requestBody); } if(odataInfo["set-cookie"] && !connectDetails.sapUserLogin){ let cookies = odataInfo["set-cookie"].split("; "); let cookie = ""; for(let i = 0; i < cookies.length; i++){ if(cookies[i].includes("sap-usercontext=") || cookies[i].includes("SAP_SESSIONID")){ cookies[i] = cookies[i].replace("path=/", ""); cookies[i] = cookies[i].replace(",", ""); cookie = i ? cookie + "; " + cookies[i].trim() : cookies[i].trim(); } } authHeaders["Cookie"] = cookie; } try{ let request = new sn_ws.RESTMessageV2(); request.setEndpoint(tmServer + path + "/" + entityCall + serviceQuery); request.setHttpMethod(reqType); request.setRequestBody(requestBody); for(let key in authHeaders){ request.setRequestHeader(key, authHeaders[key]); } let response = request.execute(); let statusCode = response.getStatusCode(); gs.info("Update status code" + statusCode); if(statusCode >= 400 || response.getErrorMessage()){ return this._getTMMessage(response, tmServer + path + "/" + entityCall); } else if(statusCode == 202){ return "Accepted"; } else if(reqType == "PUT"){ return username; } else { let responseBody = {}; try{ responseBody = response.getBody(); responseBody = typeof responseBody == 'string' ? JSON.parse(responseBody) : responseBody; }catch(error){ responseBody = {}; gs.info(error.message); } return JSON.stringify(responseBody ? responseBody.d : {}); } } catch(ex){ let errorMessage = ex.getMessage(); gs.info(errorMessage); return JSON.stringify({"type": "E", "status": 500, "message": errorMessage || "An unknown error occurred!"}); } } };