The Zurich release has arrived! Interested in new features and functionalities? Click here for more

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