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

Dr Atul G- LNG
Tera Patron
Tera Patron

Hi @nameisnani 

This is more on the education side rather than the platform side. The approve and reject buttons are visible on the left side, so I don’t think any changes are needed on the portal or that it would increase the technical debt.

*************************************************************************************************************
If my response proves useful, please indicate its helpfulness by selecting " Accept as Solution" and " Helpful." This action benefits both the community and me.

Regards
Dr. Atul G. - Learn N Grow Together
ServiceNow Techno - Functional Trainer
LinkedIn: https://www.linkedin.com/in/dratulgrover
YouTube: https://www.youtube.com/@LearnNGrowTogetherwithAtulG
Topmate: https://topmate.io/atul_grover_lng [ Connect for 1-1 Session]

****************************************************************************************************************

nameisnani
Mega Sage

@Dr Atul G- LNG  

 

But our that is cilent requirment . 

Hi @nameisnani 

I understand that this is a client requirement, but as an SN expert, it’s important to guide the customer toward best practices and help avoid technical debt. The issue is that you need to clone the widget, then add the code for the button, and the customer might ask why the same button appears on both sides. So, it’s better to educate them and avoid technical debt.

*************************************************************************************************************
If my response proves useful, please indicate its helpfulness by selecting " Accept as Solution" and " Helpful." This action benefits both the community and me.

Regards
Dr. Atul G. - Learn N Grow Together
ServiceNow Techno - Functional Trainer
LinkedIn: https://www.linkedin.com/in/dratulgrover
YouTube: https://www.youtube.com/@LearnNGrowTogetherwithAtulG
Topmate: https://topmate.io/atul_grover_lng [ Connect for 1-1 Session]

****************************************************************************************************************

Ankur Bawiskar
Tera Patron
Tera Patron

@nameisnani 

I agree with Atul here and you should train your users about the button positions.

If you still want to do that, then you will have to make changes to widget

1) Clone the OOTB "Approval Record" widget

2) add those buttons

3) then add this newly cloned widget back to approval page

AnkurBawiskar_0-1748947174362.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