feat(SLA): Apply SLA to any document (#22449)

diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index 22ce4df..3da606b 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -228,6 +228,7 @@
 
 doc_events = {
 	"*": {
+		"validate": "erpnext.support.doctype.service_level_agreement.service_level_agreement.apply",
 		"on_submit": "erpnext.healthcare.doctype.patient_history_settings.patient_history_settings.create_medical_record",
 		"on_update_after_submit": "erpnext.healthcare.doctype.patient_history_settings.patient_history_settings.update_medical_record",
 		"on_cancel": "erpnext.healthcare.doctype.patient_history_settings.patient_history_settings.delete_medical_record"
@@ -242,6 +243,9 @@
 		"on_update": ["erpnext.hr.doctype.employee.employee.update_user_permissions",
 			"erpnext.portal.utils.set_default_role"]
 	},
+	"Communication": {
+		"on_update": "erpnext.support.doctype.service_level_agreement.service_level_agreement.update_hold_time"
+	},
 	("Sales Taxes and Charges Template", 'Price List'): {
 		"on_update": "erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings.validate_cart_settings"
 	},
@@ -332,8 +336,8 @@
 		"erpnext.projects.doctype.project.project.hourly_reminder",
 		"erpnext.projects.doctype.project.project.collect_project_status",
 		"erpnext.hr.doctype.shift_type.shift_type.process_auto_attendance_for_all_shifts",
-		"erpnext.support.doctype.issue.issue.set_service_level_agreement_variance",
-		"erpnext.erpnext_integrations.connectors.shopify_connection.sync_old_orders"
+		"erpnext.erpnext_integrations.connectors.shopify_connection.sync_old_orders",
+		"erpnext.support.doctype.service_level_agreement.service_level_agreement.set_service_level_agreement_variance"
 	],
 	"hourly_long": [
 		"erpnext.stock.doctype.repost_item_valuation.repost_item_valuation.repost_entries"
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 770bef3..161241e 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -285,4 +285,5 @@
 erpnext.patches.v13_0.germany_fill_debtor_creditor_number
 erpnext.patches.v13_0.set_pos_closing_as_failed
 erpnext.patches.v13_0.update_timesheet_changes
+erpnext.patches.v13_0.add_doctype_to_sla
 erpnext.patches.v13_0.set_training_event_attendance
diff --git a/erpnext/patches/v13_0/add_doctype_to_sla.py b/erpnext/patches/v13_0/add_doctype_to_sla.py
new file mode 100644
index 0000000..3540778
--- /dev/null
+++ b/erpnext/patches/v13_0/add_doctype_to_sla.py
@@ -0,0 +1,20 @@
+# Copyright (c) 2020, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+
+import frappe
+from frappe.model.utils.rename_field import rename_field
+
+def execute():
+	frappe.reload_doc('support', 'doctype', 'service_level_agreement')
+	if frappe.db.has_column('Service Level Agreement', 'enable'):
+		rename_field('Service Level Agreement', 'enable', 'enabled')
+
+	for sla in frappe.get_all('Service Level Agreement'):
+		agreement = frappe.get_doc('Service Level Agreement', sla.name)
+		agreement.document_type = 'Issue'
+		agreement.apply_sla_for_resolution = 1
+		agreement.append('sla_fulfilled_on', {'status': 'Resolved'})
+		agreement.append('sla_fulfilled_on', {'status': 'Closed'})
+		agreement.save()
\ No newline at end of file
diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js
index ce40ced..db7c034 100755
--- a/erpnext/public/js/utils.js
+++ b/erpnext/public/js/utils.js
@@ -749,6 +749,151 @@
 	}
 });
 
+// Show SLA dashboard
+$(document).on('app_ready', function() {
+	frappe.call({
+		method: 'erpnext.support.doctype.service_level_agreement.service_level_agreement.get_sla_doctypes',
+		callback: function(r) {
+			if (!r.message)
+				return;
+
+			$.each(r.message, function(_i, d) {
+				frappe.ui.form.on(d, {
+					onload: function(frm) {
+						if (!frm.doc.service_level_agreement)
+							return;
+
+						frappe.call({
+							method: 'erpnext.support.doctype.service_level_agreement.service_level_agreement.get_service_level_agreement_filters',
+							args: {
+								doctype: frm.doc.doctype,
+								name: frm.doc.service_level_agreement,
+								customer: frm.doc.customer
+							},
+							callback: function (r) {
+								if (r && r.message) {
+									frm.set_query('priority', function() {
+										return {
+											filters: {
+												'name': ['in', r.message.priority],
+											}
+										};
+									});
+									frm.set_query('service_level_agreement', function() {
+										return {
+											filters: {
+												'name': ['in', r.message.service_level_agreements],
+											}
+										};
+									});
+								}
+							}
+						});
+					},
+
+					refresh: function(frm) {
+						if (frm.doc.status !== 'Closed' && frm.doc.service_level_agreement
+							&& frm.doc.agreement_status === 'Ongoing') {
+							frappe.call({
+								'method': 'frappe.client.get',
+								args: {
+									doctype: 'Service Level Agreement',
+									name: frm.doc.service_level_agreement
+								},
+								callback: function(data) {
+									let statuses = data.message.pause_sla_on;
+									const hold_statuses = [];
+									$.each(statuses, (_i, entry) => {
+										hold_statuses.push(entry.status);
+									});
+									if (hold_statuses.includes(frm.doc.status)) {
+										frm.dashboard.clear_headline();
+										let message = {'indicator': 'orange', 'msg': __('SLA is on hold since {0}', [moment(frm.doc.on_hold_since).fromNow(true)])};
+										frm.dashboard.set_headline_alert(
+											'<div class="row">' +
+												'<div class="col-xs-12">' +
+													'<span class="indicator whitespace-nowrap '+ message.indicator +'"><span>'+ message.msg +'</span></span> ' +
+												'</div>' +
+											'</div>'
+										);
+									} else {
+										set_time_to_resolve_and_response(frm, data.message.apply_sla_for_resolution);
+									}
+								}
+							});
+						} else if (frm.doc.service_level_agreement) {
+							frm.dashboard.clear_headline();
+
+							let agreement_status = (frm.doc.agreement_status == 'Fulfilled') ?
+								{'indicator': 'green', 'msg': 'Service Level Agreement has been fulfilled'} :
+								{'indicator': 'red', 'msg': 'Service Level Agreement Failed'};
+
+							frm.dashboard.set_headline_alert(
+								'<div class="row">' +
+									'<div class="col-xs-12">' +
+										'<span class="indicator whitespace-nowrap '+ agreement_status.indicator +'"><span class="hidden-xs">'+ agreement_status.msg +'</span></span> ' +
+									'</div>' +
+								'</div>'
+							);
+						}
+					},
+				});
+			});
+		}
+	});
+});
+
+function set_time_to_resolve_and_response(frm, apply_sla_for_resolution) {
+	frm.dashboard.clear_headline();
+
+	let time_to_respond = get_status(frm.doc.response_by_variance);
+	if (!frm.doc.first_responded_on && frm.doc.agreement_status === 'Ongoing') {
+		time_to_respond = get_time_left(frm.doc.response_by, frm.doc.agreement_status);
+	}
+
+	let alert = `
+		<div class="row">
+			<div class="col-xs-12 col-sm-6">
+				<span class="indicator whitespace-nowrap ${time_to_respond.indicator}">
+					<span>Time to Respond: ${time_to_respond.diff_display}</span>
+				</span>
+			</div>`;
+
+
+	if (apply_sla_for_resolution) {
+		let time_to_resolve = get_status(frm.doc.resolution_by_variance);
+		if (!frm.doc.resolution_date && frm.doc.agreement_status === 'Ongoing') {
+			time_to_resolve = get_time_left(frm.doc.resolution_by, frm.doc.agreement_status);
+		}
+
+		alert += `
+			<div class="col-xs-12 col-sm-6">
+				<span class="indicator whitespace-nowrap ${time_to_resolve.indicator}">
+					<span>Time to Resolve: ${time_to_resolve.diff_display}</span>
+				</span>
+			</div>`;
+	}
+
+	alert += '</div>';
+
+	frm.dashboard.set_headline_alert(alert);
+}
+
+function get_time_left(timestamp, agreement_status) {
+	const diff = moment(timestamp).diff(moment());
+	const diff_display = diff >= 44500 ? moment.duration(diff).humanize() : 'Failed';
+	let indicator = (diff_display == 'Failed' && agreement_status != 'Fulfilled') ? 'red' : 'green';
+	return {'diff_display': diff_display, 'indicator': indicator};
+}
+
+function get_status(variance) {
+	if (variance > 0) {
+		return {'diff_display': 'Fulfilled', 'indicator': 'green'};
+	} else {
+		return {'diff_display': 'Failed', 'indicator': 'red'};
+	}
+}
+
 function attach_selector_button(inner_text, append_loction, context, grid_row) {
 	let $btn_div = $("<div>").css({"margin-bottom": "10px", "margin-top": "10px"})
 		.appendTo(append_loction);
diff --git a/erpnext/support/doctype/issue/issue.js b/erpnext/support/doctype/issue/issue.js
index 99a4e04..9ac1efa 100644
--- a/erpnext/support/doctype/issue/issue.js
+++ b/erpnext/support/doctype/issue/issue.js
@@ -9,94 +9,15 @@
 			};
 		});
 
-		if (frappe.model.can_read("Support Settings")) {
-			frappe.db.get_value("Support Settings", {name: "Support Settings"},
-				["allow_resetting_service_level_agreement", "track_service_level_agreement"], (r) => {
-					if (r && r.track_service_level_agreement == "0") {
-						frm.set_df_property("service_level_section", "hidden", 1);
-					}
-					if (r && r.allow_resetting_service_level_agreement == "0") {
-						frm.set_df_property("reset_service_level_agreement", "hidden", 1);
-					}
-			});
-		}
-
-		if (frm.doc.service_level_agreement) {
-			frappe.call({
-				method: "erpnext.support.doctype.service_level_agreement.service_level_agreement.get_service_level_agreement_filters",
-				args: {
-					name: frm.doc.service_level_agreement,
-					customer: frm.doc.customer
-				},
-				callback: function (r) {
-					if (r && r.message) {
-						frm.set_query("priority", function() {
-							return {
-								filters: {
-									"name": ["in", r.message.priority],
-								}
-							};
-						});
-						frm.set_query("service_level_agreement", function() {
-							return {
-								filters: {
-									"name": ["in", r.message.service_level_agreements],
-								}
-							};
-						});
-					}
+		frappe.db.get_value("Support Settings", {name: "Support Settings"},
+			["allow_resetting_service_level_agreement", "track_service_level_agreement"], (r) => {
+				if (r && r.track_service_level_agreement == "0") {
+					frm.set_df_property("service_level_section", "hidden", 1);
+				}
+				if (r && r.allow_resetting_service_level_agreement == "0") {
+					frm.set_df_property("reset_service_level_agreement", "hidden", 1);
 				}
 			});
-		}
-	},
-
-	refresh: function(frm) {
-
-		// alert messages
-		if (frm.doc.status !== "Closed" && frm.doc.service_level_agreement
-			&& frm.doc.agreement_status === "Ongoing") {
-			frappe.call({
-				"method": "frappe.client.get",
-				args: {
-					doctype: "Service Level Agreement",
-					name: frm.doc.service_level_agreement
-				},
-				callback: function(data) {
-					let statuses = data.message.pause_sla_on;
-					const hold_statuses = [];
-					$.each(statuses, (_i, entry) => {
-						hold_statuses.push(entry.status);
-					});
-					if (hold_statuses.includes(frm.doc.status)) {
-						frm.dashboard.clear_headline();
-						let message = { "indicator": "orange", "msg": __("SLA is on hold since {0}", [moment(frm.doc.on_hold_since).fromNow(true)]) };
-						frm.dashboard.set_headline_alert(
-							'<div class="row">' +
-							'<div class="col-xs-12">' +
-							'<span class="indicator whitespace-nowrap ' + message.indicator + '"><span>' + message.msg + '</span></span> ' +
-							'</div>' +
-							'</div>'
-						);
-					} else {
-						set_time_to_resolve_and_response(frm);
-					}
-				}
-			});
-		} else if (frm.doc.service_level_agreement) {
-			frm.dashboard.clear_headline();
-
-			let agreement_status = (frm.doc.agreement_status == "Fulfilled") ?
-				{ "indicator": "green", "msg": "Service Level Agreement has been fulfilled" } :
-				{ "indicator": "red", "msg": "Service Level Agreement Failed" };
-
-			frm.dashboard.set_headline_alert(
-				'<div class="row">' +
-				'<div class="col-xs-12">' +
-				'<span class="indicator whitespace-nowrap ' + agreement_status.indicator + '"><span class="hidden-xs">' + agreement_status.msg + '</span></span> ' +
-				'</div>' +
-				'</div>'
-			);
-		}
 
 		// buttons
 		if (frm.doc.status !== "Closed") {
@@ -142,7 +63,7 @@
 					message: __("Resetting Service Level Agreement.")
 				});
 
-				frm.call("reset_service_level_agreement", {
+				frappe.call("erpnext.support.doctype.service_level_agreement.service_level_agreement.reset_service_level_agreement", {
 					reason: values.reason,
 					user: frappe.session.user_email
 				}, () => {
@@ -224,44 +145,4 @@
 		// 	frm.timeline.wrapper.data("help-article-event-attached", true);
 		// }
 	},
-});
-
-function set_time_to_resolve_and_response(frm) {
-	frm.dashboard.clear_headline();
-
-	var time_to_respond = get_status(frm.doc.response_by_variance);
-	if (!frm.doc.first_responded_on && frm.doc.agreement_status === "Ongoing") {
-		time_to_respond = get_time_left(frm.doc.response_by, frm.doc.agreement_status);
-	}
-
-	var time_to_resolve = get_status(frm.doc.resolution_by_variance);
-	if (!frm.doc.resolution_date && frm.doc.agreement_status === "Ongoing") {
-		time_to_resolve = get_time_left(frm.doc.resolution_by, frm.doc.agreement_status);
-	}
-
-	frm.dashboard.set_headline_alert(
-		'<div class="row">' +
-			'<div class="col-xs-12 col-sm-6">' +
-				'<span class="indicator whitespace-nowrap '+ time_to_respond.indicator +'"><span>Time to Respond: '+ time_to_respond.diff_display +'</span></span> ' +
-			'</div>' +
-			'<div class="col-xs-12 col-sm-6">' +
-				'<span class="indicator whitespace-nowrap '+ time_to_resolve.indicator +'"><span>Time to Resolve: '+ time_to_resolve.diff_display +'</span></span> ' +
-			'</div>' +
-		'</div>'
-	);
-}
-
-function get_time_left(timestamp, agreement_status) {
-	const diff = moment(timestamp).diff(moment());
-	const diff_display = diff >= 44500 ? moment.duration(diff).humanize() : "Failed";
-	let indicator = (diff_display == "Failed" && agreement_status != "Fulfilled") ? "red" : "green";
-	return {"diff_display": diff_display, "indicator": indicator};
-}
-
-function get_status(variance) {
-	if (variance > 0) {
-		return {"diff_display": "Fulfilled", "indicator": "green"};
-	} else {
-		return {"diff_display": "Failed", "indicator": "red"};
-	}
-}
+});
\ No newline at end of file
diff --git a/erpnext/support/doctype/issue/issue.py b/erpnext/support/doctype/issue/issue.py
index b068363..dd6d647 100644
--- a/erpnext/support/doctype/issue/issue.py
+++ b/erpnext/support/doctype/issue/issue.py
@@ -7,11 +7,10 @@
 from frappe import _
 from frappe import utils
 from frappe.model.document import Document
-from frappe.utils import cint, now_datetime, getdate, get_weekdays, add_to_date, get_time, get_datetime, time_diff_in_seconds
+from frappe.utils import now_datetime
 from datetime import datetime, timedelta
 from frappe.model.mapper import get_mapped_doc
 from frappe.utils.user import is_website_user
-from erpnext.support.doctype.service_level_agreement.service_level_agreement import get_active_service_level_agreement_for
 from frappe.email.inbox import link_communication_to_document
 
 class Issue(Document):
@@ -25,8 +24,6 @@
 		if not self.raised_by:
 			self.raised_by = frappe.session.user
 
-		self.change_service_level_agreement_and_priority()
-		self.update_status()
 		self.set_lead_contact(self.raised_by)
 
 	def on_update(self):
@@ -54,99 +51,6 @@
 				self.company = frappe.db.get_value("Lead", self.lead, "company") or \
 					frappe.db.get_default("Company")
 
-	def update_status(self):
-		status = frappe.db.get_value("Issue", self.name, "status")
-		if self.status != "Open" and status == "Open" and not self.first_responded_on:
-			self.first_responded_on = frappe.flags.current_time or now_datetime()
-
-		if self.status in ["Closed", "Resolved"] and status not in ["Resolved", "Closed"]:
-			self.resolution_date = frappe.flags.current_time or now_datetime()
-			if frappe.db.get_value("Issue", self.name, "agreement_status") == "Ongoing":
-				set_service_level_agreement_variance(issue=self.name)
-				self.update_agreement_status()
-			set_resolution_time(issue=self)
-			set_user_resolution_time(issue=self)
-
-		if self.status == "Open" and status != "Open":
-			# if no date, it should be set as None and not a blank string "", as per mysql strict config
-			self.resolution_date = None
-			self.reset_issue_metrics()
-			# enable SLA and variance on Reopen
-			self.agreement_status = "Ongoing"
-			set_service_level_agreement_variance(issue=self.name)
-
-		self.handle_hold_time(status)
-
-	def handle_hold_time(self, status):
-		if self.service_level_agreement:
-			# set response and resolution variance as None as the issue is on Hold
-			pause_sla_on = frappe.db.get_all("Pause SLA On Status", fields=["status"],
-				filters={"parent": self.service_level_agreement})
-			hold_statuses = [entry.status for entry in pause_sla_on]
-			update_values = {}
-
-			if hold_statuses:
-				if self.status in hold_statuses and status not in hold_statuses:
-					update_values['on_hold_since'] = frappe.flags.current_time or now_datetime()
-					if not self.first_responded_on:
-						update_values['response_by'] = None
-						update_values['response_by_variance'] = 0
-					update_values['resolution_by'] = None
-					update_values['resolution_by_variance'] = 0
-
-				# calculate hold time when status is changed from any hold status to any non-hold status
-				if self.status not in hold_statuses and status in hold_statuses:
-					hold_time = self.total_hold_time if self.total_hold_time else 0
-					now_time = frappe.flags.current_time or now_datetime()
-					last_hold_time = 0
-					if self.on_hold_since:
-						# last_hold_time will be added to the sla variables
-						last_hold_time = time_diff_in_seconds(now_time, self.on_hold_since)
-						update_values['total_hold_time'] = hold_time + last_hold_time
-
-					# re-calculate SLA variables after issue changes from any hold status to any non-hold status
-					# add hold time to SLA variables
-					start_date_time = get_datetime(self.service_level_agreement_creation)
-					priority = get_priority(self)
-					now_time = frappe.flags.current_time or now_datetime()
-
-					if not self.first_responded_on:
-						response_by = get_expected_time_for(parameter="response", service_level=priority, start_date_time=start_date_time)
-						response_by = add_to_date(response_by, seconds=round(last_hold_time))
-						response_by_variance = round(time_diff_in_seconds(response_by, now_time))
-						update_values['response_by'] = response_by
-						update_values['response_by_variance'] = response_by_variance + last_hold_time
-
-					resolution_by = get_expected_time_for(parameter="resolution", service_level=priority, start_date_time=start_date_time)
-					resolution_by = add_to_date(resolution_by, seconds=round(last_hold_time))
-					resolution_by_variance = round(time_diff_in_seconds(resolution_by, now_time))
-					update_values['resolution_by'] = resolution_by
-					update_values['resolution_by_variance'] = resolution_by_variance + last_hold_time
-					update_values['on_hold_since'] = None
-
-				self.db_set(update_values)
-
-	def update_agreement_status(self):
-		if self.service_level_agreement and self.agreement_status == "Ongoing":
-			if cint(frappe.db.get_value("Issue", self.name, "response_by_variance")) < 0 or \
-				cint(frappe.db.get_value("Issue", self.name, "resolution_by_variance")) < 0:
-
-				self.agreement_status = "Failed"
-			else:
-				self.agreement_status = "Fulfilled"
-
-	def update_agreement_status_on_custom_status(self):
-		"""
-			Update Agreement Fulfilled status using Custom Scripts for Custom Issue Status
-		"""
-		if not self.first_responded_on: # first_responded_on set when first reply is sent to customer
-			self.response_by_variance = round(time_diff_in_seconds(self.response_by, now_datetime()), 2)
-
-		if not self.resolution_date: # resolution_date set when issue has been closed
-			self.resolution_by_variance = round(time_diff_in_seconds(self.resolution_by, now_datetime()), 2)
-
-		self.agreement_status = "Fulfilled" if self.response_by_variance > 0 and self.resolution_by_variance > 0 else "Failed"
-
 	def create_communication(self):
 		communication = frappe.new_doc("Communication")
 		communication.update({
@@ -213,194 +117,6 @@
 
 		return replicated_issue.name
 
-	def before_insert(self):
-		if frappe.db.get_single_value("Support Settings", "track_service_level_agreement"):
-			if frappe.flags.in_test:
-				self.set_response_and_resolution_time(priority=self.priority, service_level_agreement=self.service_level_agreement)
-			else:
-				self.set_response_and_resolution_time()
-
-	def set_response_and_resolution_time(self, priority=None, service_level_agreement=None):
-		service_level_agreement = get_active_service_level_agreement_for(priority=priority,
-			customer=self.customer, service_level_agreement=service_level_agreement)
-
-		if not service_level_agreement:
-			if frappe.db.get_value("Issue", self.name, "service_level_agreement"):
-				frappe.throw(_("Couldn't Set Service Level Agreement {0}.").format(self.service_level_agreement))
-			return
-
-		if (service_level_agreement.customer and self.customer) and not (service_level_agreement.customer == self.customer):
-			frappe.throw(_("This Service Level Agreement is specific to Customer {0}").format(service_level_agreement.customer))
-
-		self.service_level_agreement = service_level_agreement.name
-		self.priority = service_level_agreement.default_priority if not priority else priority
-
-		priority = get_priority(self)
-
-		if not self.creation:
-			self.creation = now_datetime()
-			self.service_level_agreement_creation = now_datetime()
-
-		start_date_time = get_datetime(self.service_level_agreement_creation)
-		self.response_by = get_expected_time_for(parameter="response", service_level=priority, start_date_time=start_date_time)
-		self.resolution_by = get_expected_time_for(parameter="resolution", service_level=priority, start_date_time=start_date_time)
-
-		self.response_by_variance = round(time_diff_in_seconds(self.response_by, now_datetime()))
-		self.resolution_by_variance = round(time_diff_in_seconds(self.resolution_by, now_datetime()))
-
-	def change_service_level_agreement_and_priority(self):
-		if self.service_level_agreement and frappe.db.exists("Issue", self.name) and \
-			frappe.db.get_single_value("Support Settings", "track_service_level_agreement"):
-
-			if not self.priority == frappe.db.get_value("Issue", self.name, "priority"):
-				self.set_response_and_resolution_time(priority=self.priority, service_level_agreement=self.service_level_agreement)
-				frappe.msgprint(_("Priority has been changed to {0}.").format(self.priority))
-
-			if not self.service_level_agreement == frappe.db.get_value("Issue", self.name, "service_level_agreement"):
-				self.set_response_and_resolution_time(priority=self.priority, service_level_agreement=self.service_level_agreement)
-				frappe.msgprint(_("Service Level Agreement has been changed to {0}.").format(self.service_level_agreement))
-
-	@frappe.whitelist()
-	def reset_service_level_agreement(self, reason, user):
-		if not frappe.db.get_single_value("Support Settings", "allow_resetting_service_level_agreement"):
-			frappe.throw(_("Allow Resetting Service Level Agreement from Support Settings."))
-
-		frappe.get_doc({
-			"doctype": "Comment",
-			"comment_type": "Info",
-			"reference_doctype": self.doctype,
-			"reference_name": self.name,
-			"comment_email": user,
-			"content": " resetted Service Level Agreement - {0}".format(_(reason)),
-		}).insert(ignore_permissions=True)
-
-		self.service_level_agreement_creation = now_datetime()
-		self.set_response_and_resolution_time(priority=self.priority, service_level_agreement=self.service_level_agreement)
-		self.agreement_status = "Ongoing"
-		self.save()
-
-	def reset_issue_metrics(self):
-		self.db_set("resolution_time", None)
-		self.db_set("user_resolution_time", None)
-
-
-def get_priority(issue):
-	service_level_agreement = frappe.get_doc("Service Level Agreement", issue.service_level_agreement)
-	priority = service_level_agreement.get_service_level_agreement_priority(issue.priority)
-	priority.update({
-		"support_and_resolution": service_level_agreement.support_and_resolution,
-		"holiday_list": service_level_agreement.holiday_list
-	})
-	return priority
-
-
-def get_expected_time_for(parameter, service_level, start_date_time):
-	current_date_time = start_date_time
-	expected_time = current_date_time
-	start_time = None
-	end_time = None
-
-	if parameter == "response":
-		allotted_seconds = service_level.get("response_time")
-	elif parameter == "resolution":
-		allotted_seconds = service_level.get("resolution_time")
-	else:
-		frappe.throw(_("{0} parameter is invalid").format(parameter))
-
-	expected_time_is_set = 0
-
-	support_days = {}
-	for service in service_level.get("support_and_resolution"):
-		support_days[service.workday] = frappe._dict({
-			"start_time": service.start_time,
-			"end_time": service.end_time,
-		})
-
-	holidays = get_holidays(service_level.get("holiday_list"))
-	weekdays = get_weekdays()
-
-	while not expected_time_is_set:
-		current_weekday = weekdays[current_date_time.weekday()]
-
-		if not is_holiday(current_date_time, holidays) and current_weekday in support_days:
-			start_time = current_date_time - datetime(current_date_time.year, current_date_time.month, current_date_time.day) \
-				if getdate(current_date_time) == getdate(start_date_time) and get_time_in_timedelta(current_date_time.time()) > support_days[current_weekday].start_time \
-				else support_days[current_weekday].start_time
-			end_time = support_days[current_weekday].end_time
-			time_left_today = time_diff_in_seconds(end_time, start_time)
-
-			# no time left for support today
-			if time_left_today <= 0: pass
-			elif allotted_seconds:
-				if time_left_today >= allotted_seconds:
-					expected_time = datetime.combine(getdate(current_date_time), get_time(start_time))
-					expected_time = add_to_date(expected_time, seconds=allotted_seconds)
-					expected_time_is_set = 1
-				else:
-					allotted_seconds = allotted_seconds - time_left_today
-
-		if not expected_time_is_set:
-			current_date_time = add_to_date(current_date_time, days=1)
-
-	if end_time and allotted_seconds >= 86400:
-		current_date_time = datetime.combine(getdate(current_date_time), get_time(end_time))
-	else:
-		current_date_time = expected_time
-
-	return current_date_time
-
-def set_service_level_agreement_variance(issue=None):
-	current_time = frappe.flags.current_time or now_datetime()
-
-	filters = {"status": "Open", "agreement_status": "Ongoing"}
-	if issue:
-		filters = {"name": issue}
-
-	for issue in frappe.get_list("Issue", filters=filters):
-		doc = frappe.get_doc("Issue", issue.name)
-
-		if not doc.first_responded_on: # first_responded_on set when first reply is sent to customer
-			variance = round(time_diff_in_seconds(doc.response_by, current_time), 2)
-			frappe.db.set_value(dt="Issue", dn=doc.name, field="response_by_variance", val=variance, update_modified=False)
-			if variance < 0:
-				frappe.db.set_value(dt="Issue", dn=doc.name, field="agreement_status", val="Failed", update_modified=False)
-
-		if not doc.resolution_date: # resolution_date set when issue has been closed
-			variance = round(time_diff_in_seconds(doc.resolution_by, current_time), 2)
-			frappe.db.set_value(dt="Issue", dn=doc.name, field="resolution_by_variance", val=variance, update_modified=False)
-			if variance < 0:
-				frappe.db.set_value(dt="Issue", dn=doc.name, field="agreement_status", val="Failed", update_modified=False)
-
-
-def set_resolution_time(issue):
-	# total time taken from issue creation to closing
-	resolution_time = time_diff_in_seconds(issue.resolution_date, issue.creation)
-	issue.db_set("resolution_time", resolution_time)
-
-
-def set_user_resolution_time(issue):
-	# total time taken by a user to close the issue apart from wait_time
-	communications = frappe.get_list("Communication", filters={
-			"reference_doctype": issue.doctype,
-			"reference_name": issue.name
-		},
-		fields=["sent_or_received", "name", "creation"],
-		order_by="creation"
-	)
-
-	pending_time = []
-	for i in range(len(communications)):
-		if communications[i].sent_or_received == "Received" and communications[i-1].sent_or_received == "Sent":
-			wait_time = time_diff_in_seconds(communications[i].creation, communications[i-1].creation)
-			if wait_time > 0:
-				pending_time.append(wait_time)
-
-	total_pending_time = sum(pending_time)
-	resolution_time_in_secs = time_diff_in_seconds(issue.resolution_date, issue.creation)
-	user_resolution_time = resolution_time_in_secs - total_pending_time
-	issue.db_set("user_resolution_time", user_resolution_time)
-
-
 def get_list_context(context=None):
 	return {
 		"title": _("Issues"),
@@ -439,15 +155,13 @@
 
 @frappe.whitelist()
 def set_multiple_status(names, status):
-	names = json.loads(names)
-	for name in names:
-		set_status(name, status)
+
+	for name in json.loads(names):
+		frappe.db.set_value("Issue", name, "status", status)
 
 @frappe.whitelist()
 def set_status(name, status):
-	st = frappe.get_doc("Issue", name)
-	st.status = status
-	st.save()
+	frappe.db.set_value("Issue", name, "status", status)
 
 def auto_close_tickets():
 	"""Auto-close replied support tickets after 7 days"""
@@ -473,14 +187,6 @@
 	"""Called when Contact is deleted"""
 	frappe.db.sql("""UPDATE `tabIssue` set contact='' where contact=%s""", contact.name)
 
-def get_holidays(holiday_list_name):
-	holiday_list = frappe.get_cached_doc("Holiday List", holiday_list_name)
-	holidays = [holiday.holiday_date for holiday in holiday_list.holidays]
-	return holidays
-
-def is_holiday(date, holidays):
-	return getdate(date) in holidays
-
 @frappe.whitelist()
 def make_task(source_name, target_doc=None):
 	return get_mapped_doc("Issue", source_name, {
@@ -506,9 +212,7 @@
 
 	return issue.name
 
-def get_time_in_timedelta(time):
-	"""
-		Converts datetime.time(10, 36, 55, 961454) to datetime.timedelta(seconds=38215)
-	"""
-	import datetime
-	return datetime.timedelta(hours=time.hour, minutes=time.minute, seconds=time.second)
\ No newline at end of file
+def get_holidays(holiday_list_name):
+	holiday_list = frappe.get_cached_doc("Holiday List", holiday_list_name)
+	holidays = [holiday.holiday_date for holiday in holiday_list.holidays]
+	return holidays
\ No newline at end of file
diff --git a/erpnext/support/doctype/issue/test_issue.py b/erpnext/support/doctype/issue/test_issue.py
index 7da5d7f..7b9b144 100644
--- a/erpnext/support/doctype/issue/test_issue.py
+++ b/erpnext/support/doctype/issue/test_issue.py
@@ -68,7 +68,7 @@
 		self.assertEqual(issue.resolution_by, datetime.datetime(2019, 3, 6, 12, 0))
 
 		frappe.flags.current_time = datetime.datetime(2019, 3, 4, 15, 0)
-
+		issue.reload()
 		issue.status = 'Closed'
 		issue.save()
 
diff --git a/erpnext/support/doctype/service_day/service_day.json b/erpnext/support/doctype/service_day/service_day.json
index 68614b1..9662130 100644
--- a/erpnext/support/doctype/service_day/service_day.json
+++ b/erpnext/support/doctype/service_day/service_day.json
@@ -1,4 +1,5 @@
 {
+ "actions": [],
  "creation": "2019-03-04 12:55:36.403035",
  "doctype": "DocType",
  "editable_grid": 1,
@@ -16,7 +17,8 @@
    "fieldtype": "Select",
    "in_list_view": 1,
    "label": "Workday",
-   "options": "Monday\nTuesday\nWednesday\nThursday\nFriday\nSaturday\nSunday"
+   "options": "Monday\nTuesday\nWednesday\nThursday\nFriday\nSaturday\nSunday",
+   "reqd": 1
   },
   {
    "fieldname": "section_break_2",
@@ -26,7 +28,8 @@
    "fieldname": "start_time",
    "fieldtype": "Time",
    "in_list_view": 1,
-   "label": "Start Time"
+   "label": "Start Time",
+   "reqd": 1
   },
   {
    "fieldname": "column_break_3",
@@ -36,11 +39,13 @@
    "fieldname": "end_time",
    "fieldtype": "Time",
    "in_list_view": 1,
-   "label": "End Time"
+   "label": "End Time",
+   "reqd": 1
   }
  ],
  "istable": 1,
- "modified": "2019-05-05 19:15:08.999579",
+ "links": [],
+ "modified": "2020-07-06 13:28:47.303873",
  "modified_by": "Administrator",
  "module": "Support",
  "name": "Service Day",
diff --git a/erpnext/support/doctype/service_level_agreement/service_level_agreement.js b/erpnext/support/doctype/service_level_agreement/service_level_agreement.js
index 00060b9..308bce4 100644
--- a/erpnext/support/doctype/service_level_agreement/service_level_agreement.js
+++ b/erpnext/support/doctype/service_level_agreement/service_level_agreement.js
@@ -3,16 +3,87 @@
 
 frappe.ui.form.on('Service Level Agreement', {
 	setup: function(frm) {
-		let allow_statuses = [];
-		const exclude_statuses = ['Open', 'Closed', 'Resolved'];
+		if (cint(frm.doc.apply_sla_for_resolution) === 1) {
+			frm.get_field('priorities').grid.editable_fields = [
+				{fieldname: 'priority', columns: 1},
+				{fieldname: 'default_priority', columns: 1},
+				{fieldname: 'response_time', columns: 2},
+				{fieldname: 'resolution_time', columns: 2}
+			];
+		} else {
+			frm.get_field('priorities').grid.editable_fields = [
+				{fieldname: 'priority', columns: 1},
+				{fieldname: 'default_priority', columns: 1},
+				{fieldname: 'response_time', columns: 3},
+			];
+		}
+	},
 
-		frappe.model.with_doctype('Issue', () => {
-			let statuses = frappe.meta.get_docfield('Issue', 'status', frm.doc.name).options;
-			statuses = statuses.split('\n');
-			allow_statuses = statuses.filter((status) => !exclude_statuses.includes(status));
-			frm.fields_dict.pause_sla_on.grid.update_docfield_property(
-				'status', 'options', [''].concat(allow_statuses)
-			);
+	refresh: function(frm) {
+		frm.trigger('fetch_status_fields');
+		frm.trigger('toggle_resolution_fields');
+	},
+
+	document_type: function(frm) {
+		frm.trigger('fetch_status_fields');
+	},
+
+	fetch_status_fields: function(frm) {
+		let allow_statuses = [];
+		let exclude_statuses = [];
+
+		if (frm.doc.document_type) {
+			frappe.model.with_doctype(frm.doc.document_type, () => {
+				let statuses = frappe.meta.get_docfield(frm.doc.document_type, 'status', frm.doc.name).options;
+				statuses = statuses.split('\n');
+
+				exclude_statuses = ['Open', 'Closed'];
+				allow_statuses = statuses.filter((status) => !exclude_statuses.includes(status));
+
+				frm.fields_dict.pause_sla_on.grid.update_docfield_property(
+					'status', 'options', [''].concat(allow_statuses)
+				);
+
+				exclude_statuses = ['Open'];
+				allow_statuses = statuses.filter((status) => !exclude_statuses.includes(status));
+				frm.fields_dict.sla_fulfilled_on.grid.update_docfield_property(
+					'status', 'options', [''].concat(allow_statuses)
+				);
+			});
+		}
+
+		frm.refresh_field('pause_sla_on');
+	},
+
+	apply_sla_for_resolution: function(frm) {
+		frm.trigger('toggle_resolution_fields');
+	},
+
+	toggle_resolution_fields: function(frm) {
+		if (cint(frm.doc.apply_sla_for_resolution) === 1) {
+			frm.fields_dict.priorities.grid.update_docfield_property('resolution_time', 'hidden', 0);
+			frm.fields_dict.priorities.grid.update_docfield_property('resolution_time', 'reqd', 1);
+		} else {
+			frm.fields_dict.priorities.grid.update_docfield_property('resolution_time', 'hidden', 1);
+			frm.fields_dict.priorities.grid.update_docfield_property('resolution_time', 'reqd', 0);
+		}
+
+		frm.refresh_field('priorities');
+	},
+
+	onload: function(frm) {
+		frm.set_query("document_type", function() {
+			let invalid_doctypes = frappe.model.core_doctypes_list;
+			invalid_doctypes.push(frm.doc.doctype, 'Cost Center', 'Company');
+
+			return {
+				filters: [
+					['DocType', 'issingle', '=', 0],
+					['DocType', 'istable', '=', 0],
+					['DocType', 'name', 'not in', invalid_doctypes],
+					['DocType', 'module', 'not in', ["Email", "Core", "Custom", "Event Streaming", "Social", "Data Migration", "Geo", "Desk"]]
+				]
+			};
 		});
 	}
 });
diff --git a/erpnext/support/doctype/service_level_agreement/service_level_agreement.json b/erpnext/support/doctype/service_level_agreement/service_level_agreement.json
index 939c199..61ca3a3 100644
--- a/erpnext/support/doctype/service_level_agreement/service_level_agreement.json
+++ b/erpnext/support/doctype/service_level_agreement/service_level_agreement.json
@@ -1,18 +1,18 @@
 {
  "actions": [],
- "autoname": "format:SLA-{service_level}-{####}",
+ "autoname": "format:SLA-{document_type}-{service_level}-{####}",
  "creation": "2018-12-26 21:08:15.448812",
  "doctype": "DocType",
  "editable_grid": 1,
  "engine": "InnoDB",
  "field_order": [
-  "enable",
+  "enabled",
   "section_break_2",
-  "service_level",
-  "default_priority",
+  "document_type",
   "default_service_level_agreement",
+  "default_priority",
   "column_break_2",
-  "employee_group",
+  "service_level",
   "holiday_list",
   "entity_section",
   "entity_type",
@@ -20,13 +20,14 @@
   "entity",
   "agreement_details_section",
   "start_date",
-  "active",
   "column_break_7",
   "end_date",
-  "section_break_18",
-  "pause_sla_on",
   "response_and_resolution_time_section",
+  "apply_sla_for_resolution",
   "priorities",
+  "status_details",
+  "sla_fulfilled_on",
+  "pause_sla_on",
   "support_and_resolution_section_break",
   "support_and_resolution"
  ],
@@ -36,7 +37,7 @@
    "fieldtype": "Data",
    "in_list_view": 1,
    "in_standard_filter": 1,
-   "label": "Service Level",
+   "label": "Service Level Name",
    "reqd": 1
   },
   {
@@ -51,20 +52,12 @@
    "fieldtype": "Column Break"
   },
   {
-   "fieldname": "employee_group",
-   "fieldtype": "Link",
-   "in_list_view": 1,
-   "in_standard_filter": 1,
-   "label": "Employee Group",
-   "options": "Employee Group"
-  },
-  {
+   "depends_on": "eval: !doc.default_service_level_agreement",
    "fieldname": "agreement_details_section",
    "fieldtype": "Section Break",
    "label": "Agreement Details"
   },
   {
-   "depends_on": "eval: !doc.default_service_level_agreement",
    "fieldname": "start_date",
    "fieldtype": "Date",
    "label": "Start Date"
@@ -81,21 +74,18 @@
    "label": "End Date"
   },
   {
-   "collapsible": 1,
    "fieldname": "response_and_resolution_time_section",
    "fieldtype": "Section Break",
    "label": "Response and Resolution Time"
   },
   {
-   "collapsible": 1,
    "fieldname": "support_and_resolution_section_break",
    "fieldtype": "Section Break",
-   "label": "Support Hours"
+   "label": "Working Hours"
   },
   {
    "fieldname": "support_and_resolution",
    "fieldtype": "Table",
-   "label": "Support and Resolution",
    "options": "Service Day",
    "reqd": 1
   },
@@ -107,13 +97,6 @@
    "reqd": 1
   },
   {
-   "default": "1",
-   "fieldname": "active",
-   "fieldtype": "Check",
-   "label": "Active",
-   "read_only": 1
-  },
-  {
    "fieldname": "column_break_10",
    "fieldtype": "Column Break"
   },
@@ -139,14 +122,9 @@
    "options": "\nCustomer\nCustomer Group\nTerritory"
   },
   {
-   "default": "1",
-   "fieldname": "enable",
-   "fieldtype": "Check",
-   "label": "Enable"
-  },
-  {
    "fieldname": "section_break_2",
-   "fieldtype": "Section Break"
+   "fieldtype": "Section Break",
+   "hide_border": 1
   },
   {
    "default": "0",
@@ -162,19 +140,45 @@
    "read_only": 1
   },
   {
-   "fieldname": "section_break_18",
-   "fieldtype": "Section Break",
-   "hide_border": 1
-  },
-  {
    "fieldname": "pause_sla_on",
    "fieldtype": "Table",
-   "label": "Pause SLA On",
+   "label": "SLA Paused On",
    "options": "Pause SLA On Status"
+  },
+  {
+   "fieldname": "document_type",
+   "fieldtype": "Link",
+   "label": "Document Type",
+   "options": "DocType",
+   "reqd": 1
+  },
+  {
+   "default": "1",
+   "fieldname": "enabled",
+   "fieldtype": "Check",
+   "label": "Enabled"
+  },
+  {
+   "fieldname": "status_details",
+   "fieldtype": "Section Break",
+   "label": "Status Details"
+  },
+  {
+   "fieldname": "sla_fulfilled_on",
+   "fieldtype": "Table",
+   "label": "SLA Fulfilled On",
+   "options": "SLA Fulfilled On Status",
+   "reqd": 1
+  },
+  {
+   "default": "1",
+   "fieldname": "apply_sla_for_resolution",
+   "fieldtype": "Check",
+   "label": "Apply SLA for Resolution Time"
   }
  ],
  "links": [],
- "modified": "2020-06-10 12:30:15.050785",
+ "modified": "2021-05-29 13:35:41.956849",
  "modified_by": "Administrator",
  "module": "Support",
  "name": "Service Level Agreement",
diff --git a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py
index 70c4696..60e5fbe 100644
--- a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py
+++ b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py
@@ -6,44 +6,43 @@
 import frappe
 from frappe.model.document import Document
 from frappe import _
-from frappe.utils import getdate, get_weekdays, get_link_to_form
+from frappe.core.utils import get_parent_doc
+from frappe.utils import time_diff_in_seconds, getdate, get_weekdays, add_to_date, get_time, get_datetime, \
+	get_time_zone, to_timedelta, get_datetime_str, get_link_to_form, cint
+from datetime import datetime
+from erpnext.support.doctype.issue.issue import get_holidays
 
 class ServiceLevelAgreement(Document):
-
 	def validate(self):
 		self.validate_doc()
+		self.validate_status_field()
 		self.check_priorities()
 		self.check_support_and_resolution()
 
 	def check_priorities(self):
-		default_priority = []
 		priorities = []
 
 		for priority in self.priorities:
 			# Check if response and resolution time is set for every priority
-			if not priority.response_time or not priority.resolution_time:
-				frappe.throw(_("Set Response Time and Resolution Time for Priority {0} in row {1}.").format(priority.priority, priority.idx))
+			if not priority.response_time:
+				frappe.throw(_("Set Response Time for Priority {0} in row {1}.").format(priority.priority, priority.idx))
+
+			if self.apply_sla_for_resolution:
+				if not priority.resolution_time:
+					frappe.throw(_("Set Response Time for Priority {0} in row {1}.").format(priority.priority, priority.idx))
+
+				response = priority.response_time
+				resolution = priority.resolution_time
+				if response > resolution:
+					frappe.throw(_("Response Time for {0} priority in row {1} can't be greater than Resolution Time.").format(priority.priority, priority.idx))
 
 			priorities.append(priority.priority)
 
-			if priority.default_priority:
-				default_priority.append(priority.default_priority)
-
-			response = priority.response_time
-			resolution = priority.resolution_time
-
-			if response > resolution:
-				frappe.throw(_("Response Time for {0} priority in row {1} can't be greater than Resolution Time.").format(priority.priority, priority.idx))
-
 		# Check if repeated priority
 		if not len(set(priorities)) == len(priorities):
 			repeated_priority = get_repeated(priorities)
 			frappe.throw(_("Priority {0} has been repeated.").format(repeated_priority))
 
-		# Check if repeated default priority
-		if not len(set(default_priority)) == len(default_priority):
-			frappe.throw(_("Select only one Priority as Default."))
-
 		# set default priority from priorities
 		try:
 			self.default_priority = next(d.priority for d in self.priorities if d.default_priority)
@@ -55,17 +54,12 @@
 		support_days = []
 
 		for support_and_resolution in self.support_and_resolution:
-			# Check if start and end time is set for every support day
-			if not (support_and_resolution.start_time or support_and_resolution.end_time):
-				frappe.throw(_("Set Start Time and End Time for  \
-					Support Day {0} at index {1}.".format(support_and_resolution.workday, support_and_resolution.idx)))
-
 			support_days.append(support_and_resolution.workday)
 			support_and_resolution.idx = week.index(support_and_resolution.workday) + 1
 
-			if support_and_resolution.start_time >= support_and_resolution.end_time:
-				frappe.throw(_("Start Time can't be greater than or equal to End Time \
-					for {0}.".format(support_and_resolution.workday)))
+			if to_timedelta(support_and_resolution.start_time) >= to_timedelta(support_and_resolution.end_time):
+				frappe.throw(_("Start Time can't be greater than or equal to End Time for {0}.").format(
+					support_and_resolution.workday))
 
 		# Check for repeated workday
 		if not len(set(support_days)) == len(support_days):
@@ -73,24 +67,34 @@
 			frappe.throw(_("Workday {0} has been repeated.").format(repeated_days))
 
 	def validate_doc(self):
-		if not frappe.db.get_single_value("Support Settings", "track_service_level_agreement") and self.enable:
+		if self.enabled and self.document_type == "Issue" \
+			and not frappe.db.get_single_value("Support Settings", "track_service_level_agreement"):
 			frappe.throw(_("{0} is not enabled in {1}").format(frappe.bold("Track Service Level Agreement"),
 				get_link_to_form("Support Settings", "Support Settings")))
 
-		if self.default_service_level_agreement:
-			if frappe.db.exists("Service Level Agreement", {"default_service_level_agreement": "1", "name": ["!=", self.name]}):
-				frappe.throw(_("A Default Service Level Agreement already exists."))
-		else:
-			if self.start_date and self.end_date:
-				if getdate(self.start_date) >= getdate(self.end_date):
-					frappe.throw(_("Start Date of Agreement can't be greater than or equal to End Date."))
+		if self.default_service_level_agreement and frappe.db.exists("Service Level Agreement", {
+			"document_type": self.document_type,
+			"default_service_level_agreement": "1",
+			"name": ["!=", self.name]
+		}):
+			frappe.throw(_("Default Service Level Agreement for {0} already exists.").format(self.document_type))
 
-				if getdate(self.end_date) < getdate(frappe.utils.getdate()):
-					frappe.throw(_("End Date of Agreement can't be less than today."))
+		if self.start_date and self.end_date:
+			self.validate_from_to_dates(self.start_date, self.end_date)
 
-		if self.entity_type and self.entity:
-			if frappe.db.exists("Service Level Agreement", {"entity_type": self.entity_type, "entity": self.entity, "name": ["!=", self.name]}):
-				frappe.throw(_("Service Level Agreement with Entity Type {0} and Entity {1} already exists.").format(self.entity_type, self.entity))
+		if self.entity_type and self.entity and frappe.db.exists("Service Level Agreement", {
+			"entity_type": self.entity_type,
+			"entity": self.entity,
+			"name": ["!=", self.name]
+		}):
+			frappe.throw(_("Service Level Agreement for {0} {1} already exists.").format(
+				frappe.bold(self.entity_type), frappe.bold(self.entity)))
+
+	def validate_status_field(self):
+		meta = frappe.get_meta(self.document_type)
+		if not meta.get_field("status"):
+			frappe.throw(_("The Document Type {0} must have a Status field to configure Service Level Agreement").format(
+				frappe.bold(self.document_type)))
 
 	def get_service_level_agreement_priority(self, priority):
 		priority = frappe.get_doc("Service Level Priority", {"priority": priority, "parent": self.name})
@@ -101,78 +105,169 @@
 			"resolution_time": priority.resolution_time
 		})
 
+	def before_insert(self):
+		# no need to set up SLA fields for Issue dt as they are standard fields in Issue
+		if self.document_type == "Issue":
+			return
+
+		service_level_agreement_fields = get_service_level_agreement_fields()
+		meta = frappe.get_meta(self.document_type, cached=False)
+
+		if meta.custom:
+			self.create_docfields(meta, service_level_agreement_fields)
+		else:
+			self.create_custom_fields(meta, service_level_agreement_fields)
+
+	def on_trash(self):
+		set_documents_with_active_service_level_agreement()
+
+	def after_insert(self):
+		set_documents_with_active_service_level_agreement()
+
+	def on_update(self):
+		set_documents_with_active_service_level_agreement()
+
+	def create_docfields(self, meta, service_level_agreement_fields):
+		last_index = len(meta.fields)
+
+		for field in service_level_agreement_fields:
+			if not meta.has_field(field.get("fieldname")):
+				last_index += 1
+
+				frappe.get_doc({
+					"doctype": "DocField",
+					"idx": last_index,
+					"parenttype": "DocType",
+					"parentfield": "fields",
+					"parent": self.document_type,
+					"label": field.get("label"),
+					"fieldname": field.get("fieldname"),
+					"fieldtype": field.get("fieldtype"),
+					"collapsible": field.get("collapsible"),
+					"options": field.get("options"),
+					"read_only": field.get("read_only"),
+					"hidden": field.get("hidden"),
+					"description": field.get("description"),
+					"default": field.get("default"),
+				}).insert(ignore_permissions=True)
+			else:
+				existing_field = meta.get_field(field.get("fieldname"))
+				self.reset_field_properties(existing_field, "DocField", field)
+
+		# to update meta and modified timestamp
+		frappe.get_doc('DocType', self.document_type).save(ignore_permissions=True)
+
+	def create_custom_fields(self, meta, service_level_agreement_fields):
+		for field in service_level_agreement_fields:
+			if not meta.has_field(field.get("fieldname")):
+				frappe.get_doc({
+					"doctype": "Custom Field",
+					"dt": self.document_type,
+					"label": field.get("label"),
+					"fieldname": field.get("fieldname"),
+					"fieldtype": field.get("fieldtype"),
+					"insert_after": "append",
+					"collapsible": field.get("collapsible"),
+					"options": field.get("options"),
+					"read_only": field.get("read_only"),
+					"hidden": field.get("hidden"),
+					"description": field.get("description"),
+					"default": field.get("default"),
+				}).insert(ignore_permissions=True)
+			else:
+				existing_field = meta.get_field(field.get("fieldname"))
+				self.reset_field_properties(existing_field, "Custom Field", field)
+
+	def reset_field_properties(self, field, field_dt, sla_field):
+		field = frappe.get_doc(field_dt, {"fieldname": field.fieldname})
+		field.label = sla_field.get("label")
+		field.fieldname = sla_field.get("fieldname")
+		field.fieldtype = sla_field.get("fieldtype")
+		field.collapsible = sla_field.get("collapsible")
+		field.hidden = sla_field.get("hidden")
+		field.options = sla_field.get("options")
+		field.read_only = sla_field.get("read_only")
+		field.hidden = sla_field.get("hidden")
+		field.description = sla_field.get("description")
+		field.default = sla_field.get("default")
+		field.save(ignore_permissions=True)
+
+
 def check_agreement_status():
-	service_level_agreements = frappe.get_list("Service Level Agreement", filters=[
-		{"active": 1},
+	service_level_agreements = frappe.get_all("Service Level Agreement", filters=[
+		{"enabled": 1},
 		{"default_service_level_agreement": 0}
 	], fields=["name"])
 
 	for service_level_agreement in service_level_agreements:
 		doc = frappe.get_doc("Service Level Agreement", service_level_agreement.name)
 		if doc.end_date and getdate(doc.end_date) < getdate(frappe.utils.getdate()):
-			frappe.db.set_value("Service Level Agreement", service_level_agreement.name, "active", 0)
+			frappe.db.set_value("Service Level Agreement", service_level_agreement.name, "enabled", 0)
 
-def get_active_service_level_agreement_for(priority, customer=None, service_level_agreement=None):
-	if not frappe.db.get_single_value("Support Settings", "track_service_level_agreement"):
+
+def get_active_service_level_agreement_for(doctype, priority, customer=None, service_level_agreement=None):
+	if doctype == "Issue" and not frappe.db.get_single_value("Support Settings", "track_service_level_agreement"):
 		return
 
 	filters = [
-		["Service Level Agreement", "active", "=", 1],
-		["Service Level Agreement", "enable", "=", 1]
+		["Service Level Agreement", "document_type", "=", doctype],
+		["Service Level Agreement", "enabled", "=", 1]
 	]
-
 	if priority:
 		filters.append(["Service Level Priority", "priority", "=", priority])
 
-	or_filters = [
-		["Service Level Agreement", "entity", "in", [customer, get_customer_group(customer), get_customer_territory(customer)]]
-	]
+	or_filters = []
 	if service_level_agreement:
 		or_filters = [
 			["Service Level Agreement", "name", "=", service_level_agreement],
 		]
 
+	if customer:
+		or_filters.append(
+			["Service Level Agreement", "entity", "in", [customer, get_customer_group(customer), get_customer_territory(customer)]]
+		)
 	or_filters.append(["Service Level Agreement", "default_service_level_agreement", "=", 1])
 
-	agreement = frappe.get_list("Service Level Agreement", filters=filters, or_filters=or_filters,
-		fields=["name", "default_priority"])
+	agreement = frappe.get_all("Service Level Agreement", filters=filters, or_filters=or_filters,
+		fields=["name", "default_priority", "apply_sla_for_resolution"])
 
 	return agreement[0] if agreement else None
 
+
 def get_customer_group(customer):
-	if customer:
-		return frappe.db.get_value("Customer", customer, "customer_group")
+	return frappe.db.get_value("Customer", customer, "customer_group") if customer else None
+
 
 def get_customer_territory(customer):
-	if customer:
-		return frappe.db.get_value("Customer", customer, "territory")
+	return frappe.db.get_value("Customer", customer, "territory") if customer else None
+
 
 @frappe.whitelist()
-def get_service_level_agreement_filters(name, customer=None):
+def get_service_level_agreement_filters(doctype, name, customer=None):
 	if not frappe.db.get_single_value("Support Settings", "track_service_level_agreement"):
 		return
 
 	filters = [
-		["Service Level Agreement", "active", "=", 1],
-		["Service Level Agreement", "enable", "=", 1]
+		["Service Level Agreement", "document_type", "=", doctype],
+		["Service Level Agreement", "enabled", "=", 1]
 	]
 
-	if not customer:
-		or_filters = [
-			["Service Level Agreement", "default_service_level_agreement", "=", 1]
-		]
-	else:
+	or_filters = [
+		["Service Level Agreement", "default_service_level_agreement", "=", 1]
+	]
+
+	if customer:
 		# Include SLA with No Entity and Entity Type
-		or_filters = [
-			["Service Level Agreement", "entity", "in", [customer, get_customer_group(customer), get_customer_territory(customer), ""]],
-			["Service Level Agreement", "default_service_level_agreement", "=", 1]
-		]
+		or_filters.append(
+			["Service Level Agreement", "entity", "in", [customer, get_customer_group(customer), get_customer_territory(customer), ""]]
+		)
 
 	return {
-		"priority": [priority.priority for priority in frappe.get_list("Service Level Priority", filters={"parent": name}, fields=["priority"])],
-		"service_level_agreements": [d.name for d in frappe.get_list("Service Level Agreement", filters=filters, or_filters=or_filters)]
+		"priority": [priority.priority for priority in frappe.get_all("Service Level Priority", filters={"parent": name}, fields=["priority"])],
+		"service_level_agreements": [d.name for d in frappe.get_all("Service Level Agreement", filters=filters, or_filters=or_filters)]
 	}
 
+
 def get_repeated(values):
 	unique_list = []
 	diff = []
@@ -183,3 +278,573 @@
 			if value not in diff:
 				diff.append(str(value))
 	return " ".join(diff)
+
+
+def get_documents_with_active_service_level_agreement():
+	if not frappe.cache().hget("service_level_agreement", "active"):
+		set_documents_with_active_service_level_agreement()
+
+	return frappe.cache().hget("service_level_agreement", "active")
+
+
+def set_documents_with_active_service_level_agreement():
+	active = [sla.document_type for sla in frappe.get_all("Service Level Agreement", fields=["document_type"])]
+	frappe.cache().hset("service_level_agreement", "active", active)
+
+
+def apply(doc, method=None):
+	# Applies SLA to document on validate
+	if frappe.flags.in_patch or frappe.flags.in_install or frappe.flags.in_setup_wizard or \
+		doc.doctype not in get_documents_with_active_service_level_agreement():
+		return
+
+	service_level_agreement = get_active_service_level_agreement_for(doctype=doc.get("doctype"), priority=doc.get("priority"),
+		customer=doc.get("customer"), service_level_agreement=doc.get("service_level_agreement"))
+
+	if not service_level_agreement:
+		return
+
+	set_sla_properties(doc, service_level_agreement)
+
+
+def set_sla_properties(doc, service_level_agreement):
+	if frappe.db.exists(doc.doctype, doc.name):
+		from_db = frappe.get_doc(doc.doctype, doc.name)
+	else:
+		from_db = frappe._dict({})
+
+	meta = frappe.get_meta(doc.doctype)
+
+	if meta.has_field("customer") and service_level_agreement.customer and doc.get("customer") and \
+		not service_level_agreement.customer == doc.get("customer"):
+		frappe.throw(_("Service Level Agreement {0} is specific to Customer {1}").format(service_level_agreement.name,
+			service_level_agreement.customer))
+
+	doc.service_level_agreement = service_level_agreement.name
+	doc.priority = doc.get("priority") or service_level_agreement.default_priority
+	priority = get_priority(doc)
+
+	if not doc.creation:
+		doc.creation = now_datetime(doc.get("owner"))
+
+		if meta.has_field("service_level_agreement_creation"):
+			doc.service_level_agreement_creation = now_datetime(doc.get("owner"))
+
+	start_date_time = get_datetime(doc.get("service_level_agreement_creation") or doc.creation)
+
+	set_response_by_and_variance(doc, meta, start_date_time, priority)
+	if service_level_agreement.apply_sla_for_resolution:
+		set_resolution_by_and_variance(doc, meta, start_date_time, priority)
+
+	update_status(doc, from_db, meta)
+
+
+def update_status(doc, from_db, meta):
+	if meta.has_field("status"):
+		if meta.has_field("first_responded_on") and doc.status != "Open" and \
+			from_db.status == "Open" and not doc.first_responded_on:
+			doc.first_responded_on = frappe.flags.current_time or now_datetime(doc.get("owner"))
+
+		if meta.has_field("service_level_agreement") and doc.service_level_agreement:
+			# mark sla status as fulfilled based on the configuration
+			fulfillment_statuses = [entry.status for entry in frappe.db.get_all("SLA Fulfilled On Status", filters={
+				"parent": doc.service_level_agreement
+			}, fields=["status"])]
+
+			if doc.status in fulfillment_statuses and from_db.status not in fulfillment_statuses:
+				apply_sla_for_resolution = frappe.db.get_value("Service Level Agreement", doc.service_level_agreement,
+					"apply_sla_for_resolution")
+
+				if apply_sla_for_resolution and meta.has_field("resolution_date"):
+					doc.resolution_date = frappe.flags.current_time or now_datetime(doc.get("owner"))
+
+				if meta.has_field("agreement_status") and from_db.agreement_status == "Ongoing":
+					set_service_level_agreement_variance(doc.doctype, doc.name)
+					update_agreement_status(doc, meta)
+
+				if apply_sla_for_resolution:
+					set_resolution_time(doc, meta)
+					set_user_resolution_time(doc, meta)
+
+		if doc.status == "Open" and from_db.status != "Open":
+			# if no date, it should be set as None and not a blank string "", as per mysql strict config
+			# enable SLA and variance on Reopen
+			reset_metrics(doc, meta)
+			set_service_level_agreement_variance(doc.doctype, doc.name)
+
+	handle_hold_time(doc, meta, from_db.status)
+
+
+def get_expected_time_for(parameter, service_level, start_date_time):
+	current_date_time = start_date_time
+	expected_time = current_date_time
+	start_time = end_time = None
+	expected_time_is_set = 0
+
+	allotted_seconds = get_allotted_seconds(parameter, service_level)
+	support_days = get_support_days(service_level)
+	holidays = get_holidays(service_level.get("holiday_list"))
+	weekdays = get_weekdays()
+
+	while not expected_time_is_set:
+		current_weekday = weekdays[current_date_time.weekday()]
+
+		if not is_holiday(current_date_time, holidays) and current_weekday in support_days:
+			if getdate(current_date_time) == getdate(start_date_time) \
+				and get_time_in_timedelta(current_date_time.time()) > support_days[current_weekday].start_time:
+				start_time = current_date_time - datetime(current_date_time.year, current_date_time.month, current_date_time.day)
+			else:
+				start_time = support_days[current_weekday].start_time
+
+			end_time = support_days[current_weekday].end_time
+			time_left_today = time_diff_in_seconds(end_time, start_time)
+			# no time left for support today
+			if time_left_today <= 0:
+				pass
+
+			elif allotted_seconds:
+				if time_left_today >= allotted_seconds:
+					expected_time = datetime.combine(getdate(current_date_time), get_time(start_time))
+					expected_time = add_to_date(expected_time, seconds=allotted_seconds)
+					expected_time_is_set = 1
+				else:
+					allotted_seconds = allotted_seconds - time_left_today
+
+		if not expected_time_is_set:
+			current_date_time = add_to_date(current_date_time, days=1)
+
+	if end_time and allotted_seconds >= 86400:
+		current_date_time = datetime.combine(getdate(current_date_time), get_time(end_time))
+	else:
+		current_date_time = expected_time
+
+	return current_date_time
+
+
+def get_allotted_seconds(parameter, service_level):
+	allotted_seconds = 0
+	if parameter == "response":
+		allotted_seconds = service_level.get("response_time")
+	elif parameter == "resolution":
+		allotted_seconds = service_level.get("resolution_time")
+	else:
+		frappe.throw(_("{0} parameter is invalid").format(parameter))
+
+	return allotted_seconds
+
+
+def get_support_days(service_level):
+	support_days = {}
+	for service in service_level.get("support_and_resolution"):
+		support_days[service.workday] = frappe._dict({
+			"start_time": service.start_time,
+			"end_time": service.end_time,
+		})
+	return support_days
+
+
+def set_service_level_agreement_variance(doctype, doc=None):
+
+	filters = {"status": "Open", "agreement_status": "Ongoing"}
+
+	if doc:
+		filters = {"name": doc}
+
+	for entry in frappe.get_all(doctype, filters=filters):
+		current_doc = frappe.get_doc(doctype, entry.name)
+		current_time = frappe.flags.current_time or now_datetime(current_doc.get("owner"))
+		apply_sla_for_resolution = frappe.db.get_value("Service Level Agreement", current_doc.service_level_agreement,
+			"apply_sla_for_resolution")
+
+		if not current_doc.first_responded_on: # first_responded_on set when first reply is sent to customer
+			variance = round(time_diff_in_seconds(current_doc.response_by, current_time), 2)
+			frappe.db.set_value(current_doc.doctype, current_doc.name, "response_by_variance", variance, update_modified=False)
+
+			if variance < 0:
+				frappe.db.set_value(current_doc.doctype, current_doc.name, "agreement_status", "Failed", update_modified=False)
+
+		if apply_sla_for_resolution and not current_doc.get("resolution_date"): # resolution_date set when issue has been closed
+			variance = round(time_diff_in_seconds(current_doc.resolution_by, current_time), 2)
+			frappe.db.set_value(current_doc.doctype, current_doc.name, "resolution_by_variance", variance, update_modified=False)
+
+			if variance < 0:
+				frappe.db.set_value(current_doc.doctype, current_doc.name, "agreement_status", "Failed", update_modified=False)
+
+
+def set_user_resolution_time(doc, meta):
+	# total time taken by a user to close the issue apart from wait_time
+	if not meta.has_field("user_resolution_time"):
+		return
+
+	communications = frappe.get_all("Communication", filters={
+			"reference_doctype": doc.doctype,
+			"reference_name": doc.name
+		}, fields=["sent_or_received", "name", "creation"], order_by="creation")
+
+	pending_time = []
+	for i in range(len(communications)):
+		if communications[i].sent_or_received == "Received" and communications[i-1].sent_or_received == "Sent":
+			wait_time = time_diff_in_seconds(communications[i].creation, communications[i-1].creation)
+			if wait_time > 0:
+				pending_time.append(wait_time)
+
+	total_pending_time = sum(pending_time)
+	resolution_time_in_secs = time_diff_in_seconds(doc.resolution_date, doc.creation)
+	doc.user_resolution_time = resolution_time_in_secs - total_pending_time
+
+
+def change_service_level_agreement_and_priority(self):
+	if self.service_level_agreement and frappe.db.exists("Issue", self.name) and \
+		frappe.db.get_single_value("Support Settings", "track_service_level_agreement"):
+
+		if not self.priority == frappe.db.get_value("Issue", self.name, "priority"):
+			self.set_response_and_resolution_time(priority=self.priority, service_level_agreement=self.service_level_agreement)
+			frappe.msgprint(_("Priority has been changed to {0}.").format(self.priority))
+
+		if not self.service_level_agreement == frappe.db.get_value("Issue", self.name, "service_level_agreement"):
+			self.set_response_and_resolution_time(priority=self.priority, service_level_agreement=self.service_level_agreement)
+			frappe.msgprint(_("Service Level Agreement has been changed to {0}.").format(self.service_level_agreement))
+
+
+def get_priority(doc):
+	service_level_agreement = frappe.get_doc("Service Level Agreement", doc.service_level_agreement)
+	priority = service_level_agreement.get_service_level_agreement_priority(doc.priority)
+	priority.update({
+		"support_and_resolution": service_level_agreement.support_and_resolution,
+		"holiday_list": service_level_agreement.holiday_list
+	})
+	return priority
+
+
+def reset_service_level_agreement(doc, reason, user):
+	if not frappe.db.get_single_value("Support Settings", "allow_resetting_service_level_agreement"):
+		frappe.throw(_("Allow Resetting Service Level Agreement from Support Settings."))
+
+	frappe.get_doc({
+		"doctype": "Comment",
+		"comment_type": "Info",
+		"reference_doctype": doc.doctype,
+		"reference_name": doc.name,
+		"comment_email": user,
+		"content": " resetted Service Level Agreement - {0}".format(_(reason)),
+	}).insert(ignore_permissions=True)
+
+	doc.service_level_agreement_creation = now_datetime(doc.get("owner"))
+	doc.set_response_and_resolution_time(priority=doc.priority, service_level_agreement=doc.service_level_agreement)
+	doc.agreement_status = "Ongoing"
+	doc.save()
+
+
+def reset_metrics(doc, meta):
+	if meta.has_field("resolution_date"):
+		doc.resolution_date = None
+
+	if not meta.has_field("resolution_time"):
+		doc.resolution_time = None
+
+	if not meta.has_field("user_resolution_time"):
+		doc.user_resolution_time = None
+
+	if meta.has_field("agreement_status"):
+		doc.agreement_status = "Ongoing"
+
+
+def set_resolution_time(doc, meta):
+	# total time taken from issue creation to closing
+	if not meta.has_field("resolution_time"):
+		return
+
+	doc.resolution_time = time_diff_in_seconds(doc.resolution_date, doc.creation)
+
+
+# called via hooks on communication update
+def update_hold_time(doc, status):
+	parent = get_parent_doc(doc)
+	if not parent:
+		return
+
+	if doc.communication_type == "Comment":
+		return
+
+	status_field = parent.meta.get_field("status")
+	if status_field:
+		options = (status_field.options or "").splitlines()
+
+		# if status has a "Replied" option, then handle hold time
+		if ("Replied" in options) and doc.sent_or_received == "Received":
+			meta = frappe.get_meta(parent.doctype)
+			handle_hold_time(parent, meta, 'Replied')
+
+
+def handle_hold_time(doc, meta, status):
+	if meta.has_field("service_level_agreement") and doc.service_level_agreement:
+		# set response and resolution variance as None as the issue is on Hold for status as Replied
+		hold_statuses = [entry.status for entry in frappe.db.get_all("Pause SLA On Status", filters={
+				"parent": doc.service_level_agreement
+			}, fields=["status"])]
+
+		if not hold_statuses:
+			return
+
+		if meta.has_field("status") and doc.status in hold_statuses and status not in hold_statuses:
+			apply_hold_status(doc, meta)
+
+		# calculate hold time when status is changed from any hold status to any non-hold status
+		if meta.has_field("status") and doc.status not in hold_statuses and status in hold_statuses:
+			reset_hold_status_and_update_hold_time(doc, meta)
+
+
+def apply_hold_status(doc, meta):
+	update_values = {'on_hold_since': frappe.flags.current_time or now_datetime(doc.get("owner"))}
+
+	if meta.has_field("first_responded_on") and not doc.first_responded_on:
+		update_values['response_by'] = None
+		update_values['response_by_variance'] = 0
+
+	update_values['resolution_by'] = None
+	update_values['resolution_by_variance'] = 0
+
+	doc.db_set(update_values)
+
+
+def reset_hold_status_and_update_hold_time(doc, meta):
+	hold_time = doc.total_hold_time if meta.has_field("total_hold_time") and doc.total_hold_time else 0
+	now_time = frappe.flags.current_time or now_datetime(doc.get("owner"))
+	last_hold_time = 0
+	update_values = {}
+
+	if meta.has_field("on_hold_since") and doc.on_hold_since:
+		# last_hold_time will be added to the sla variables
+		last_hold_time = time_diff_in_seconds(now_time, doc.on_hold_since)
+		update_values['total_hold_time'] = hold_time + last_hold_time
+
+	# re-calculate SLA variables after issue changes from any hold status to any non-hold status
+	start_date_time = get_datetime(doc.get("service_level_agreement_creation") or doc.creation)
+	priority = get_priority(doc)
+	now_time = frappe.flags.current_time or now_datetime(doc.get("owner"))
+
+	# add hold time to response by variance
+	if meta.has_field("first_responded_on") and not doc.first_responded_on:
+		response_by = get_expected_time_for(parameter="response", service_level=priority, start_date_time=start_date_time)
+		response_by = add_to_date(response_by, seconds=round(last_hold_time))
+		response_by_variance = round(time_diff_in_seconds(response_by, now_time))
+
+		update_values['response_by'] = response_by
+		update_values['response_by_variance'] = response_by_variance + last_hold_time
+
+	# add hold time to resolution by variance
+	if frappe.db.get_value("Service Level Agreement", doc.service_level_agreement, "apply_sla_for_resolution"):
+		resolution_by = get_expected_time_for(parameter="resolution", service_level=priority, start_date_time=start_date_time)
+		resolution_by = add_to_date(resolution_by, seconds=round(last_hold_time))
+		resolution_by_variance = round(time_diff_in_seconds(resolution_by, now_time))
+
+		update_values['resolution_by'] = resolution_by
+		update_values['resolution_by_variance'] = resolution_by_variance + last_hold_time
+
+	update_values['on_hold_since'] = None
+
+	doc.db_set(update_values)
+
+
+def get_service_level_agreement_fields():
+	return [
+		{
+			"collapsible": 1,
+			"fieldname": "service_level_section",
+			"fieldtype": "Section Break",
+			"label": "Service Level"
+		},
+		{
+			"fieldname": "service_level_agreement",
+			"fieldtype": "Link",
+			"label": "Service Level Agreement",
+			"options": "Service Level Agreement"
+		},
+		{
+			"fieldname": "priority",
+			"fieldtype": "Link",
+			"label": "Priority",
+			"options": "Issue Priority"
+		},
+		{
+			"fieldname": "response_by",
+			"fieldtype": "Datetime",
+			"label": "Response By",
+			"read_only": 1
+		},
+		{
+			"fieldname": "response_by_variance",
+			"fieldtype": "Duration",
+			"hide_seconds": 1,
+			"label": "Response By Variance",
+			"read_only": 1
+		},
+		{
+			"fieldname": "first_responded_on",
+			"fieldtype": "Datetime",
+			"label": "First Responded On",
+			"read_only": 1
+		},
+		{
+			"fieldname": "on_hold_since",
+			"fieldtype": "Datetime",
+			"hidden": 1,
+			"label": "On Hold Since",
+			"read_only": 1
+		},
+		{
+			"fieldname": "total_hold_time",
+			"fieldtype": "Duration",
+			"label": "Total Hold Time",
+			"read_only": 1
+		},
+		{
+			"fieldname": "cb",
+			"fieldtype": "Column Break",
+			"read_only": 1
+		},
+		{
+			"default": "Ongoing",
+			"fieldname": "agreement_status",
+			"fieldtype": "Select",
+			"label": "Service Level Agreement Status",
+			"options": "Ongoing\nFulfilled\nFailed",
+			"read_only": 1
+		},
+		{
+			"fieldname": "resolution_by",
+			"fieldtype": "Datetime",
+			"label": "Resolution By",
+			"read_only": 1
+		},
+		{
+			"fieldname": "resolution_by_variance",
+			"fieldtype": "Duration",
+			"hide_seconds": 1,
+			"label": "Resolution By Variance",
+			"read_only": 1
+		},
+		{
+			"fieldname": "service_level_agreement_creation",
+			"fieldtype": "Datetime",
+			"hidden": 1,
+			"label": "Service Level Agreement Creation",
+			"read_only": 1
+		},
+		{
+			"depends_on": "eval:!doc.__islocal",
+			"fieldname": "resolution_date",
+			"fieldtype": "Datetime",
+			"label": "Resolution Date",
+			"no_copy": 1,
+			"read_only": 1
+		}
+	]
+
+
+def update_agreement_status_on_custom_status(doc):
+	# Update Agreement Fulfilled status using Custom Scripts for Custom Status
+
+	meta = frappe.get_meta(doc.doctype)
+	now_time = frappe.flags.current_time or now_datetime(doc.get("owner"))
+	if meta.has_field("first_responded_on") and not doc.first_responded_on:
+		# first_responded_on set when first reply is sent to customer
+		doc.response_by_variance = round(time_diff_in_seconds(doc.response_by, now_time), 2)
+
+	if meta.has_field("resolution_date") and not doc.resolution_date:
+		# resolution_date set when issue has been closed
+		doc.resolution_by_variance = round(time_diff_in_seconds(doc.resolution_by, now_time), 2)
+
+	if meta.has_field("agreement_status"):
+		doc.agreement_status = "Fulfilled" if doc.response_by_variance > 0 and doc.resolution_by_variance > 0 else "Failed"
+
+
+def update_agreement_status(doc, meta):
+	if meta.has_field("service_level_agreement") and meta.has_field("agreement_status") and \
+		doc.service_level_agreement and doc.agreement_status == "Ongoing":
+
+		apply_sla_for_resolution = frappe.db.get_value("Service Level Agreement", doc.service_level_agreement,
+			"apply_sla_for_resolution")
+
+		# if SLA is applied for resolution check for response and resolution, else only response
+		if apply_sla_for_resolution:
+			if meta.has_field("response_by_variance") and meta.has_field("resolution_by_variance"):
+				if cint(frappe.db.get_value(doc.doctype, doc.name, "response_by_variance")) < 0 or \
+					cint(frappe.db.get_value(doc.doctype, doc.name, "resolution_by_variance")) < 0:
+
+					doc.agreement_status = "Failed"
+				else:
+					doc.agreement_status = "Fulfilled"
+		else:
+			if meta.has_field("response_by_variance") and \
+				cint(frappe.db.get_value(doc.doctype, doc.name, "response_by_variance")) < 0:
+				doc.agreement_status = "Failed"
+			else:
+				doc.agreement_status = "Fulfilled"
+
+
+def is_holiday(date, holidays):
+	return getdate(date) in holidays
+
+
+def get_time_in_timedelta(time):
+	"""Converts datetime.time(10, 36, 55, 961454) to datetime.timedelta(seconds=38215)."""
+	import datetime
+	return datetime.timedelta(hours=time.hour, minutes=time.minute, seconds=time.second)
+
+
+def set_response_by_and_variance(doc, meta, start_date_time, priority):
+	if meta.has_field("response_by"):
+		doc.response_by = get_expected_time_for(parameter="response", service_level=priority, start_date_time=start_date_time)
+
+	if meta.has_field("response_by_variance"):
+		now_time = frappe.flags.current_time or now_datetime(doc.get("owner"))
+		doc.response_by_variance = round(time_diff_in_seconds(doc.response_by, now_time), 2)
+
+def set_resolution_by_and_variance(doc, meta, start_date_time, priority):
+	if meta.has_field("resolution_by"):
+		doc.resolution_by = get_expected_time_for(parameter="resolution", service_level=priority, start_date_time=start_date_time)
+
+	if meta.has_field("resolution_by_variance"):
+		now_time = frappe.flags.current_time or now_datetime(doc.get("owner"))
+		doc.resolution_by_variance = round(time_diff_in_seconds(doc.resolution_by, now_time), 2)
+
+
+def now_datetime(user):
+	dt = convert_utc_to_user_timezone(datetime.utcnow(), user)
+	return dt.replace(tzinfo=None)
+
+
+def convert_utc_to_user_timezone(utc_timestamp, user):
+	from pytz import timezone, UnknownTimeZoneError
+
+	user_tz = get_tz(user)
+	utcnow = timezone('UTC').localize(utc_timestamp)
+	try:
+		return utcnow.astimezone(timezone(user_tz))
+	except UnknownTimeZoneError:
+		return utcnow
+
+
+def get_tz(user):
+	return frappe.db.get_value("User", user, "time_zone") or get_time_zone()
+
+
+@frappe.whitelist()
+def get_user_time(user, to_string=False):
+	return get_datetime_str(now_datetime(user)) if to_string else now_datetime(user)
+
+
+@frappe.whitelist()
+def get_sla_doctypes():
+	doctypes = []
+	data = frappe.get_list('Service Level Agreement',
+		{'enabled': 1},
+		['document_type'],
+		distinct=1
+	)
+
+	for entry in data:
+		doctypes.append(entry.document_type)
+
+	return doctypes
diff --git a/erpnext/support/doctype/service_level_agreement/test_service_level_agreement.py b/erpnext/support/doctype/service_level_agreement/test_service_level_agreement.py
index 07ef368..2a8446d 100644
--- a/erpnext/support/doctype/service_level_agreement/test_service_level_agreement.py
+++ b/erpnext/support/doctype/service_level_agreement/test_service_level_agreement.py
@@ -5,19 +5,20 @@
 
 import frappe
 import unittest
-from erpnext.hr.doctype.employee_group.test_employee_group import make_employee_group
+import datetime
+from frappe.utils import flt
 from erpnext.support.doctype.issue_priority.test_issue_priority import make_priorities
+from erpnext.support.doctype.service_level_agreement.service_level_agreement import get_service_level_agreement_fields
 
 class TestServiceLevelAgreement(unittest.TestCase):
 	def setUp(self):
-		frappe.db.sql("delete from `tabService Level Agreement`")
 		frappe.db.set_value("Support Settings", None, "track_service_level_agreement", 1)
+		frappe.db.sql("delete from `tabLead`")
 
 	def test_service_level_agreement(self):
 		# Default Service Level Agreement
 		create_default_service_level_agreement = create_service_level_agreement(default_service_level_agreement=1,
-			holiday_list="__Test Holiday List", employee_group="_Test Employee Group",
-			entity_type=None, entity=None, response_time=14400, resolution_time=21600)
+			holiday_list="__Test Holiday List", entity_type=None, entity=None, response_time=14400, resolution_time=21600)
 
 		get_default_service_level_agreement = get_service_level_agreement(default_service_level_agreement=1)
 
@@ -29,8 +30,8 @@
 		# Service Level Agreement for Customer
 		customer = create_customer()
 		create_customer_service_level_agreement = create_service_level_agreement(default_service_level_agreement=0,
-			holiday_list="__Test Holiday List", employee_group="_Test Employee Group",
-			entity_type="Customer", entity=customer, response_time=7200, resolution_time=10800)
+			holiday_list="__Test Holiday List", entity_type="Customer", entity=customer,
+			response_time=7200, resolution_time=10800)
 		get_customer_service_level_agreement = get_service_level_agreement(entity_type="Customer", entity=customer)
 
 		self.assertEqual(create_customer_service_level_agreement.name, get_customer_service_level_agreement.name)
@@ -41,8 +42,8 @@
 		# Service Level Agreement for Customer Group
 		customer_group = create_customer_group()
 		create_customer_group_service_level_agreement = create_service_level_agreement(default_service_level_agreement=0,
-			holiday_list="__Test Holiday List", employee_group="_Test Employee Group",
-			entity_type="Customer Group", entity=customer_group, response_time=7200, resolution_time=10800)
+			holiday_list="__Test Holiday List", entity_type="Customer Group", entity=customer_group,
+			response_time=7200, resolution_time=10800)
 		get_customer_group_service_level_agreement = get_service_level_agreement(entity_type="Customer Group", entity=customer_group)
 
 		self.assertEqual(create_customer_group_service_level_agreement.name, get_customer_group_service_level_agreement.name)
@@ -53,7 +54,7 @@
 		# Service Level Agreement for Territory
 		territory = create_territory()
 		create_territory_service_level_agreement = create_service_level_agreement(default_service_level_agreement=0,
-			holiday_list="__Test Holiday List", employee_group="_Test Employee Group",
+			holiday_list="__Test Holiday List",
 			entity_type="Territory", entity=territory, response_time=7200, resolution_time=10800)
 		get_territory_service_level_agreement = get_service_level_agreement(entity_type="Territory", entity=territory)
 
@@ -62,64 +63,223 @@
 		self.assertEqual(create_territory_service_level_agreement.entity, get_territory_service_level_agreement.entity)
 		self.assertEqual(create_territory_service_level_agreement.default_service_level_agreement, get_territory_service_level_agreement.default_service_level_agreement)
 
+	def test_custom_field_creation_for_sla_on_standard_dt(self):
+		# Default Service Level Agreement
+		doctype = "Lead"
+		lead_sla = create_service_level_agreement(
+			default_service_level_agreement=1,
+			holiday_list="__Test Holiday List",
+			entity_type=None, entity=None,
+			response_time=14400, resolution_time=21600,
+			doctype=doctype,
+			sla_fulfilled_on=[{"status": "Converted"}]
+		)
 
-def get_service_level_agreement(default_service_level_agreement=None, entity_type=None, entity=None):
+		# check default SLA for lead
+		default_sla = get_service_level_agreement(default_service_level_agreement=1, doctype=doctype)
+		self.assertEqual(lead_sla.name, default_sla.name)
+
+		# check SLA custom fields created for leads
+		sla_fields = get_service_level_agreement_fields()
+		meta = frappe.get_meta(doctype, cached=False)
+
+		for field in sla_fields:
+			self.assertTrue(meta.has_field(field.get("fieldname")))
+
+	def test_docfield_creation_for_sla_on_custom_dt(self):
+		doctype = create_custom_doctype()
+		sla = create_service_level_agreement(
+			default_service_level_agreement=1,
+			holiday_list="__Test Holiday List",
+			entity_type=None, entity=None,
+			response_time=14400, resolution_time=21600,
+			doctype=doctype.name
+		)
+
+		# check default SLA for custom dt
+		default_sla = get_service_level_agreement(default_service_level_agreement=1, doctype=doctype.name)
+		self.assertEqual(sla.name, default_sla.name)
+
+		# check SLA docfields created
+		sla_fields = get_service_level_agreement_fields()
+		meta = frappe.get_meta(doctype.name, cached=False)
+
+		for field in sla_fields:
+			self.assertTrue(meta.has_field(field.get("fieldname")))
+
+	def test_sla_application(self):
+		# Default Service Level Agreement
+		doctype = "Lead"
+		lead_sla = create_service_level_agreement(
+			default_service_level_agreement=1,
+			holiday_list="__Test Holiday List",
+			entity_type=None, entity=None,
+			response_time=14400, resolution_time=21600,
+			doctype=doctype,
+			sla_fulfilled_on=[{"status": "Converted"}]
+		)
+
+		# make lead with default SLA
+		creation = datetime.datetime(2019, 3, 4, 12, 0)
+		lead = make_lead(creation=creation, index=1)
+
+		self.assertEqual(lead.service_level_agreement, lead_sla.name)
+		self.assertEqual(lead.response_by, datetime.datetime(2019, 3, 4, 16, 0))
+		self.assertEqual(lead.resolution_by, datetime.datetime(2019, 3, 4, 18, 0))
+
+		frappe.flags.current_time = datetime.datetime(2019, 3, 4, 15, 0)
+		lead.reload()
+		lead.status = 'Converted'
+		lead.save()
+
+		self.assertEqual(lead.agreement_status, 'Fulfilled')
+
+	def test_hold_time(self):
+		doctype = "Lead"
+		create_service_level_agreement(
+			default_service_level_agreement=1,
+			holiday_list="__Test Holiday List",
+			entity_type=None, entity=None,
+			response_time=14400, resolution_time=21600,
+			doctype=doctype,
+			sla_fulfilled_on=[{"status": "Converted"}],
+			pause_sla_on=[{"status": "Replied"}]
+		)
+
+		creation = datetime.datetime(2020, 3, 4, 4, 0)
+		lead = make_lead(creation, index=2)
+
+		frappe.flags.current_time = datetime.datetime(2020, 3, 4, 4, 15)
+		lead.reload()
+		lead.status = 'Replied'
+		lead.save()
+
+		lead.reload()
+		self.assertEqual(lead.on_hold_since, frappe.flags.current_time)
+
+		frappe.flags.current_time = datetime.datetime(2020, 3, 4, 5, 5)
+		lead.reload()
+		lead.status = 'Converted'
+		lead.save()
+
+		lead.reload()
+		self.assertEqual(flt(lead.total_hold_time, 2), 3000)
+		self.assertEqual(lead.resolution_by, datetime.datetime(2020, 3, 4, 16, 50))
+
+	def test_failed_sla_for_response_only(self):
+		doctype = "Lead"
+		create_service_level_agreement(
+			default_service_level_agreement=1,
+			holiday_list="__Test Holiday List",
+			entity_type=None, entity=None,
+			response_time=14400,
+			doctype=doctype,
+			sla_fulfilled_on=[{"status": "Replied"}],
+			pause_sla_on=[],
+			apply_sla_for_resolution=0
+		)
+
+		creation = datetime.datetime(2019, 3, 4, 12, 0)
+		lead = make_lead(creation=creation, index=1)
+		self.assertEqual(lead.response_by, datetime.datetime(2019, 3, 4, 16, 0))
+
+		# failed with response time only
+		frappe.flags.current_time = datetime.datetime(2019, 3, 4, 16, 5)
+		lead.reload()
+		lead.status = 'Replied'
+		lead.save()
+
+		lead.reload()
+		self.assertEqual(lead.agreement_status, 'Failed')
+
+	def test_fulfilled_sla_for_response_only(self):
+		doctype = "Lead"
+		lead_sla = create_service_level_agreement(
+			default_service_level_agreement=1,
+			holiday_list="__Test Holiday List",
+			entity_type=None, entity=None,
+			response_time=14400,
+			doctype=doctype,
+			sla_fulfilled_on=[{"status": "Replied"}],
+			apply_sla_for_resolution=0
+		)
+
+		# fulfilled with response time only
+		creation = datetime.datetime(2019, 3, 4, 12, 0)
+		lead = make_lead(creation=creation, index=2)
+
+		self.assertEqual(lead.service_level_agreement, lead_sla.name)
+		self.assertEqual(lead.response_by, datetime.datetime(2019, 3, 4, 16, 0))
+
+		frappe.flags.current_time = datetime.datetime(2019, 3, 4, 15, 30)
+		lead.reload()
+		lead.status = 'Replied'
+		lead.save()
+
+		lead.reload()
+		self.assertEqual(lead.agreement_status, 'Fulfilled')
+
+	def tearDown(self):
+		for d in frappe.get_all("Service Level Agreement"):
+			frappe.delete_doc("Service Level Agreement", d.name, force=1)
+
+
+def get_service_level_agreement(default_service_level_agreement=None, entity_type=None, entity=None, doctype="Issue"):
 	if default_service_level_agreement:
-		filters = {"default_service_level_agreement": default_service_level_agreement}
+		filters = {"default_service_level_agreement": default_service_level_agreement, "document_type": doctype}
 	else:
 		filters = {"entity_type": entity_type, "entity": entity}
 
 	service_level_agreement = frappe.get_doc("Service Level Agreement", filters)
 	return service_level_agreement
 
-def create_service_level_agreement(default_service_level_agreement, holiday_list, employee_group,
-	response_time, entity_type, entity, resolution_time):
+def create_service_level_agreement(default_service_level_agreement, holiday_list, response_time, entity_type,
+	entity, resolution_time=0, doctype="Issue", sla_fulfilled_on=[], pause_sla_on=[], apply_sla_for_resolution=1):
 
-	employee_group = make_employee_group()
 	make_holiday_list()
 	make_priorities()
 
-	service_level_agreement = frappe.get_doc({
+	if not sla_fulfilled_on:
+		sla_fulfilled_on = [
+			{"status": "Resolved"},
+			{"status": "Closed"}
+		]
+
+	pause_sla_on = [{"status": "Replied"}] if doctype == "Issue" else pause_sla_on
+
+	service_level_agreement = frappe._dict({
 		"doctype": "Service Level Agreement",
-		"enable": 1,
+		"enabled": 1,
+		"document_type": doctype,
 		"service_level": "__Test Service Level",
 		"default_service_level_agreement": default_service_level_agreement,
 		"default_priority": "Medium",
 		"holiday_list": holiday_list,
-		"employee_group": employee_group,
 		"entity_type": entity_type,
 		"entity": entity,
 		"start_date": frappe.utils.getdate(),
 		"end_date": frappe.utils.add_to_date(frappe.utils.getdate(), days=100),
+		"apply_sla_for_resolution": apply_sla_for_resolution,
 		"priorities": [
 			{
 				"priority": "Low",
 				"response_time": response_time,
-				"response_time_period": "Hour",
 				"resolution_time": resolution_time,
-				"resolution_time_period": "Hour",
 			},
 			{
 				"priority": "Medium",
 				"response_time": response_time,
 				"default_priority": 1,
-				"response_time_period": "Hour",
 				"resolution_time": resolution_time,
-				"resolution_time_period": "Hour",
 			},
 			{
 				"priority": "High",
 				"response_time": response_time,
-				"response_time_period": "Hour",
 				"resolution_time": resolution_time,
-				"resolution_time_period": "Hour",
 			}
 		],
-		"pause_sla_on": [
-			{
-				"status": "Replied"
-			}
-		],
+		"sla_fulfilled_on": sla_fulfilled_on,
+		"pause_sla_on": pause_sla_on,
 		"support_and_resolution": [
 			{
 				"workday": "Monday",
@@ -173,10 +333,13 @@
 	service_level_agreement_exists = frappe.db.exists("Service Level Agreement", filters)
 
 	if not service_level_agreement_exists:
-		service_level_agreement.insert(ignore_permissions=True)
-		return service_level_agreement
+		doc = frappe.get_doc(service_level_agreement).insert(ignore_permissions=True)
 	else:
-		return frappe.get_doc("Service Level Agreement", service_level_agreement_exists)
+		doc = frappe.get_doc("Service Level Agreement", service_level_agreement_exists)
+		doc.update(service_level_agreement)
+		doc.save()
+
+	return doc
 
 
 def create_customer():
@@ -219,19 +382,19 @@
 
 def create_service_level_agreements_for_issues():
 	create_service_level_agreement(default_service_level_agreement=1, holiday_list="__Test Holiday List",
-		employee_group="_Test Employee Group", entity_type=None, entity=None, response_time=14400, resolution_time=21600)
+		entity_type=None, entity=None, response_time=14400, resolution_time=21600)
 
 	create_customer()
 	create_service_level_agreement(default_service_level_agreement=0, holiday_list="__Test Holiday List",
-		employee_group="_Test Employee Group", entity_type="Customer", entity="_Test Customer", response_time=7200, resolution_time=10800)
+		entity_type="Customer", entity="_Test Customer", response_time=7200, resolution_time=10800)
 
 	create_customer_group()
 	create_service_level_agreement(default_service_level_agreement=0, holiday_list="__Test Holiday List",
-		employee_group="_Test Employee Group", entity_type="Customer Group", entity="_Test SLA Customer Group", response_time=7200, resolution_time=10800)
+		entity_type="Customer Group", entity="_Test SLA Customer Group", response_time=7200, resolution_time=10800)
 
 	create_territory()
 	create_service_level_agreement(default_service_level_agreement=0, holiday_list="__Test Holiday List",
-		employee_group="_Test Employee Group", entity_type="Territory", entity="_Test SLA Territory", response_time=7200, resolution_time=10800)
+		entity_type="Territory", entity="_Test SLA Territory", response_time=7200, resolution_time=10800)
 
 def make_holiday_list():
 	holiday_list = frappe.db.exists("Holiday List", "__Test Holiday List")
@@ -256,3 +419,55 @@
 				},
 			]
 		}).insert()
+
+def create_custom_doctype():
+	if not frappe.db.exists("DocType", "Test SLA on Custom Dt"):
+		doc = frappe.get_doc({
+				"doctype": "DocType",
+				"module": "Support",
+				"custom": 1,
+				"fields": [
+					{
+						"label": "Date",
+						"fieldname": "date",
+						"fieldtype": "Date"
+					},
+					{
+						"label": "Description",
+						"fieldname": "desc",
+						"fieldtype": "Long Text"
+					},
+					{
+						"label": "Email ID",
+						"fieldname": "email_id",
+						"fieldtype": "Link",
+						"options": "Customer"
+					},
+					{
+						"label": "Status",
+						"fieldname": "status",
+						"fieldtype": "Select",
+						"options": "Open\nReplied\nClosed"
+					}
+				],
+				"permissions": [{
+					"role": "System Manager",
+					"read": 1,
+					"write": 1
+				}],
+				"name": "Test SLA on Custom Dt",
+			})
+		doc.insert()
+		return doc
+	else:
+		return frappe.get_doc("DocType", "Test SLA on Custom Dt")
+
+def make_lead(creation=None, index=0):
+	return frappe.get_doc({
+		"doctype": "Lead",
+		"email_id": "test_lead1@example{0}.com".format(index),
+		"lead_name": "_Test Lead {0}".format(index),
+		"status": "Open",
+		"creation": creation,
+		"service_level_agreement_creation": creation
+	}).insert(ignore_permissions=True)
\ No newline at end of file
diff --git a/erpnext/support/doctype/service_level_priority/service_level_priority.json b/erpnext/support/doctype/service_level_priority/service_level_priority.json
index 65d5169..0367fc6 100644
--- a/erpnext/support/doctype/service_level_priority/service_level_priority.json
+++ b/erpnext/support/doctype/service_level_priority/service_level_priority.json
@@ -15,12 +15,13 @@
  ],
  "fields": [
   {
-   "columns": 2,
+   "columns": 1,
    "fieldname": "priority",
    "fieldtype": "Link",
    "in_list_view": 1,
    "label": "Priority",
-   "options": "Issue Priority"
+   "options": "Issue Priority",
+   "reqd": 1
   },
   {
    "fieldname": "sb_00",
@@ -32,7 +33,6 @@
    "fieldtype": "Duration",
    "hide_days": 1,
    "hide_seconds": 1,
-   "in_list_view": 1,
    "label": "Resolution Time"
   },
   {
@@ -58,12 +58,13 @@
    "hide_days": 1,
    "hide_seconds": 1,
    "in_list_view": 1,
-   "label": "First Response Time"
+   "label": "First Response Time",
+   "reqd": 1
   }
  ],
  "istable": 1,
  "links": [],
- "modified": "2020-06-10 12:45:47.545915",
+ "modified": "2021-05-29 19:52:51.733248",
  "modified_by": "Administrator",
  "module": "Support",
  "name": "Service Level Priority",
@@ -73,4 +74,4 @@
  "sort_field": "modified",
  "sort_order": "DESC",
  "track_changes": 1
-}
+}
\ No newline at end of file
diff --git a/erpnext/support/doctype/sla_fulfilled_on_status/__init__.py b/erpnext/support/doctype/sla_fulfilled_on_status/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/support/doctype/sla_fulfilled_on_status/__init__.py
diff --git a/erpnext/support/doctype/sla_fulfilled_on_status/sla_fulfilled_on_status.json b/erpnext/support/doctype/sla_fulfilled_on_status/sla_fulfilled_on_status.json
new file mode 100644
index 0000000..87124de
--- /dev/null
+++ b/erpnext/support/doctype/sla_fulfilled_on_status/sla_fulfilled_on_status.json
@@ -0,0 +1,31 @@
+{
+ "actions": [],
+ "creation": "2021-05-26 21:11:29.176369",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "status"
+ ],
+ "fields": [
+  {
+   "fieldname": "status",
+   "fieldtype": "Select",
+   "in_list_view": 1,
+   "label": "Status",
+   "reqd": 1
+  }
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2021-05-26 21:11:29.176369",
+ "modified_by": "Administrator",
+ "module": "Support",
+ "name": "SLA Fulfilled On Status",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/support/doctype/sla_fulfilled_on_status/sla_fulfilled_on_status.py b/erpnext/support/doctype/sla_fulfilled_on_status/sla_fulfilled_on_status.py
new file mode 100644
index 0000000..b0b5ffc
--- /dev/null
+++ b/erpnext/support/doctype/sla_fulfilled_on_status/sla_fulfilled_on_status.py
@@ -0,0 +1,8 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+# import frappe
+from frappe.model.document import Document
+
+class SLAFulfilledOnStatus(Document):
+	pass