Welcome to Community Week 2025! Join us to learn, connect, and be recognized as we celebrate the spirit of Community and the power of AI. Get the details  

Creating an Approval action in a ServicePortal Widget

Peter Williams
Kilo Sage

Good afternoon folks,

i am creating a new Widget to help with approvals and would like to get someone insight into where i am going wrong.

 

i created two buttons:

HTML Template:

 

<div ng-repeat="approval in data.approvals" class="sp-approval m-b-xl">
<button name="approve"
class="btn btn-primary btn-sm"
ng-click="triggerSingleApprovalModal(item, 'approved');">
${Approve}
</button><button name="reject"
class="btn btn-default btn-sm"
ng-click="triggerSingleApprovalModal(item,'rejected');">${Reject}</button>
</div>

in my client script i have this:

function ($scope, spUtil, spUIActionsExecuter, $uibModal, spModal, $timeout) {

// Log when controller loads
console.log("Approval controller initialized");

$scope.triggerSingleApprovalModal = function(approval, op) {
console.log("triggerSingleApprovalModal called with approval:", approval, "op:", op);

if (!approval) {
console.error("No approval object provided!");
return;
}
if (!approval.approvers || approval.approvers.length === 0) {
console.error("Approval object missing approvers array or it is empty:", approval);
return;
}

$scope.data.op = op;
$scope.approval = approval;

if (($scope.options.require_approved_confirmation && op === 'approved') ||
($scope.options.require_rejected_confirmation && op === 'rejected')) {

console.log("Opening confirmation modal for op:", op);

$scope.modalInstance = $uibModal.open({
templateUrl: 'se-single-approval-modal.html',
scope: $scope
});

$scope.modalInstance.closed.then(function() {
console.log("Modal closed, resetting values");
resetValues();
});

} else {
console.log("No confirmation required, proceeding to handleSingleApproval");
$scope.handleSingleApproval(approval, op);
}
};

$scope.handleSingleApproval = function(approval, op) {
console.log("handleSingleApproval called with approval:", approval, "op:", op);

if (!approval.approvers || approval.approvers.length === 0) {
console.error("No approvers found on approval:", approval);
return;
}

var approverSysId = approval.approvers[0];
console.log("Approver sys_id:", approverSysId);

$scope.server.update({
approverSysId: approverSysId,
op: op
}).then(function(response) {
console.log("Server update response:", response);

if (response.data && response.data.message) {
console.log("Success message from server:", response.data.message);
} else if (response.data && response.data.error) {
alert("Error: " + response.data.error);
}
}).catch(function(err){
console.error("Error in server update:", err);
});
};

// Simple reset function
function resetValues() {
console.log("Resetting scope values");
$scope.data.op = null;
$scope.approval = null;
}

}


in my server script i got this:

// Handle pagination from input
if (input && input.approvals && input.approvals.length !== 0 && input.pagination && typeof input.pagination.currentPage === 'number') {
currentPage = input.pagination.currentPage;
initRow = currentPage * maxNumberOfItemsInTheList;
lastRow = initRow + maxNumberOfItemsInTheList;
}
options.initRow = initRow;
options.lastRow = lastRow;

// Handle single or bulk approval actions
if (input && input.action) {
gs.info("Handling action: " + input.action + " with op: " + input.op); // LOG
if (input.action === "single_action" && (input.op === 'approved' || input.op === 'rejected')) {
data.statusObj = portalApprovalUtil.handleSingleApprovalAction(input, options);
} else if (input.action === "bulk_action" && (input.op === "approved" || input.op === "rejected")) {
data.statusObj = portalApprovalUtil.handleBulkApprovalAction(input, options);
} else if (input.action) {
gs.addErrorMessage("Invalid input operation.");
gs.info("Invalid input operation detected."); // LOG
}
}

data.ViewApprovalPageMsg = gs.getMessage("View approval page");
var approvalsPage = portalApprovalUtil.getApprovals(options);

// Load e-signature registry map for use later
var esigRequiredMap = loadEsigRequiredMap();

// Build approvals list with variables and attachments
var approvalsByRitm = {};
var gr = new GlideRecord('sysapproval_approver');
gr.addQuery('approver', gs.getUserID());
gr.addQuery('state', 'requested');
gr.query();

gs.info("Loading approvals for user " + gs.getUserName() + " (" + gs.getUserID() + ")"); // LOG

while (gr.next()) {
var ritmSysId = gr.sysapproval.sys_id.toString();

if (!approvalsByRitm[ritmSysId]) {
var reqItem = new GlideRecord('sc_req_item');
if (reqItem.get(ritmSysId)) {
var loadedData = loadVariablesAndAttachments(reqItem, ritmSysId, esigRequiredMap);

var item = {
task: {
sys_id: reqItem.sys_id.toString(),
number: reqItem.number.toString(),
short_description: reqItem.short_description.toString(),
opened: reqItem.opened_at ? reqItem.opened_at.toString() : ''
},
approvers: [gr.sys_id.toString()],
variables: loadedData.variables,
attachments: loadedData.attachments,
requireEsigApproval: loadedData.requireEsigApproval
};

approvalsByRitm[ritmSysId] = item;
gs.info("Added approval item for RITM: " + ritmSysId); // LOG
} else {
gs.info("sc_req_item record not found for sys_id: " + ritmSysId); // LOG
}
} else {
approvalsByRitm[ritmSysId].approvers.push(gr.sys_id.toString());
}
}

any insight into where i am going wrong with my code will be helpful

 

2 REPLIES 2

Tushar
Kilo Sage
Kilo Sage

Hi @Peter Williams -

 

you use ng-repeat="approval in data.approvals" but pass item to triggerSingleApprovalModal(item, 'approved') or triggerSingleApprovalModal(item, 'rejected')

 

 

To start with can you please fix the HTML ng-click to pass approval instead of item.

 

Also in handleSingleApproval function, you call $scope.server.update({ approverSysId: approverSysId, op: op }), but Script expects input.action to be set (e.g., single_action or bulk_action).  As you’re not passing action in the payload, which may cause the server to skip the approval logic or log Invalid input operation.

 

add a "single_action" to the server.update call.

 

Thanks,

Tushar

 

 

 

Thanks.

 

Thank you for getting back

 

i updated my html to this

<button name="approve" class="btn btn-primary btn-sm" ng-click="triggerSingleApprovalModal('approved')">${Approve}</button>
<button name="reject" class="btn btn-default btn-sm" ng-click="triggerSingleApprovalModal('rejected')">${Reject}</button>
         
on the Server side is this:
//single approval handler
if(input.action == "single_action"){
if (input.op == 'approved' || input.op == 'rejected') {
data.statusObj = portalApprovalUtil.handleSingleApprovalAction(input,options);
}else{
gs.addErrorMessage("Invalid input operation.");
}
}
 
client side:
 
$scope.triggerSingleApprovalModal = function(approval, op) {
$scope.data.op = op;
$scope.approval = approval;
if($scope.options.require_approved_confirmation && op == 'approved' || $scope.options.require_rejected_confirmation && op == 'rejected'){
$scope.modalInstance = $uibModal.open({
templateUrl: 'se-single-approval-modal.html',
scope: $scope
});
 
$scope.modalInstance.closed.then(function() {
resetValues();
});
}else{
$scope.handleSingleApproval(approval,op);
}
}
 
$scope.handleSingleApproval = function(approval,op){
if (op == 'rejected' && $scope.options.require_rejection_comment) {
$scope.showRejectionCommentBox = true;
$timeout(function() {
$('.modal-open .modal').animate({
scrollTop: ($(".rejection-comment-box ").offset().top + $('.modal-open .modal').height())
}, 300);
});
 
if ($scope.data.comment == '' || !$scope.data.comment) {
return;
}
 
} else {
$scope.showRejectionCommentBox = false;
}
$scope.loading = true;
$scope.data.action = "single_action";
$scope.data.op = op;
$scope.data.target = approval.sys_id+"";
get().then(function() {
$scope.selectedApprovalsArray = $scope.selectedApprovalsArray.filter(function(a) {
return (a.sys_id != approval.sys_id)
});
spUtil[$scope.data.statusObj.type]($scope.data.statusObj.message);
$scope.closeModal();
$scope.loading = false;
},function(error){
console.log(error);
$scope.closeModal();
$scope.loading = false;
});
}
 
i am just a little confused on what i need to do