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.quality_management.doctype.quality_review.quality_review.review",
"erpnext.support.doctype.service_level_agreement.service_level_agreement.check_agreement_status",
"erpnext.support.doctype.issue.issue.set_service_level_agreement_status"
],
"daily_long": [
"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")
for priority in priorities:
frappe.get_doc({
"doctype": "Issue Priority",
"name": priority
}).insert(ignore_permissions=True)
if not frappe.db.exists("Issue Priority", priority):
frappe.get_doc({
"doctype": "Issue Priority",
"name": priority
}).insert(ignore_permissions=True)
frappe.reload_doc("support", "doctype", "issue")
frappe.reload_doc("support", "doctype", "service_level")

View File

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

View File

@ -1,6 +1,25 @@
frappe.ui.form.on("Issue", {
onload: function(frm) {
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) {
@ -43,24 +62,6 @@ frappe.ui.form.on("Issue", {
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) {

View File

@ -77,11 +77,9 @@ class Issue(Document):
def update_agreement_status(self):
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"
else:
self.agreement_fulfilled = "Fulfilled"
@ -232,33 +230,27 @@ def get_expected_time_for(parameter, service_level, start_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):
filters = {"status": "Open", "agreement_fulfilled": "Ongoing"}
current_time = frappe.flags.current_time or now_datetime()
filters = {"status": "Open", "agreement_fulfilled": "Ongoing"}
if issue:
filters = {"name": issue}
issues = frappe.get_list("Issue", filters=filters)
for issue in issues:
for issue in frappe.get_list("Issue", filters=filters):
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)
if not doc.resolution_date:
variance = round(time_diff_in_hours(doc.resolution_by, now_datetime()), 2)
if variance < 0:
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)
if variance < 0:
frappe.db.set_value("Issue", doc.name, "agreement_fulfilled", "Failed")
def get_list_context(context=None):
return {

View File

@ -6,12 +6,15 @@
"engine": "InnoDB",
"field_order": [
"service_level",
"customer",
"default_service_level_agreement",
"holiday_list",
"column_break_2",
"employee_group",
"default_priority",
"apply_to_section",
"apply_to",
"column_break_10",
"entity",
"agreement_details_section",
"start_date",
"active",
@ -23,16 +26,6 @@
"support_and_resolution"
],
"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",
"depends_on": "eval: !doc.customer;",
@ -133,9 +126,34 @@
"fieldtype": "Check",
"label": "Active",
"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",
"module": "Support",
"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):
filters = [
["Service Level Agreement", "active", "=", 1]
["Service Level Agreement", "active", "=", 1],
]
if priority:
filters.append(["Service Level Priority", "priority", "=", priority])
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:
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])
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
@frappe.whitelist()
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):
def test_service_level_agreement(self):
test_make_service_level_agreement = make_service_level_agreement()
test_get_service_level_agreement = get_service_level_agreement()
make_service_level()
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():
make_service_level()
# Default Service Level Agreement
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
default_service_level_agreement = frappe.get_doc({
"doctype": "Service Level Agreement",
"service_level_agreement_name": "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",
}
]
})
self.assertEqual(create_default_service_level_agreement.name, get_default_service_level_agreement.name)
self.assertEqual(create_default_service_level_agreement.apply_to, get_default_service_level_agreement.apply_to)
self.assertEqual(create_default_service_level_agreement.entity, get_default_service_level_agreement.entity)
self.assertEqual(create_default_service_level_agreement.default_service_level_agreement, get_default_service_level_agreement.default_service_level_agreement)
default_service_level_agreement_exists = frappe.db.exists("Service Level Agreement", "SLA-Default Service Level Agreement")
if not default_service_level_agreement_exists:
default_service_level_agreement.insert(ignore_permissions=True)
# Service Level Agreement for Customer
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({
"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)
create_customer_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", entity=customer.name, response_time=2, resolution_time=3)
get_customer_service_level_agreement = get_service_level_agreement(apply_to="Customer", entity=customer.name)
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)
self.assertEqual(create_customer_service_level_agreement.entity, get_customer_service_level_agreement.entity)
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:
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({
"doctype": "Service Level Agreement",
"service_level_agreement_name": "_Test Service Level Agreement",
"customer": customer.customer_name,
"service_level": "_Test Service Level",
"holiday_list": "__Test Holiday List",
"employee_group": "_Test Employee Group",
"default_service_level_agreement": default_service_level_agreement,
"service_level": service_level,
"holiday_list": holiday_list,
"employee_group": employee_group,
"apply_to": apply_to,
"entity": entity,
"start_date": frappe.utils.getdate(),
"end_date": frappe.utils.add_to_date(frappe.utils.getdate(), days=100),
"priorities": [
{
"priority": "Low",
"response_time": 2,
"response_time_period": "Day",
"resolution_time": 3,
"resolution_time_period": "Day",
"response_time": response_time,
"response_time_period": "Hour",
"resolution_time": resolution_time,
"resolution_time_period": "Hour",
},
{
"priority": "Medium",
"response_time": 2,
"response_time_period": "Day",
"resolution_time": 3,
"resolution_time_period": "Day",
"response_time": response_time,
"default_priority": 1,
"response_time_period": "Hour",
"resolution_time": resolution_time,
"resolution_time_period": "Hour",
},
{
"priority": "High",
"response_time": 2,
"response_time_period": "Day",
"resolution_time": 3,
"resolution_time_period": "Day",
"response_time": response_time,
"response_time_period": "Hour",
"resolution_time": resolution_time,
"resolution_time_period": "Hour",
}
],
"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:
service_level_agreement.insert(ignore_permissions=True)
return service_level_agreement
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():
service_level_agreement = frappe.get_doc("Service Level Agreement", "SLA-_Test Service Level Agreement")
return service_level_agreement
def create_customer_group():
customer_group = frappe.get_doc({
"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