feat: sla based on customer/group/territory

This commit is contained in:
Himanshu Warekar 2019-06-07 15:28:42 +05:30
parent db1d1197ea
commit 0674d16fee
8 changed files with 194 additions and 177 deletions

View File

@ -264,7 +264,6 @@ scheduler_events = {
"erpnext.projects.doctype.project.project.send_project_status_email_to_users", "erpnext.projects.doctype.project.project.send_project_status_email_to_users",
"erpnext.quality_management.doctype.quality_review.quality_review.review", "erpnext.quality_management.doctype.quality_review.quality_review.review",
"erpnext.support.doctype.service_level_agreement.service_level_agreement.check_agreement_status", "erpnext.support.doctype.service_level_agreement.service_level_agreement.check_agreement_status",
"erpnext.support.doctype.issue.issue.set_service_level_agreement_status"
], ],
"daily_long": [ "daily_long": [
"erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.update_latest_price_in_all_boms" "erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.update_latest_price_in_all_boms"

View File

@ -6,10 +6,11 @@ def execute():
priorities = frappe.get_meta("Issue").get_field("priority").options.split("\n") priorities = frappe.get_meta("Issue").get_field("priority").options.split("\n")
for priority in priorities: for priority in priorities:
frappe.get_doc({ if not frappe.db.exists("Issue Priority", priority):
"doctype": "Issue Priority", frappe.get_doc({
"name": priority "doctype": "Issue Priority",
}).insert(ignore_permissions=True) "name": priority
}).insert(ignore_permissions=True)
frappe.reload_doc("support", "doctype", "issue") frappe.reload_doc("support", "doctype", "issue")
frappe.reload_doc("support", "doctype", "service_level") frappe.reload_doc("support", "doctype", "service_level")

View File

@ -8,7 +8,7 @@ from frappe import _
from frappe.utils.nestedset import NestedSet from frappe.utils.nestedset import NestedSet
class CustomerGroup(NestedSet): class CustomerGroup(NestedSet):
nsm_parent_field = 'parent_customer_group'; nsm_parent_field = 'parent_customer_group'
def on_update(self): def on_update(self):
self.validate_name_with_customer() self.validate_name_with_customer()

View File

@ -1,6 +1,25 @@
frappe.ui.form.on("Issue", { frappe.ui.form.on("Issue", {
onload: function(frm) { onload: function(frm) {
frm.email_field = "raised_by"; frm.email_field = "raised_by";
if (frm.doc.service_level_agreement) {
frappe.call({
method: "erpnext.support.doctype.service_level_agreement.service_level_agreement.get_service_level_agreement_priorities",
args: {
name: frm.doc.service_level_agreement,
},
callback: function (r) {
if (r && r.message) {
frm.set_query('priority', function() {
return {
filters: {
"name": ["in", r.message],
}
};
});
}
}
});
}
}, },
refresh: function (frm) { refresh: function (frm) {
@ -43,24 +62,6 @@ frappe.ui.form.on("Issue", {
frm.save(); frm.save();
}); });
} }
frappe.call({
method: "erpnext.support.doctype.service_level_agreement.service_level_agreement.get_service_level_agreement_priorities",
args: {
name: frm.doc.service_level_agreement,
},
callback: function (r) {
if (r && r.message) {
frm.set_query('priority', function() {
return {
filters: {
"name": ["in", r.message],
}
};
});
}
}
});
}, },
priority: function(frm) { priority: function(frm) {

View File

@ -77,11 +77,9 @@ class Issue(Document):
def update_agreement_status(self): def update_agreement_status(self):
current_time = frappe.flags.current_time or now_datetime() current_time = frappe.flags.current_time or now_datetime()
if self.service_level_agreement and self.agreement_fulfilled == "Ongoing":
response_time_diff = round(time_diff_in_hours(self.response_by, current_time), 2)
resolution_time_diff = round(time_diff_in_hours(self.resolution_by, current_time), 2)
if response_time_diff < 0 or resolution_time_diff < 0: if self.service_level_agreement and self.agreement_fulfilled == "Ongoing":
if self.response_by_variance < 0 or self.resolution_by_variance < 0:
self.agreement_fulfilled = "Failed" self.agreement_fulfilled = "Failed"
else: else:
self.agreement_fulfilled = "Fulfilled" self.agreement_fulfilled = "Fulfilled"
@ -232,33 +230,27 @@ def get_expected_time_for(parameter, service_level, start_date_time):
return current_date_time return current_date_time
def set_service_level_agreement_status():
issues = frappe.get_list("Issue", filters={"status": "Open", "agreement_fulfilled": "Ongoing"})
for issue in issues:
doc = frappe.get_doc("Issue", issue.name)
if doc.service_level_agreement and doc.agreement_fulfilled == "Ongoing":
response_time_diff = round(time_diff_in_hours(doc.response_by, now_datetime()), 2)
resolution_time_diff = round(time_diff_in_hours(doc.resolution_by, now_datetime()), 2)
if response_time_diff < 0 or resolution_time_diff < 0:
frappe.db.set_value("Issue", doc.name, "agreement_fulfilled", "Failed")
else:
frappe.db.set_value("Issue", doc.name, "agreement_fulfilled", "Fulfilled")
def set_service_level_agreement_variance(issue=None): def set_service_level_agreement_variance(issue=None):
filters = {"status": "Open", "agreement_fulfilled": "Ongoing"} current_time = frappe.flags.current_time or now_datetime()
filters = {"status": "Open", "agreement_fulfilled": "Ongoing"}
if issue: if issue:
filters = {"name": issue} filters = {"name": issue}
issues = frappe.get_list("Issue", filters=filters) for issue in frappe.get_list("Issue", filters=filters):
for issue in issues:
doc = frappe.get_doc("Issue", issue.name) doc = frappe.get_doc("Issue", issue.name)
if not doc.first_responded_on:
variance = round(time_diff_in_hours(doc.response_by, now_datetime()), 2) if not doc.first_responded_on: # first_responded_on set when first reply is sent to customer
variance = round(time_diff_in_hours(doc.response_by, current_time), 2)
frappe.db.set_value("Issue", doc.name, "response_by_variance", variance) frappe.db.set_value("Issue", doc.name, "response_by_variance", variance)
if not doc.resolution_date: if variance < 0:
variance = round(time_diff_in_hours(doc.resolution_by, now_datetime()), 2) frappe.db.set_value("Issue", doc.name, "agreement_fulfilled", "Failed")
if not doc.resolution_date: # resolution_date set when issue has been closed
variance = round(time_diff_in_hours(doc.resolution_by, current_time), 2)
frappe.db.set_value("Issue", doc.name, "resolution_by_variance", variance) frappe.db.set_value("Issue", doc.name, "resolution_by_variance", variance)
if variance < 0:
frappe.db.set_value("Issue", doc.name, "agreement_fulfilled", "Failed")
def get_list_context(context=None): def get_list_context(context=None):
return { return {

View File

@ -6,12 +6,15 @@
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [ "field_order": [
"service_level", "service_level",
"customer",
"default_service_level_agreement", "default_service_level_agreement",
"holiday_list", "holiday_list",
"column_break_2", "column_break_2",
"employee_group", "employee_group",
"default_priority", "default_priority",
"apply_to_section",
"apply_to",
"column_break_10",
"entity",
"agreement_details_section", "agreement_details_section",
"start_date", "start_date",
"active", "active",
@ -23,16 +26,6 @@
"support_and_resolution" "support_and_resolution"
], ],
"fields": [ "fields": [
{
"depends_on": "eval: !doc.default_service_level_agreement;",
"fieldname": "customer",
"fieldtype": "Link",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Customer",
"options": "Customer",
"set_only_once": 1
},
{ {
"default": "0", "default": "0",
"depends_on": "eval: !doc.customer;", "depends_on": "eval: !doc.customer;",
@ -133,9 +126,34 @@
"fieldtype": "Check", "fieldtype": "Check",
"label": "Active", "label": "Active",
"read_only": 1 "read_only": 1
},
{
"collapsible_depends_on": "eval: !doc.default_service_level_agreement;",
"fieldname": "apply_to_section",
"fieldtype": "Section Break",
"label": "Apply To"
},
{
"fieldname": "apply_to",
"fieldtype": "Select",
"in_standard_filter": 1,
"label": "Apply To",
"options": "\nCustomer\nCustomer Group\nTerritory"
},
{
"fieldname": "column_break_10",
"fieldtype": "Column Break"
},
{
"fieldname": "entity",
"fieldtype": "Dynamic Link",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Entity",
"options": "apply_to"
} }
], ],
"modified": "2019-06-06 12:56:24.545060", "modified": "2019-06-07 00:30:34.755416",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Support", "module": "Support",
"name": "Service Level Agreement", "name": "Service Level Agreement",

View File

@ -45,14 +45,14 @@ def check_agreement_status():
def get_active_service_level_agreement_for(priority, customer=None, service_level_agreement=None): def get_active_service_level_agreement_for(priority, customer=None, service_level_agreement=None):
filters = [ filters = [
["Service Level Agreement", "active", "=", 1] ["Service Level Agreement", "active", "=", 1],
] ]
if priority: if priority:
filters.append(["Service Level Priority", "priority", "=", priority]) filters.append(["Service Level Priority", "priority", "=", priority])
or_filters = [ or_filters = [
["Service Level Agreement", "customer", "=", customer] ["Service Level Agreement", "entity", "in", [customer, get_customer_group(customer), get_customer_territory(customer)]]
] ]
if service_level_agreement: if service_level_agreement:
or_filters = [ or_filters = [
@ -62,10 +62,18 @@ def get_active_service_level_agreement_for(priority, customer=None, service_leve
or_filters.append(["Service Level Agreement", "default_service_level_agreement", "=", 1]) or_filters.append(["Service Level Agreement", "default_service_level_agreement", "=", 1])
agreement = frappe.get_list("Service Level Agreement", filters=filters, or_filters=or_filters, agreement = frappe.get_list("Service Level Agreement", filters=filters, or_filters=or_filters,
fields=["name", "default_priority", "customer"]) fields=["name", "default_priority"], debug=True)
return agreement[0] if agreement else None return agreement[0] if agreement else None
@frappe.whitelist() @frappe.whitelist()
def get_service_level_agreement_priorities(name): def get_service_level_agreement_priorities(name):
return [priority.priority for priority in frappe.get_list("Service Level Priority", filters={"parent": name}, fields=["priority"])] return [priority.priority for priority in frappe.get_list("Service Level Priority", filters={"parent": name}, fields=["priority"])]
def get_customer_group(customer):
if customer:
return frappe.db.get_value("Customer", customer, "customer_group")
def get_customer_territory(customer):
if customer:
return frappe.db.get_value("Customer", customer, "territory")

View File

@ -10,136 +10,116 @@ from erpnext.support.doctype.service_level.test_service_level import make_servic
class TestServiceLevelAgreement(unittest.TestCase): class TestServiceLevelAgreement(unittest.TestCase):
def test_service_level_agreement(self): def test_service_level_agreement(self):
test_make_service_level_agreement = make_service_level_agreement() make_service_level()
test_get_service_level_agreement = get_service_level_agreement()
self.assertEqual(test_make_service_level_agreement.name, test_get_service_level_agreement.name)
self.assertEqual(test_make_service_level_agreement.customer, test_get_service_level_agreement.customer)
self.assertEqual(test_make_service_level_agreement.default_service_level_agreement, test_get_service_level_agreement.default_service_level_agreement)
def make_service_level_agreement(): # Default Service Level Agreement
make_service_level() create_default_service_level_agreement = create_service_level_agreement(default_service_level_agreement=1,
service_level="__Test Service Level", holiday_list="__Test Holiday List", employee_group="_Test Employee Group",
apply_to=None, entity=None, response_time=4, resolution_time=6)
get_default_service_level_agreement = get_service_level_agreement(default_service_level_agreement=1)
# Default Service Level Agreement self.assertEqual(create_default_service_level_agreement.name, get_default_service_level_agreement.name)
default_service_level_agreement = frappe.get_doc({ self.assertEqual(create_default_service_level_agreement.apply_to, get_default_service_level_agreement.apply_to)
"doctype": "Service Level Agreement", self.assertEqual(create_default_service_level_agreement.entity, get_default_service_level_agreement.entity)
"service_level_agreement_name": "Default Service Level Agreement", self.assertEqual(create_default_service_level_agreement.default_service_level_agreement, get_default_service_level_agreement.default_service_level_agreement)
"default_service_level_agreement": 1,
"service_level": "__Test Service Level",
"holiday_list": "__Test Holiday List",
"employee_group": "_Test Employee Group",
"start_date": frappe.utils.getdate(),
"end_date": frappe.utils.add_to_date(frappe.utils.getdate(), days=100),
"priorities": [
{
"priority": "Low",
"response_time": 4,
"response_time_period": "Hour",
"resolution_time": 6,
"resolution_time_period": "Hour",
},
{
"priority": "Medium",
"response_time": 4,
"default_priority": 1,
"response_time_period": "Hour",
"resolution_time": 6,
"resolution_time_period": "Hour",
},
{
"priority": "High",
"response_time": 4,
"response_time_period": "Hour",
"resolution_time": 6,
"resolution_time_period": "Hour",
}
],
"support_and_resolution": [
{
"workday": "Monday",
"start_time": "10:00:00",
"end_time": "18:00:00",
},
{
"workday": "Tuesday",
"start_time": "10:00:00",
"end_time": "18:00:00",
},
{
"workday": "Wednesday",
"start_time": "10:00:00",
"end_time": "18:00:00",
},
{
"workday": "Thursday",
"start_time": "10:00:00",
"end_time": "18:00:00",
},
{
"workday": "Friday",
"start_time": "10:00:00",
"end_time": "18:00:00",
},
{
"workday": "Saturday",
"start_time": "10:00:00",
"end_time": "18:00:00",
},
{
"workday": "Sunday",
"start_time": "10:00:00",
"end_time": "18:00:00",
}
]
})
default_service_level_agreement_exists = frappe.db.exists("Service Level Agreement", "SLA-Default Service Level Agreement")
if not default_service_level_agreement_exists: # Service Level Agreement for Customer
default_service_level_agreement.insert(ignore_permissions=True) customer = frappe.get_doc({
"doctype": "Customer",
"customer_name": "_Test Customer",
"customer_group": "Commercial",
"customer_type": "Individual",
"territory": "Rest Of The World"
})
if not frappe.db.exists("Customer", "_Test Customer"):
customer.insert(ignore_permissions=True)
else:
customer = frappe.get_doc("Customer", "_Test Customer")
customer = frappe.get_doc({ create_customer_service_level_agreement = create_service_level_agreement(default_service_level_agreement=0,
"doctype": "Customer", service_level="__Test Service Level", holiday_list="__Test Holiday List", employee_group="_Test Employee Group",
"customer_name": "_Test Customer", apply_to="Customer", entity=customer.name, response_time=2, resolution_time=3)
"customer_group": "Commercial", get_customer_service_level_agreement = get_service_level_agreement(apply_to="Customer", entity=customer.name)
"customer_type": "Individual",
"territory": "Rest Of The World" self.assertEqual(create_customer_service_level_agreement.name, get_customer_service_level_agreement.name)
}) self.assertEqual(create_customer_service_level_agreement.apply_to, get_customer_service_level_agreement.apply_to)
if not frappe.db.exists("Customer", "_Test Customer"): self.assertEqual(create_customer_service_level_agreement.entity, get_customer_service_level_agreement.entity)
customer.insert(ignore_permissions=True) self.assertEqual(create_customer_service_level_agreement.default_service_level_agreement, get_customer_service_level_agreement.default_service_level_agreement)
# 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,
service_level="__Test Service Level", holiday_list="__Test Holiday List", employee_group="_Test Employee Group",
apply_to="Customer Group", entity=customer_group.name, response_time=4, resolution_time=6)
get_customer_group_service_level_agreement = get_service_level_agreement(apply_to="Customer Group", entity=customer_group.name)
self.assertEqual(create_customer_group_service_level_agreement.name, get_customer_group_service_level_agreement.name)
self.assertEqual(create_customer_group_service_level_agreement.apply_to, get_customer_group_service_level_agreement.apply_to)
self.assertEqual(create_customer_group_service_level_agreement.entity, get_customer_group_service_level_agreement.entity)
self.assertEqual(create_customer_group_service_level_agreement.default_service_level_agreement, get_customer_group_service_level_agreement.default_service_level_agreement)
# Service Level Agreement for Territory
territory = create_territory()
create_territory_service_level_agreement = create_service_level_agreement(default_service_level_agreement=0,
service_level="__Test Service Level", holiday_list="__Test Holiday List", employee_group="_Test Employee Group",
apply_to="Territory", entity=territory.name, response_time=2, resolution_time=3)
get_territory_service_level_agreement = get_service_level_agreement(apply_to="Territory", entity=territory.name)
self.assertEqual(create_territory_service_level_agreement.name, get_territory_service_level_agreement.name)
self.assertEqual(create_territory_service_level_agreement.apply_to, get_territory_service_level_agreement.apply_to)
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 get_service_level_agreement(default_service_level_agreement=None, apply_to=None, entity=None):
if default_service_level_agreement:
filters = {"default_service_level_agreement": default_service_level_agreement}
else: else:
customer = frappe.get_doc("Customer", "_Test Customer") filters = {"apply_to": apply_to, "entity": entity}
service_level_agreement = frappe.get_doc("Service Level Agreement", filters)
print(service_level_agreement)
return service_level_agreement
def create_service_level_agreement(default_service_level_agreement, service_level, holiday_list, employee_group,
response_time, apply_to, entity, resolution_time):
service_level_agreement = frappe.get_doc({ service_level_agreement = frappe.get_doc({
"doctype": "Service Level Agreement", "doctype": "Service Level Agreement",
"service_level_agreement_name": "_Test Service Level Agreement", "default_service_level_agreement": default_service_level_agreement,
"customer": customer.customer_name, "service_level": service_level,
"service_level": "_Test Service Level", "holiday_list": holiday_list,
"holiday_list": "__Test Holiday List", "employee_group": employee_group,
"employee_group": "_Test Employee Group", "apply_to": apply_to,
"entity": entity,
"start_date": frappe.utils.getdate(), "start_date": frappe.utils.getdate(),
"end_date": frappe.utils.add_to_date(frappe.utils.getdate(), days=100), "end_date": frappe.utils.add_to_date(frappe.utils.getdate(), days=100),
"priorities": [ "priorities": [
{ {
"priority": "Low", "priority": "Low",
"response_time": 2, "response_time": response_time,
"response_time_period": "Day", "response_time_period": "Hour",
"resolution_time": 3, "resolution_time": resolution_time,
"resolution_time_period": "Day", "resolution_time_period": "Hour",
}, },
{ {
"priority": "Medium", "priority": "Medium",
"response_time": 2, "response_time": response_time,
"response_time_period": "Day", "default_priority": 1,
"resolution_time": 3, "response_time_period": "Hour",
"resolution_time_period": "Day", "resolution_time": resolution_time,
"resolution_time_period": "Hour",
}, },
{ {
"priority": "High", "priority": "High",
"response_time": 2, "response_time": response_time,
"response_time_period": "Day", "response_time_period": "Hour",
"resolution_time": 3, "resolution_time": resolution_time,
"resolution_time_period": "Day", "resolution_time_period": "Hour",
} }
], ],
"support_and_resolution": [ "support_and_resolution": [
@ -181,14 +161,32 @@ def make_service_level_agreement():
] ]
}) })
service_level_agreement_exists = frappe.db.exists("Service Level Agreement", {"service_level_agreement_name": "_Test Service Level Agreement"}) service_level_agreement_exists = frappe.db.exists("Service Level Agreement", service_level_agreement.name)
if not service_level_agreement_exists: if not service_level_agreement_exists:
service_level_agreement.insert(ignore_permissions=True) service_level_agreement.insert(ignore_permissions=True)
return service_level_agreement return service_level_agreement
else: else:
return frappe.get_doc("Service Level Agreement", "SLA-_Test Service Level Agreement") return frappe.get_doc("Service Level Agreement", service_level_agreement.name)
def get_service_level_agreement(): def create_customer_group():
service_level_agreement = frappe.get_doc("Service Level Agreement", "SLA-_Test Service Level Agreement") customer_group = frappe.get_doc({
return service_level_agreement "doctype": "Customer Group",
"customer_group_name": "_Test SLA Customer Group"
})
if not frappe.db.exists("Customer Group", {"customer_group_name": "_Test SLA Customer Group"}):
customer_group.insert()
return customer_group.name
def create_territory():
territory = frappe.get_doc({
"doctype": "Territory",
"territory_name": "_Test SLA Territory",
})
if not frappe.db.exists("Territory", {"territory_name": "_Test SLA Territory"}):
territory.insert()
return territory.name