My Approvals Portal page enhancement - Allow approval fro RITM view

nameisnani
Mega Sage

Hi Team ,

 

can anyone please help me on this requirment 

 

Refer attached this is a confusing interface for the user - Please add an Approve and Reject button where the tick .

 

nameisnani_0-1748941929139.png

 

 

 

 

can anyone please provide steps to achieve this requirment

 

please provide configuration screenshots for better understanding .

 

@Ankur Bawiskar  Please help me on this requriment with your solution 

 

Thanks 

1 ACCEPTED SOLUTION

@nameisnani 

here is the working example, my buttons are at different place

You can try to adjust it as per your requirement

Script Include:

var ApprovalHandler = Class.create();
ApprovalHandler.prototype = {
    initialize: function() {},

    processApproval: function(approvalId, action) {
        // Validate action
        if (action != 'approved' && action != 'rejected') {
            return { success: false, message: 'Invalid action' };
        }

        var gr = new GlideRecord('sysapproval_approver');
        if (gr.get(approvalId)) {
            // Check if the current user is the approver or a delegate
            if (gr.approver != gs.getUserID()) {
                var delegateUtil = new global.DelegationUtil();
                if (!delegateUtil.isUserOrDelegate(gr.approver)) {
                    return { success: false, message: 'You are not authorized to approve this request' };
                }
            }
            gr.state = action;
            var result = gr.update();
            if (result) {
                // Optionally trigger workflow or other logic here
                return { success: true, message: 'Approval updated successfully' };
            } else {
                return { success: false, message: 'Failed to update approval' };
            }
        } else {
            return { success: false, message: 'Approval record not found' };
        }
    }
};

AnkurBawiskar_0-1748957000148.png

 

Widget

HTML:

<div ng-if="!data.isValid">
  ${Record not found}
</div>
<div ng-if="data.isValid">
<div class="panel panel-{{::options.color}} b">
  <div class="panel-heading">
    <h2 class="panel-title">${Approval request for {{::task.table}} {{::task.number.display_value}}}</h2>
  </div>

  <div class="panel-body">
    <div ng-if="task.short_description">{{::task.short_description.display_value}}</div>
    <div ng-if="task.opened_by"><label>${Opened by}</label> {{::task.opened_by.display_value}}</div>
    <div ng-if="task.requested_by"><label>{{::task.requested_by_label}}</label> {{::task.requested_by.display_value}}</div>
    <div ng-if="::data.approver"><label>${Approver}</label> {{::data.approver}}</div>
    <div ng-if="task.start_date"><label>${Start}</label> {{::task.start_date.display_value}}</div>
    <div ng-if="task.end_date"><label>${End}</label> {{::task.end_date.display_value}}</div>
    <div ng-if="task.quantity">${Quantity} {{::task.quantity.display_value}}</div>
    <div ng-if="task.price.value > 0"><label>${Price}</label> {{::task.price.display_value}}
      <span ng-if="task.recurring_frequency.value != null"><label>${Recurring price}</label> {{::task.recurring_price.display_value}} {{task.recurring_frequency.display_value}}</span>
      <label ng-if="task.quantity && task.quantity.value > 1"> ${each}</label>
    </div>
    <button ng-click="approve()" class="btn btn-success">Approve</button>
<button ng-click="reject()" class="btn btn-danger">Reject</button>

    
    <div ng-if="data.items.length > 0">
      <h3 class="h4">${Items in this Request}</h3>
      <div ng-repeat="item in data.items">
        <h4>
          {{::item.short_description}}
        </h4>
        <div ng-if="item.price">${Price} {{::item.price}}
          <span ng-if="item.recurring_price">${Recurring price} {{::item.recurring_price}} {{::item.recurring_frequency}}</span>
        </div>
        <sp-widget ng-if="item.variableSummarizerWidget" widget="item.variableSummarizerWidget"></sp-widget>

      </div>
    </div>

    <sp-widget widget="data.variableSummarizerWidget"></sp-widget>
</div>
  <sp-widget widget="data.ticketConversation"></sp-widget>
</div>

Client Controller:

function ($scope, $animate, $rootScope) {
	 var c = this;
  $scope.$watch("data.task", function() {
    $scope.task = $scope.data.task; // copy for shortcuts above
  })

$scope.approve = function() {
        c.server.get({
            action: 'approved',
            approvalId: c.data.sys_id
        }).then(function(response) {
            if (response.data.result.success) {
                alert('Approved successfully!');
                // Optionally reload or redirect
            } else {
                alert(response.data.result.message);
            }
        });
    };

    $scope.reject = function() {
        c.server.get({
            action: 'rejected',
            approvalId: c.data.sys_id
        }).then(function(response) {
            if (response.data.result.success) {
                alert('Rejected successfully!');
                // Optionally reload or redirect
            } else {
                alert(response.data.result.message);
            }
        });
    };

}

Server Side Script:

(function() {
	// g_approval_form_request is for approval summarizer ACLs
	// that let user read a record they need to approve. This global
	// variable is then deleted at the bottom of the script
	g_approval_form_request = true;
	
	
	
	var gr = $sp.getRecord();
	if (gr == null || !gr.isValid()) {
		data.isValid = false;
		return;
	}
	if (gr.getValue("approver") != gs.getUserID())
		data.approver = gr.approver.getDisplayValue();
	data.isValid = true;
	var task = getRecordBeingApproved(gr);
	if (task == null) {
		data.isValid = false;
		return;
	}

	var t = {};
	t = $sp.getFieldsObject(task, 'number,short_description,opened_by,requested_by,start_date,end_date,quantity,price,recurring_price,recurring_frequency');
	t.table = task.getLabel();

	var items = [];
	var idx = 0;
	var itemsGR = new GlideRecord("sc_req_item");
	itemsGR.addQuery("request", task.sys_id);
	itemsGR.query();
	while (itemsGR.next()) {
	  var item = {};
	  item.short_description = itemsGR.short_description.toString();
	  if (itemsGR.getValue("price") > 0)
		  item.price = itemsGR.getDisplayValue("price");

	  if (itemsGR.getValue("recurring_price") > 0) {
		  item.recurring_price = itemsGR.getDisplayValue("recurring_price");
			item.recurring_frequency = itemsGR.getDisplayValue("recurring_frequency");
	  }

	  if (itemsGR) {
		  item.variables = new GlobalServiceCatalogUtil().getVariablesForTask(itemsGR, true);
		  item.variableSummarizerWidget = $sp.getWidget('sc-variable-summarizer', {'variables' : item.variables, 'toggle' : false, 'task': t.number.value});
		}
	  items[idx] = item;
	  idx++;
	}

	data.items = items;
	data.sys_id = gr.getUniqueValue();

	t.requested_by_label = gs.getMessage('Requestor');
	if(t.requested_by && t.requested_by.type== 'glide_date')
		t.requested_by_label = t.requested_by.label;

	data.task = t;
	if (task) {
		data.variables = new GlobalServiceCatalogUtil().getVariablesForTask(task, true);
		data.variableSummarizerWidget = $sp.getWidget('sc-variable-summarizer', {'variables' : data.variables, 'toggle' : true, 'task': t.number.value});
	}

	function getRecordBeingApproved(gr) {
		var approvalTargetRecord;
	  if (!gr.sysapproval.nil())
			approvalTargetRecord = gr.sysapproval.getRefRecord();
		else
			approvalTargetRecord = gr.document_id.getRefRecord();

		return (approvalTargetRecord.canRead()) ? approvalTargetRecord : null;
	}

	var ticketConversationOptions = {
		placeholder: gs.getMessage("Type your message here..."),
		placeholderNoEntries: gs.getMessage("Start a conversation..."),
		btnLabel: gs.getMessage("Send")
	};

	if (options.use_approval_record_activity_stream === true ||
			options.use_approval_record_activity_stream === "true") {
		ticketConversationOptions.sys_id = gr.getUniqueValue();
		ticketConversationOptions.table  = gr.getRecordClassName();
		ticketConversationOptions.title  = gs.getMessage("Activity Stream for Approval");
	} else {
		ticketConversationOptions.sys_id = task.getUniqueValue();
		ticketConversationOptions.table  = task.getRecordClassName();
		ticketConversationOptions.title  = gs.getMessage("Activity Stream for {0}", task.getLabel());
	}
	data.ticketConversation = $sp.getWidget('widget-ticket-conversation', ticketConversationOptions);
	delete g_approval_form_request;
	
	if (input && input.action) {
        var handler = new ApprovalHandler();
        var result = handler.processApproval(input.approvalId, input.action);
        data.result = result;
        return;
    }
	
})();

Output:

approval widget copy buttons.gif

If my response helped please mark it correct and close the thread so that it benefits future readers.

Regards,
Ankur
Certified Technical Architect  ||  9x ServiceNow MVP  ||  ServiceNow Community Leader

View solution in original post

16 REPLIES 16

@Ankur Bawiskar 

 

Could you help how to add those buttons in the widget . 

@nameisnani 

I will suggest to start from your side as it will be learning for you as well.

If my response helped please mark it correct and close the thread so that it benefits future readers.

Regards,
Ankur
Certified Technical Architect  ||  9x ServiceNow MVP  ||  ServiceNow Community Leader

@Ankur Bawiskar  

 

That is true , but service portal complete new , if possible could you please provide starting steps to me , 

@nameisnani 

you can use this in HTML to show the buttons, then add logic on what happens when it's clicked

something like this will help you get started

<button ng-click="approve()" class="btn btn-success">Approve</button>
<button ng-click="reject()" class="btn btn-danger">Reject</button>

AnkurBawiskar_0-1748953351374.png

 

If my response helped please mark it correct and close the thread so that it benefits future readers.

Regards,
Ankur
Certified Technical Architect  ||  9x ServiceNow MVP  ||  ServiceNow Community Leader

@nameisnani 

here is the working example, my buttons are at different place

You can try to adjust it as per your requirement

Script Include:

var ApprovalHandler = Class.create();
ApprovalHandler.prototype = {
    initialize: function() {},

    processApproval: function(approvalId, action) {
        // Validate action
        if (action != 'approved' && action != 'rejected') {
            return { success: false, message: 'Invalid action' };
        }

        var gr = new GlideRecord('sysapproval_approver');
        if (gr.get(approvalId)) {
            // Check if the current user is the approver or a delegate
            if (gr.approver != gs.getUserID()) {
                var delegateUtil = new global.DelegationUtil();
                if (!delegateUtil.isUserOrDelegate(gr.approver)) {
                    return { success: false, message: 'You are not authorized to approve this request' };
                }
            }
            gr.state = action;
            var result = gr.update();
            if (result) {
                // Optionally trigger workflow or other logic here
                return { success: true, message: 'Approval updated successfully' };
            } else {
                return { success: false, message: 'Failed to update approval' };
            }
        } else {
            return { success: false, message: 'Approval record not found' };
        }
    }
};

AnkurBawiskar_0-1748957000148.png

 

Widget

HTML:

<div ng-if="!data.isValid">
  ${Record not found}
</div>
<div ng-if="data.isValid">
<div class="panel panel-{{::options.color}} b">
  <div class="panel-heading">
    <h2 class="panel-title">${Approval request for {{::task.table}} {{::task.number.display_value}}}</h2>
  </div>

  <div class="panel-body">
    <div ng-if="task.short_description">{{::task.short_description.display_value}}</div>
    <div ng-if="task.opened_by"><label>${Opened by}</label> {{::task.opened_by.display_value}}</div>
    <div ng-if="task.requested_by"><label>{{::task.requested_by_label}}</label> {{::task.requested_by.display_value}}</div>
    <div ng-if="::data.approver"><label>${Approver}</label> {{::data.approver}}</div>
    <div ng-if="task.start_date"><label>${Start}</label> {{::task.start_date.display_value}}</div>
    <div ng-if="task.end_date"><label>${End}</label> {{::task.end_date.display_value}}</div>
    <div ng-if="task.quantity">${Quantity} {{::task.quantity.display_value}}</div>
    <div ng-if="task.price.value > 0"><label>${Price}</label> {{::task.price.display_value}}
      <span ng-if="task.recurring_frequency.value != null"><label>${Recurring price}</label> {{::task.recurring_price.display_value}} {{task.recurring_frequency.display_value}}</span>
      <label ng-if="task.quantity && task.quantity.value > 1"> ${each}</label>
    </div>
    <button ng-click="approve()" class="btn btn-success">Approve</button>
<button ng-click="reject()" class="btn btn-danger">Reject</button>

    
    <div ng-if="data.items.length > 0">
      <h3 class="h4">${Items in this Request}</h3>
      <div ng-repeat="item in data.items">
        <h4>
          {{::item.short_description}}
        </h4>
        <div ng-if="item.price">${Price} {{::item.price}}
          <span ng-if="item.recurring_price">${Recurring price} {{::item.recurring_price}} {{::item.recurring_frequency}}</span>
        </div>
        <sp-widget ng-if="item.variableSummarizerWidget" widget="item.variableSummarizerWidget"></sp-widget>

      </div>
    </div>

    <sp-widget widget="data.variableSummarizerWidget"></sp-widget>
</div>
  <sp-widget widget="data.ticketConversation"></sp-widget>
</div>

Client Controller:

function ($scope, $animate, $rootScope) {
	 var c = this;
  $scope.$watch("data.task", function() {
    $scope.task = $scope.data.task; // copy for shortcuts above
  })

$scope.approve = function() {
        c.server.get({
            action: 'approved',
            approvalId: c.data.sys_id
        }).then(function(response) {
            if (response.data.result.success) {
                alert('Approved successfully!');
                // Optionally reload or redirect
            } else {
                alert(response.data.result.message);
            }
        });
    };

    $scope.reject = function() {
        c.server.get({
            action: 'rejected',
            approvalId: c.data.sys_id
        }).then(function(response) {
            if (response.data.result.success) {
                alert('Rejected successfully!');
                // Optionally reload or redirect
            } else {
                alert(response.data.result.message);
            }
        });
    };

}

Server Side Script:

(function() {
	// g_approval_form_request is for approval summarizer ACLs
	// that let user read a record they need to approve. This global
	// variable is then deleted at the bottom of the script
	g_approval_form_request = true;
	
	
	
	var gr = $sp.getRecord();
	if (gr == null || !gr.isValid()) {
		data.isValid = false;
		return;
	}
	if (gr.getValue("approver") != gs.getUserID())
		data.approver = gr.approver.getDisplayValue();
	data.isValid = true;
	var task = getRecordBeingApproved(gr);
	if (task == null) {
		data.isValid = false;
		return;
	}

	var t = {};
	t = $sp.getFieldsObject(task, 'number,short_description,opened_by,requested_by,start_date,end_date,quantity,price,recurring_price,recurring_frequency');
	t.table = task.getLabel();

	var items = [];
	var idx = 0;
	var itemsGR = new GlideRecord("sc_req_item");
	itemsGR.addQuery("request", task.sys_id);
	itemsGR.query();
	while (itemsGR.next()) {
	  var item = {};
	  item.short_description = itemsGR.short_description.toString();
	  if (itemsGR.getValue("price") > 0)
		  item.price = itemsGR.getDisplayValue("price");

	  if (itemsGR.getValue("recurring_price") > 0) {
		  item.recurring_price = itemsGR.getDisplayValue("recurring_price");
			item.recurring_frequency = itemsGR.getDisplayValue("recurring_frequency");
	  }

	  if (itemsGR) {
		  item.variables = new GlobalServiceCatalogUtil().getVariablesForTask(itemsGR, true);
		  item.variableSummarizerWidget = $sp.getWidget('sc-variable-summarizer', {'variables' : item.variables, 'toggle' : false, 'task': t.number.value});
		}
	  items[idx] = item;
	  idx++;
	}

	data.items = items;
	data.sys_id = gr.getUniqueValue();

	t.requested_by_label = gs.getMessage('Requestor');
	if(t.requested_by && t.requested_by.type== 'glide_date')
		t.requested_by_label = t.requested_by.label;

	data.task = t;
	if (task) {
		data.variables = new GlobalServiceCatalogUtil().getVariablesForTask(task, true);
		data.variableSummarizerWidget = $sp.getWidget('sc-variable-summarizer', {'variables' : data.variables, 'toggle' : true, 'task': t.number.value});
	}

	function getRecordBeingApproved(gr) {
		var approvalTargetRecord;
	  if (!gr.sysapproval.nil())
			approvalTargetRecord = gr.sysapproval.getRefRecord();
		else
			approvalTargetRecord = gr.document_id.getRefRecord();

		return (approvalTargetRecord.canRead()) ? approvalTargetRecord : null;
	}

	var ticketConversationOptions = {
		placeholder: gs.getMessage("Type your message here..."),
		placeholderNoEntries: gs.getMessage("Start a conversation..."),
		btnLabel: gs.getMessage("Send")
	};

	if (options.use_approval_record_activity_stream === true ||
			options.use_approval_record_activity_stream === "true") {
		ticketConversationOptions.sys_id = gr.getUniqueValue();
		ticketConversationOptions.table  = gr.getRecordClassName();
		ticketConversationOptions.title  = gs.getMessage("Activity Stream for Approval");
	} else {
		ticketConversationOptions.sys_id = task.getUniqueValue();
		ticketConversationOptions.table  = task.getRecordClassName();
		ticketConversationOptions.title  = gs.getMessage("Activity Stream for {0}", task.getLabel());
	}
	data.ticketConversation = $sp.getWidget('widget-ticket-conversation', ticketConversationOptions);
	delete g_approval_form_request;
	
	if (input && input.action) {
        var handler = new ApprovalHandler();
        var result = handler.processApproval(input.approvalId, input.action);
        data.result = result;
        return;
    }
	
})();

Output:

approval widget copy buttons.gif

If my response helped please mark it correct and close the thread so that it benefits future readers.

Regards,
Ankur
Certified Technical Architect  ||  9x ServiceNow MVP  ||  ServiceNow Community Leader