Approval Comments

Brian S5
Kilo Sage

Hey Community,

 

Was wondering if someone could assist me in my travels through the SN universe. Currently email responses only either reject if reject is clicked or if they respond with text and dont hit approve/reject. Approvals process only if accept/reject is in subject line. 

We currently have a workflow that fires an approval, the approval gets sent (notifications as well) and everything works perfect when the email response subject contains approve or reject. The issue is, when someone doesnt read the actual content of the email approval message, and instead of clicking on approve/reject, they respond with "Im ok with this" or "this is fine" or "Whats this for ?" or "Why am i supposed to approve this ?" or any other malformed string rather than accept/reject and hitting send. 

What's currently happening: Nothing as it sits, the email response comes in from the approval notification, and according to the IEA, if it doesnt have either reject or approve in subject line, nothing is updated and the approval has been failed by the system message shows in the log

The end goal: To pass whatever string of text they send in the email to the comments on either the approval record, or copy the email comments to the comments on the item, even if there isnt approve or reject in the comments. I just want the email response to copy to the item. 

What I've tried: Business rule (below) on sysapproval_approver before insert or before update or both (did both for testing)

condition: current.sysapproval.sys_class_name == 'sc_req_item'

BR Script:

(function executeRule(current, previous /*null when async*/) {
var gr = new GlideRecord('sc_req_item');
gr.get(current.sysapproval);
gr.comments = current.comments.toString();
gr.update();
})(current, previous);

 

IEA script:

 

/*global current, email, gs, GlideController, GlideRecord*/
/*eslint-disable eqeqeq*/
processApprovalEmail();

function processApprovalEmail() {
"use strict";
var errorMsg = "";
var msgArray = [];

if (current.getTableName() != "sysapproval_approver")
return;

var displayValue = getApprovalDisplayValue(current);

if (!validUser()) {
gs.log(getFailurePreamble() + "Sender email does not match approval assignee.");
msgArray.push(displayValue);
msgArray.push(current.approver.getDisplayValue());
msgArray.push(current.approver.email);
errorMsg = gs.getMessage("approvalInvalidUser", msgArray);
createEmailEvent(errorMsg);
return;
}

if (current.state == 'cancelled') {
gs.log(getFailurePreamble() + "The approval has been canceled.");
msgArray.push(displayValue);
errorMsg = gs.getMessage("approvalCancelled", msgArray);
createEmailEvent(errorMsg);
return;
}

if (email.body.state != undefined)
current.state = email.body.state;

if (current.state == "requested" && email.subject.indexOf("approve") >= 0)
current.state = "approved";

if (current.state == "requested" && email.subject.indexOf("reject") >= 0)
current.state = "rejected";

if (email.subject.indexOf("reject") == -1 && email.subject.indexOf("approve") == -1) {
// gs.log(getFailurePreamble() + "The subject is malformed. The approver probably did not click the approve or reject button on the email.");
// msgArray.push(displayValue);
// errorMsg = gs.getMessage("approvalFailed", msgArray);
// createEmailEvent(errorMsg);
return;
}

current.comments = "reply from: " + email.from + "\n\n" + email.body_text;
var controller = new GlideController();
controller.putGlobal("approvalSource", "email");
current.update();
controller.removeGlobal("approvalSource");

function validUser() {
if (current.approver == email.from_sys_id)
return true;

// check if the email is from a delegate of the approver
var g = new GlideRecord("sys_user_delegate");
g.addQuery("user", current.approver.toString());
g.addQuery("delegate", email.from_sys_id);
g.addQuery("approvals", "true");
g.addQuery("starts", "<=", gs.daysAgo(0));
g.addQuery("ends", ">=", gs.daysAgo(0));
g.query();
return g.hasNext();
}

function createEmailEvent(msg) {
gs.eventQueue("approval.email.errorMsg", current, email.from, msg);
}

function getFailurePreamble() {
return 'Approval email from ' + email.from + ' for task "' + displayValue + '" assigned to "' + current.approver.getDisplayValue()
+ '" failed because: ';
}

function getApprovalDisplayValue(approval) {
if (!gs.nil(approval.sysapproval))
return approval.getDisplayValue();
else {
var target = new GlideRecord(approval.source_table);
if (target.get(approval.document_id))
return target.getDisplayValue();
}
gs.warn("Target for sysapproval_approver:" + approval.getUniqueValue() + " not found. Target=" + approval.source_table + ":" + approval.document_id);
return "Unknown";
}

}

​
1 ACCEPTED SOLUTION

Ok I moved the current.comments and current.update a little higher.  It will put the comments in and sent that notification that the it failed.  If they read the email and use the link it will add the comments and set it to approved or rejected.

/*global current, email, gs, GlideController, GlideRecord*/
/*eslint-disable eqeqeq*/
processApprovalEmail();

function processApprovalEmail() {
	"use strict";
	var errorMsg = "";
	var msgArray = [];

	if (current.getTableName() != "sysapproval_approver")
		return;

	var displayValue = getApprovalDisplayValue(current);

	if (!validUser()) {
		gs.log(getFailurePreamble() + "Sender email does not match approval assignee.");
		msgArray.push(displayValue);
		msgArray.push(current.approver.getDisplayValue());
		msgArray.push(current.approver.email);
		errorMsg = gs.getMessage("approvalInvalidUser", msgArray);
		createEmailEvent(errorMsg);
		return;
	}

	if (current.state == 'cancelled') {
		gs.log(getFailurePreamble() + "The approval has been canceled.");
		msgArray.push(displayValue);
		errorMsg = gs.getMessage("approvalCancelled", msgArray);
		createEmailEvent(errorMsg);
		return;
	}

	current.comments = "reply from: " + email.from + "\n\n" + email.body_text;
	current.update();
	
	if (email.body.state != undefined)
		current.state = email.body.state;

	if (email.subject.indexOf("approve") >= 0)
		current.state = "approved";

	if (email.subject.indexOf("reject") >= 0)
		current.state = "rejected";

	if (current.state != "approved" && current.state != "rejected") {
		gs.log(getFailurePreamble() + "The subject is malformed. The approver probably did not click the approve or reject button on the email.");
		msgArray.push(displayValue);
		errorMsg = gs.getMessage("approvalFailed", msgArray);
		createEmailEvent(errorMsg);
		return;
	}

	//current.comments = "reply from: " + email.from + "\n\n" + email.body_text;
	var controller = new GlideController();
	controller.putGlobal("approvalSource", "email");
	current.update();
	controller.removeGlobal("approvalSource");

	function validUser() {
		if (current.approver == email.from_sys_id)
			return true;

		// check if the email is from a delegate of the approver
		var g = new GlideRecord("sys_user_delegate");
		g.addQuery("user", current.approver.toString());
		g.addQuery("delegate", email.from_sys_id);
		g.addQuery("approvals", "true");
		g.addQuery("starts", "<=", gs.daysAgo(0));
		g.addQuery("ends", ">=", gs.daysAgo(0));
		g.query();
		return g.hasNext();
	}

	function createEmailEvent(msg) {
		gs.eventQueue("approval.email.errorMsg", current, email.from, msg);
	}

	function getFailurePreamble() {
		return 'Approval email from ' + email.from + ' for task "' + displayValue + '" assigned to "' + current.approver.getDisplayValue()
				+ '" failed because: ';
	}

	function getApprovalDisplayValue(approval) {
		if (!gs.nil(approval.sysapproval))
			return approval.getDisplayValue();
		else {
			var target = new GlideRecord(approval.source_table);
			if (target.get(approval.document_id))
				return target.getDisplayValue();
		}
		gs.warn("Target for sysapproval_approver:" + approval.getUniqueValue() + " not found. Target=" + approval.source_table + ":" + approval.document_id);
		return "Unknown";
	}

}

View solution in original post

4 REPLIES 4

Brian Lancaster
Tera Sage

Try this on your inbound action code.  Basically I just added some code after if (current.state != "approved" && current.state != "rejected") to the OOB inbound action.

/*global current, email, gs, GlideController, GlideRecord*/
/*eslint-disable eqeqeq*/
processApprovalEmail();

function processApprovalEmail() {
	"use strict";
	var errorMsg = "";
	var msgArray = [];

	if (current.getTableName() != "sysapproval_approver")
		return;

	var displayValue = getApprovalDisplayValue(current);

	if (!validUser()) {
		gs.log(getFailurePreamble() + "Sender email does not match approval assignee.");
		msgArray.push(displayValue);
		msgArray.push(current.approver.getDisplayValue());
		msgArray.push(current.approver.email);
		errorMsg = gs.getMessage("approvalInvalidUser", msgArray);
		createEmailEvent(errorMsg);
		return;
	}

	if (current.state == 'cancelled') {
		gs.log(getFailurePreamble() + "The approval has been canceled.");
		msgArray.push(displayValue);
		errorMsg = gs.getMessage("approvalCancelled", msgArray);
		createEmailEvent(errorMsg);
		return;
	}

	if (email.body.state != undefined)
		current.state = email.body.state;

	if (email.subject.indexOf("approve") >= 0)
		current.state = "approved";

	if (email.subject.indexOf("reject") >= 0)
		current.state = "rejected";

	if (current.state != "approved" && current.state != "rejected") {
		current.comments = "reply from: " + email.from + "\n\n" + email.body_text;
		current.update();
		gs.log(getFailurePreamble() + "The subject is malformed. The approver probably did not click the approve or reject button on the email.");
		msgArray.push(displayValue);
		errorMsg = gs.getMessage("approvalFailed", msgArray);
		createEmailEvent(errorMsg);
		return;
	}

	current.comments = "reply from: " + email.from + "\n\n" + email.body_text;
	var controller = new GlideController();
	controller.putGlobal("approvalSource", "email");
	current.update();
	controller.removeGlobal("approvalSource");

	function validUser() {
		if (current.approver == email.from_sys_id)
			return true;

		// check if the email is from a delegate of the approver
		var g = new GlideRecord("sys_user_delegate");
		g.addQuery("user", current.approver.toString());
		g.addQuery("delegate", email.from_sys_id);
		g.addQuery("approvals", "true");
		g.addQuery("starts", "<=", gs.daysAgo(0));
		g.addQuery("ends", ">=", gs.daysAgo(0));
		g.query();
		return g.hasNext();
	}

	function createEmailEvent(msg) {
		gs.eventQueue("approval.email.errorMsg", current, email.from, msg);
	}

	function getFailurePreamble() {
		return 'Approval email from ' + email.from + ' for task "' + displayValue + '" assigned to "' + current.approver.getDisplayValue()
				+ '" failed because: ';
	}

	function getApprovalDisplayValue(approval) {
		if (!gs.nil(approval.sysapproval))
			return approval.getDisplayValue();
		else {
			var target = new GlideRecord(approval.source_table);
			if (target.get(approval.document_id))
				return target.getDisplayValue();
		}
		gs.warn("Target for sysapproval_approver:" + approval.getUniqueValue() + " not found. Target=" + approval.source_table + ":" + approval.document_id);
		return "Unknown";
	}

}

Thank you for responding. 

I tried that code in my IEA, however it didnt seem to do anything different. 

I changed the if condition back to the original but left the part in that copies to the comments, and it worked as far as updating the comments in the item which is what i was looking for. 

However the issue now, is that even if the email is sent in with a response that isnt approve/deny the item is updated, but an email goes back to the end user that says:

The approval for "RITM0013250" failed because the approval response email was formatted incorrectly. Click the "Approve" or "Reject" button in the original email.

This notification is ideal, and im pretty sure there isnt an IF combination that can account for a response rather than a approve/reject, so i will make sure to communicate the correct process for this approval issue we have come across. 

 

Thank you again for helping steer me in the right direction. 

 

Ok I moved the current.comments and current.update a little higher.  It will put the comments in and sent that notification that the it failed.  If they read the email and use the link it will add the comments and set it to approved or rejected.

/*global current, email, gs, GlideController, GlideRecord*/
/*eslint-disable eqeqeq*/
processApprovalEmail();

function processApprovalEmail() {
	"use strict";
	var errorMsg = "";
	var msgArray = [];

	if (current.getTableName() != "sysapproval_approver")
		return;

	var displayValue = getApprovalDisplayValue(current);

	if (!validUser()) {
		gs.log(getFailurePreamble() + "Sender email does not match approval assignee.");
		msgArray.push(displayValue);
		msgArray.push(current.approver.getDisplayValue());
		msgArray.push(current.approver.email);
		errorMsg = gs.getMessage("approvalInvalidUser", msgArray);
		createEmailEvent(errorMsg);
		return;
	}

	if (current.state == 'cancelled') {
		gs.log(getFailurePreamble() + "The approval has been canceled.");
		msgArray.push(displayValue);
		errorMsg = gs.getMessage("approvalCancelled", msgArray);
		createEmailEvent(errorMsg);
		return;
	}

	current.comments = "reply from: " + email.from + "\n\n" + email.body_text;
	current.update();
	
	if (email.body.state != undefined)
		current.state = email.body.state;

	if (email.subject.indexOf("approve") >= 0)
		current.state = "approved";

	if (email.subject.indexOf("reject") >= 0)
		current.state = "rejected";

	if (current.state != "approved" && current.state != "rejected") {
		gs.log(getFailurePreamble() + "The subject is malformed. The approver probably did not click the approve or reject button on the email.");
		msgArray.push(displayValue);
		errorMsg = gs.getMessage("approvalFailed", msgArray);
		createEmailEvent(errorMsg);
		return;
	}

	//current.comments = "reply from: " + email.from + "\n\n" + email.body_text;
	var controller = new GlideController();
	controller.putGlobal("approvalSource", "email");
	current.update();
	controller.removeGlobal("approvalSource");

	function validUser() {
		if (current.approver == email.from_sys_id)
			return true;

		// check if the email is from a delegate of the approver
		var g = new GlideRecord("sys_user_delegate");
		g.addQuery("user", current.approver.toString());
		g.addQuery("delegate", email.from_sys_id);
		g.addQuery("approvals", "true");
		g.addQuery("starts", "<=", gs.daysAgo(0));
		g.addQuery("ends", ">=", gs.daysAgo(0));
		g.query();
		return g.hasNext();
	}

	function createEmailEvent(msg) {
		gs.eventQueue("approval.email.errorMsg", current, email.from, msg);
	}

	function getFailurePreamble() {
		return 'Approval email from ' + email.from + ' for task "' + displayValue + '" assigned to "' + current.approver.getDisplayValue()
				+ '" failed because: ';
	}

	function getApprovalDisplayValue(approval) {
		if (!gs.nil(approval.sysapproval))
			return approval.getDisplayValue();
		else {
			var target = new GlideRecord(approval.source_table);
			if (target.get(approval.document_id))
				return target.getDisplayValue();
		}
		gs.warn("Target for sysapproval_approver:" + approval.getUniqueValue() + " not found. Target=" + approval.source_table + ":" + approval.document_id);
		return "Unknown";
	}

}

Thank you for responding Brian. Was a little to hectic to test this yesterday, however I was able to test this, this morning.

 

It is working as desired now. Thank you for helping me with this, much appreciated.