Merge branch 'develop' into update-items-permission-fix
This commit is contained in:
commit
74f34c2ee3
@ -5,7 +5,22 @@ import frappe
|
|||||||
import json
|
import json
|
||||||
from frappe.utils import nowdate, add_months, get_date_str
|
from frappe.utils import nowdate, add_months, get_date_str
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from erpnext.accounts.utils import get_fiscal_year, get_account_name
|
from erpnext.accounts.utils import get_fiscal_year, get_account_name, FiscalYearError
|
||||||
|
|
||||||
|
def _get_fiscal_year(date=None):
|
||||||
|
try:
|
||||||
|
fiscal_year = get_fiscal_year(date=nowdate(), as_dict=True)
|
||||||
|
return fiscal_year
|
||||||
|
|
||||||
|
except FiscalYearError:
|
||||||
|
#if no fiscal year for current date then get default fiscal year
|
||||||
|
try:
|
||||||
|
fiscal_year = get_fiscal_year(as_dict=True)
|
||||||
|
return fiscal_year
|
||||||
|
|
||||||
|
except FiscalYearError:
|
||||||
|
#if still no fiscal year found then no accounting data created, return
|
||||||
|
return None
|
||||||
|
|
||||||
def get_company_for_dashboards():
|
def get_company_for_dashboards():
|
||||||
company = frappe.defaults.get_defaults().company
|
company = frappe.defaults.get_defaults().company
|
||||||
@ -18,10 +33,16 @@ def get_company_for_dashboards():
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def get_data():
|
def get_data():
|
||||||
|
|
||||||
|
fiscal_year = _get_fiscal_year(nowdate())
|
||||||
|
|
||||||
|
if not fiscal_year:
|
||||||
|
return frappe._dict()
|
||||||
|
|
||||||
return frappe._dict({
|
return frappe._dict({
|
||||||
"dashboards": get_dashboards(),
|
"dashboards": get_dashboards(),
|
||||||
"charts": get_charts(),
|
"charts": get_charts(fiscal_year),
|
||||||
"number_cards": get_number_cards()
|
"number_cards": get_number_cards(fiscal_year)
|
||||||
})
|
})
|
||||||
|
|
||||||
def get_dashboards():
|
def get_dashboards():
|
||||||
@ -46,10 +67,9 @@ def get_dashboards():
|
|||||||
]
|
]
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def get_charts():
|
def get_charts(fiscal_year):
|
||||||
company = frappe.get_doc("Company", get_company_for_dashboards())
|
company = frappe.get_doc("Company", get_company_for_dashboards())
|
||||||
bank_account = company.default_bank_account or get_account_name("Bank", company=company.name)
|
bank_account = company.default_bank_account or get_account_name("Bank", company=company.name)
|
||||||
fiscal_year = get_fiscal_year(date=nowdate())
|
|
||||||
default_cost_center = company.cost_center
|
default_cost_center = company.cost_center
|
||||||
|
|
||||||
return [
|
return [
|
||||||
@ -61,8 +81,8 @@ def get_charts():
|
|||||||
"filters_json": json.dumps({
|
"filters_json": json.dumps({
|
||||||
"company": company.name,
|
"company": company.name,
|
||||||
"filter_based_on": "Fiscal Year",
|
"filter_based_on": "Fiscal Year",
|
||||||
"from_fiscal_year": fiscal_year[0],
|
"from_fiscal_year": fiscal_year.get('name'),
|
||||||
"to_fiscal_year": fiscal_year[0],
|
"to_fiscal_year": fiscal_year.get('name'),
|
||||||
"periodicity": "Monthly",
|
"periodicity": "Monthly",
|
||||||
"include_default_book_entries": 1
|
"include_default_book_entries": 1
|
||||||
}),
|
}),
|
||||||
@ -158,8 +178,8 @@ def get_charts():
|
|||||||
"report_name": "Budget Variance Report",
|
"report_name": "Budget Variance Report",
|
||||||
"filters_json": json.dumps({
|
"filters_json": json.dumps({
|
||||||
"company": company.name,
|
"company": company.name,
|
||||||
"from_fiscal_year": fiscal_year[0],
|
"from_fiscal_year": fiscal_year.get('name'),
|
||||||
"to_fiscal_year": fiscal_year[0],
|
"to_fiscal_year": fiscal_year.get('name'),
|
||||||
"period": "Monthly",
|
"period": "Monthly",
|
||||||
"budget_against": "Cost Center"
|
"budget_against": "Cost Center"
|
||||||
}),
|
}),
|
||||||
@ -190,10 +210,10 @@ def get_charts():
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
def get_number_cards():
|
def get_number_cards(fiscal_year):
|
||||||
fiscal_year = get_fiscal_year(date=nowdate())
|
|
||||||
year_start_date = get_date_str(fiscal_year[1])
|
year_start_date = get_date_str(fiscal_year.get("year_start_date"))
|
||||||
year_end_date = get_date_str(fiscal_year[2])
|
year_end_date = get_date_str(fiscal_year.get("year_end_date"))
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
"doctype": "Number Card",
|
"doctype": "Number Card",
|
||||||
|
@ -72,7 +72,11 @@ def make_dimension_in_accounting_doctypes(doc):
|
|||||||
if doctype == "Budget":
|
if doctype == "Budget":
|
||||||
add_dimension_to_budget_doctype(df, doc)
|
add_dimension_to_budget_doctype(df, doc)
|
||||||
else:
|
else:
|
||||||
create_custom_field(doctype, df)
|
meta = frappe.get_meta(doctype, cached=False)
|
||||||
|
fieldnames = [d.fieldname for d in meta.get("fields")]
|
||||||
|
|
||||||
|
if df['fieldname'] not in fieldnames:
|
||||||
|
create_custom_field(doctype, df)
|
||||||
|
|
||||||
count += 1
|
count += 1
|
||||||
|
|
||||||
|
@ -1745,53 +1745,6 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
|
|
||||||
check_gl_entries(self, si.name, expected_gle, "2019-01-30")
|
check_gl_entries(self, si.name, expected_gle, "2019-01-30")
|
||||||
|
|
||||||
def test_deferred_error_email(self):
|
|
||||||
deferred_account = create_account(account_name="Deferred Revenue",
|
|
||||||
parent_account="Current Liabilities - _TC", company="_Test Company")
|
|
||||||
|
|
||||||
item = create_item("_Test Item for Deferred Accounting")
|
|
||||||
item.enable_deferred_revenue = 1
|
|
||||||
item.deferred_revenue_account = deferred_account
|
|
||||||
item.no_of_months = 12
|
|
||||||
item.save()
|
|
||||||
|
|
||||||
si = create_sales_invoice(item=item.name, posting_date="2019-01-10", do_not_submit=True)
|
|
||||||
si.items[0].enable_deferred_revenue = 1
|
|
||||||
si.items[0].service_start_date = "2019-01-10"
|
|
||||||
si.items[0].service_end_date = "2019-03-15"
|
|
||||||
si.items[0].deferred_revenue_account = deferred_account
|
|
||||||
si.save()
|
|
||||||
si.submit()
|
|
||||||
|
|
||||||
from erpnext.accounts.deferred_revenue import convert_deferred_revenue_to_income
|
|
||||||
|
|
||||||
acc_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
|
|
||||||
acc_settings.acc_frozen_upto = '2019-01-31'
|
|
||||||
acc_settings.save()
|
|
||||||
|
|
||||||
pda = frappe.get_doc(dict(
|
|
||||||
doctype='Process Deferred Accounting',
|
|
||||||
posting_date=nowdate(),
|
|
||||||
start_date="2019-01-01",
|
|
||||||
end_date="2019-03-31",
|
|
||||||
type="Income",
|
|
||||||
company="_Test Company"
|
|
||||||
))
|
|
||||||
|
|
||||||
pda.insert()
|
|
||||||
pda.submit()
|
|
||||||
|
|
||||||
email = frappe.db.sql(""" select name from `tabEmail Queue`
|
|
||||||
where message like %(txt)s """, {
|
|
||||||
'txt': "%%%s%%" % "Error while processing deferred accounting for {0}".format(pda.name)
|
|
||||||
})
|
|
||||||
|
|
||||||
self.assertTrue(email)
|
|
||||||
|
|
||||||
acc_settings.load_from_db()
|
|
||||||
acc_settings.acc_frozen_upto = None
|
|
||||||
acc_settings.save()
|
|
||||||
|
|
||||||
def test_inter_company_transaction(self):
|
def test_inter_company_transaction(self):
|
||||||
|
|
||||||
if not frappe.db.exists("Customer", "_Test Internal Customer"):
|
if not frappe.db.exists("Customer", "_Test Internal Customer"):
|
||||||
|
@ -5,14 +5,23 @@ import frappe
|
|||||||
import json
|
import json
|
||||||
from frappe.utils import nowdate, add_months, get_date_str
|
from frappe.utils import nowdate, add_months, get_date_str
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from erpnext.accounts.utils import get_fiscal_year
|
from erpnext.accounts.dashboard_fixtures import _get_fiscal_year
|
||||||
|
from erpnext.buying.dashboard_fixtures import get_company_for_dashboards
|
||||||
|
|
||||||
def get_data():
|
def get_data():
|
||||||
|
|
||||||
|
fiscal_year = _get_fiscal_year(nowdate())
|
||||||
|
|
||||||
|
if not fiscal_year:
|
||||||
|
return frappe._dict()
|
||||||
|
|
||||||
|
year_start_date = get_date_str(fiscal_year.get('year_start_date'))
|
||||||
|
year_end_date = get_date_str(fiscal_year.get('year_end_date'))
|
||||||
|
|
||||||
return frappe._dict({
|
return frappe._dict({
|
||||||
"dashboards": get_dashboards(),
|
"dashboards": get_dashboards(),
|
||||||
"charts": get_charts(),
|
"charts": get_charts(fiscal_year, year_start_date, year_end_date),
|
||||||
"number_cards": get_number_cards(),
|
"number_cards": get_number_cards(fiscal_year, year_start_date, year_end_date),
|
||||||
})
|
})
|
||||||
|
|
||||||
def get_dashboards():
|
def get_dashboards():
|
||||||
@ -31,12 +40,7 @@ def get_dashboards():
|
|||||||
]
|
]
|
||||||
}]
|
}]
|
||||||
|
|
||||||
fiscal_year = get_fiscal_year(date=nowdate())
|
def get_charts(fiscal_year, year_start_date, year_end_date):
|
||||||
year_start_date = get_date_str(fiscal_year[1])
|
|
||||||
year_end_date = get_date_str(fiscal_year[2])
|
|
||||||
|
|
||||||
|
|
||||||
def get_charts():
|
|
||||||
company = get_company_for_dashboards()
|
company = get_company_for_dashboards()
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@ -55,8 +59,8 @@ def get_charts():
|
|||||||
"company": company,
|
"company": company,
|
||||||
"status": "In Location",
|
"status": "In Location",
|
||||||
"filter_based_on": "Fiscal Year",
|
"filter_based_on": "Fiscal Year",
|
||||||
"from_fiscal_year": fiscal_year[0],
|
"from_fiscal_year": fiscal_year.get('name'),
|
||||||
"to_fiscal_year": fiscal_year[0],
|
"to_fiscal_year": fiscal_year.get('name'),
|
||||||
"period_start_date": year_start_date,
|
"period_start_date": year_start_date,
|
||||||
"period_end_date": year_end_date,
|
"period_end_date": year_end_date,
|
||||||
"date_based_on": "Purchase Date",
|
"date_based_on": "Purchase Date",
|
||||||
@ -134,7 +138,7 @@ def get_charts():
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
def get_number_cards():
|
def get_number_cards(fiscal_year, year_start_date, year_end_date):
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
"name": "Total Assets",
|
"name": "Total Assets",
|
||||||
@ -172,14 +176,4 @@ def get_number_cards():
|
|||||||
"filters_json": "[]",
|
"filters_json": "[]",
|
||||||
"doctype": "Number Card"
|
"doctype": "Number Card"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
def get_company_for_dashboards():
|
|
||||||
company = frappe.defaults.get_defaults().company
|
|
||||||
if company:
|
|
||||||
return company
|
|
||||||
else:
|
|
||||||
company_list = frappe.get_list("Company")
|
|
||||||
if company_list:
|
|
||||||
return company_list[0].name
|
|
||||||
return None
|
|
@ -41,7 +41,7 @@ def assign_tasks(asset_maintenance_name, assign_to_member, maintenance_task, nex
|
|||||||
team_member = frappe.db.get_value('User', assign_to_member, "email")
|
team_member = frappe.db.get_value('User', assign_to_member, "email")
|
||||||
args = {
|
args = {
|
||||||
'doctype' : 'Asset Maintenance',
|
'doctype' : 'Asset Maintenance',
|
||||||
'assign_to' : team_member,
|
'assign_to' : [team_member],
|
||||||
'name' : asset_maintenance_name,
|
'name' : asset_maintenance_name,
|
||||||
'description' : maintenance_task,
|
'description' : maintenance_task,
|
||||||
'date' : next_due_date
|
'date' : next_due_date
|
||||||
|
@ -5,13 +5,24 @@ import frappe
|
|||||||
import json
|
import json
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils import nowdate
|
from frappe.utils import nowdate
|
||||||
from erpnext.accounts.utils import get_fiscal_year
|
from erpnext.accounts.dashboard_fixtures import _get_fiscal_year
|
||||||
|
|
||||||
def get_data():
|
def get_data():
|
||||||
|
|
||||||
|
fiscal_year = _get_fiscal_year(nowdate())
|
||||||
|
|
||||||
|
if not fiscal_year:
|
||||||
|
return frappe._dict()
|
||||||
|
|
||||||
|
company = frappe.get_doc("Company", get_company_for_dashboards())
|
||||||
|
fiscal_year_name = fiscal_year.get("name")
|
||||||
|
start_date = str(fiscal_year.get("year_start_date"))
|
||||||
|
end_date = str(fiscal_year.get("year_end_date"))
|
||||||
|
|
||||||
return frappe._dict({
|
return frappe._dict({
|
||||||
"dashboards": get_dashboards(),
|
"dashboards": get_dashboards(),
|
||||||
"charts": get_charts(),
|
"charts": get_charts(company, fiscal_year_name, start_date, end_date),
|
||||||
"number_cards": get_number_cards(),
|
"number_cards": get_number_cards(company, fiscal_year_name, start_date, end_date),
|
||||||
})
|
})
|
||||||
|
|
||||||
def get_company_for_dashboards():
|
def get_company_for_dashboards():
|
||||||
@ -24,12 +35,6 @@ def get_company_for_dashboards():
|
|||||||
return company_list[0].name
|
return company_list[0].name
|
||||||
return None
|
return None
|
||||||
|
|
||||||
company = frappe.get_doc("Company", get_company_for_dashboards())
|
|
||||||
fiscal_year = get_fiscal_year(nowdate(), as_dict=1)
|
|
||||||
fiscal_year_name = fiscal_year.get("name")
|
|
||||||
start_date = str(fiscal_year.get("year_start_date"))
|
|
||||||
end_date = str(fiscal_year.get("year_end_date"))
|
|
||||||
|
|
||||||
def get_dashboards():
|
def get_dashboards():
|
||||||
return [{
|
return [{
|
||||||
"name": "Buying",
|
"name": "Buying",
|
||||||
@ -48,7 +53,7 @@ def get_dashboards():
|
|||||||
]
|
]
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def get_charts():
|
def get_charts(company, fiscal_year_name, start_date, end_date):
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
"name": "Purchase Order Analysis",
|
"name": "Purchase Order Analysis",
|
||||||
@ -139,7 +144,7 @@ def get_charts():
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
def get_number_cards():
|
def get_number_cards(company, fiscal_year_name, start_date, end_date):
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
"name": "Annual Purchase",
|
"name": "Annual Purchase",
|
||||||
|
@ -71,6 +71,15 @@ class PurchaseOrder(BuyingController):
|
|||||||
"compare_fields": [["project", "="], ["item_code", "="],
|
"compare_fields": [["project", "="], ["item_code", "="],
|
||||||
["uom", "="], ["conversion_factor", "="]],
|
["uom", "="], ["conversion_factor", "="]],
|
||||||
"is_child_table": True
|
"is_child_table": True
|
||||||
|
},
|
||||||
|
"Material Request": {
|
||||||
|
"ref_dn_field": "material_request",
|
||||||
|
"compare_fields": [["company", "="]],
|
||||||
|
},
|
||||||
|
"Material Request Item": {
|
||||||
|
"ref_dn_field": "material_request_item",
|
||||||
|
"compare_fields": [["project", "="], ["item_code", "="]],
|
||||||
|
"is_child_table": True
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ class TestProcurementTracker(unittest.TestCase):
|
|||||||
def test_result_for_procurement_tracker(self):
|
def test_result_for_procurement_tracker(self):
|
||||||
filters = {
|
filters = {
|
||||||
'company': '_Test Procurement Company',
|
'company': '_Test Procurement Company',
|
||||||
'cost_center': '_Test Cost Center - _TC'
|
'cost_center': 'Main - _TPC'
|
||||||
}
|
}
|
||||||
expected_data = self.generate_expected_data()
|
expected_data = self.generate_expected_data()
|
||||||
report = execute(filters)
|
report = execute(filters)
|
||||||
@ -33,24 +33,27 @@ class TestProcurementTracker(unittest.TestCase):
|
|||||||
country="Pakistan"
|
country="Pakistan"
|
||||||
)).insert()
|
)).insert()
|
||||||
warehouse = create_warehouse("_Test Procurement Warehouse", company="_Test Procurement Company")
|
warehouse = create_warehouse("_Test Procurement Warehouse", company="_Test Procurement Company")
|
||||||
mr = make_material_request(company="_Test Procurement Company", warehouse=warehouse)
|
mr = make_material_request(company="_Test Procurement Company", warehouse=warehouse, cost_center="Main - _TPC")
|
||||||
po = make_purchase_order(mr.name)
|
po = make_purchase_order(mr.name)
|
||||||
po.supplier = "_Test Supplier"
|
po.supplier = "_Test Supplier"
|
||||||
po.get("items")[0].cost_center = "_Test Cost Center - _TC"
|
po.get("items")[0].cost_center = "Main - _TPC"
|
||||||
po.submit()
|
po.submit()
|
||||||
pr = make_purchase_receipt(po.name)
|
pr = make_purchase_receipt(po.name)
|
||||||
|
pr.get("items")[0].cost_center = "Main - _TPC"
|
||||||
pr.submit()
|
pr.submit()
|
||||||
frappe.db.commit()
|
frappe.db.commit()
|
||||||
date_obj = datetime.date(datetime.now())
|
date_obj = datetime.date(datetime.now())
|
||||||
|
|
||||||
|
po.load_from_db()
|
||||||
|
|
||||||
expected_data = {
|
expected_data = {
|
||||||
"material_request_date": date_obj,
|
"material_request_date": date_obj,
|
||||||
"cost_center": "_Test Cost Center - _TC",
|
"cost_center": "Main - _TPC",
|
||||||
"project": None,
|
"project": None,
|
||||||
"requesting_site": "_Test Procurement Warehouse - _TPC",
|
"requesting_site": "_Test Procurement Warehouse - _TPC",
|
||||||
"requestor": "Administrator",
|
"requestor": "Administrator",
|
||||||
"material_request_no": mr.name,
|
"material_request_no": mr.name,
|
||||||
"description": '_Test Item 1',
|
"item_code": '_Test Item',
|
||||||
"quantity": 10.0,
|
"quantity": 10.0,
|
||||||
"unit_of_measurement": "_Test UOM",
|
"unit_of_measurement": "_Test UOM",
|
||||||
"status": "To Bill",
|
"status": "To Bill",
|
||||||
@ -58,9 +61,9 @@ class TestProcurementTracker(unittest.TestCase):
|
|||||||
"purchase_order": po.name,
|
"purchase_order": po.name,
|
||||||
"supplier": "_Test Supplier",
|
"supplier": "_Test Supplier",
|
||||||
"estimated_cost": 0.0,
|
"estimated_cost": 0.0,
|
||||||
"actual_cost": None,
|
"actual_cost": 0.0,
|
||||||
"purchase_order_amt": 5000.0,
|
"purchase_order_amt": po.net_total,
|
||||||
"purchase_order_amt_in_company_currency": 300000.0,
|
"purchase_order_amt_in_company_currency": po.base_net_total,
|
||||||
"expected_delivery_date": date_obj,
|
"expected_delivery_date": date_obj,
|
||||||
"actual_delivery_date": date_obj
|
"actual_delivery_date": date_obj
|
||||||
}
|
}
|
||||||
|
@ -349,7 +349,7 @@ class BuyingController(StockController):
|
|||||||
})
|
})
|
||||||
|
|
||||||
if not rm.rate:
|
if not rm.rate:
|
||||||
rm.rate = get_valuation_rate(raw_material_data.item_code, self.supplier_warehouse,
|
rm.rate = get_valuation_rate(raw_material_data.rm_item_code, self.supplier_warehouse,
|
||||||
self.doctype, self.name, currency=self.company_currency, company=self.company)
|
self.doctype, self.name, currency=self.company_currency, company=self.company)
|
||||||
|
|
||||||
rm.amount = qty * flt(rm.rate)
|
rm.amount = qty * flt(rm.rate)
|
||||||
|
@ -15,6 +15,7 @@ class TestInpatientRecord(unittest.TestCase):
|
|||||||
patient = create_patient()
|
patient = create_patient()
|
||||||
# Schedule Admission
|
# Schedule Admission
|
||||||
ip_record = create_inpatient(patient)
|
ip_record = create_inpatient(patient)
|
||||||
|
ip_record.expected_length_of_stay = 0
|
||||||
ip_record.save(ignore_permissions = True)
|
ip_record.save(ignore_permissions = True)
|
||||||
self.assertEqual(ip_record.name, frappe.db.get_value("Patient", patient, "inpatient_record"))
|
self.assertEqual(ip_record.name, frappe.db.get_value("Patient", patient, "inpatient_record"))
|
||||||
self.assertEqual(ip_record.status, frappe.db.get_value("Patient", patient, "inpatient_status"))
|
self.assertEqual(ip_record.status, frappe.db.get_value("Patient", patient, "inpatient_status"))
|
||||||
@ -26,7 +27,7 @@ class TestInpatientRecord(unittest.TestCase):
|
|||||||
self.assertEqual("Occupied", frappe.db.get_value("Healthcare Service Unit", service_unit, "occupancy_status"))
|
self.assertEqual("Occupied", frappe.db.get_value("Healthcare Service Unit", service_unit, "occupancy_status"))
|
||||||
|
|
||||||
# Discharge
|
# Discharge
|
||||||
schedule_discharge(patient=patient)
|
schedule_discharge(frappe.as_json({'patient': patient}))
|
||||||
self.assertEqual("Vacant", frappe.db.get_value("Healthcare Service Unit", service_unit, "occupancy_status"))
|
self.assertEqual("Vacant", frappe.db.get_value("Healthcare Service Unit", service_unit, "occupancy_status"))
|
||||||
|
|
||||||
ip_record1 = frappe.get_doc("Inpatient Record", ip_record.name)
|
ip_record1 = frappe.get_doc("Inpatient Record", ip_record.name)
|
||||||
@ -44,8 +45,10 @@ class TestInpatientRecord(unittest.TestCase):
|
|||||||
patient = create_patient()
|
patient = create_patient()
|
||||||
|
|
||||||
ip_record = create_inpatient(patient)
|
ip_record = create_inpatient(patient)
|
||||||
|
ip_record.expected_length_of_stay = 0
|
||||||
ip_record.save(ignore_permissions = True)
|
ip_record.save(ignore_permissions = True)
|
||||||
ip_record_new = create_inpatient(patient)
|
ip_record_new = create_inpatient(patient)
|
||||||
|
ip_record_new.expected_length_of_stay = 0
|
||||||
self.assertRaises(frappe.ValidationError, ip_record_new.save)
|
self.assertRaises(frappe.ValidationError, ip_record_new.save)
|
||||||
|
|
||||||
service_unit = get_healthcare_service_unit()
|
service_unit = get_healthcare_service_unit()
|
||||||
|
@ -205,7 +205,7 @@
|
|||||||
"label": "Status",
|
"label": "Status",
|
||||||
"oldfieldname": "status",
|
"oldfieldname": "status",
|
||||||
"oldfieldtype": "Select",
|
"oldfieldtype": "Select",
|
||||||
"options": "\nActive\nLeft",
|
"options": "Active\nLeft",
|
||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
"search_index": 1
|
"search_index": 1
|
||||||
},
|
},
|
||||||
@ -667,6 +667,7 @@
|
|||||||
"oldfieldtype": "Date"
|
"oldfieldtype": "Date"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"depends_on": "eval:doc.status == \"Left\"",
|
||||||
"fieldname": "relieving_date",
|
"fieldname": "relieving_date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"label": "Relieving Date",
|
"label": "Relieving Date",
|
||||||
@ -803,7 +804,7 @@
|
|||||||
"idx": 24,
|
"idx": 24,
|
||||||
"image_field": "image",
|
"image_field": "image",
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-05-05 18:51:03.152503",
|
"modified": "2020-06-15 12:26:30.003741",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "HR",
|
"module": "HR",
|
||||||
"name": "Employee",
|
"name": "Employee",
|
||||||
|
@ -66,6 +66,7 @@
|
|||||||
"fieldname": "employee",
|
"fieldname": "employee",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"in_global_search": 1,
|
"in_global_search": 1,
|
||||||
|
"in_standard_filter": 1,
|
||||||
"label": "From Employee",
|
"label": "From Employee",
|
||||||
"oldfieldname": "employee",
|
"oldfieldname": "employee",
|
||||||
"oldfieldtype": "Link",
|
"oldfieldtype": "Link",
|
||||||
@ -164,6 +165,7 @@
|
|||||||
"default": "Today",
|
"default": "Today",
|
||||||
"fieldname": "posting_date",
|
"fieldname": "posting_date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
|
"in_standard_filter": 1,
|
||||||
"label": "Posting Date",
|
"label": "Posting Date",
|
||||||
"oldfieldname": "posting_date",
|
"oldfieldname": "posting_date",
|
||||||
"oldfieldtype": "Date",
|
"oldfieldtype": "Date",
|
||||||
@ -236,6 +238,7 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "company",
|
"fieldname": "company",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
|
"in_standard_filter": 1,
|
||||||
"label": "Company",
|
"label": "Company",
|
||||||
"oldfieldname": "company",
|
"oldfieldname": "company",
|
||||||
"oldfieldtype": "Link",
|
"oldfieldtype": "Link",
|
||||||
@ -368,7 +371,7 @@
|
|||||||
"idx": 1,
|
"idx": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2019-12-14 23:52:05.388458",
|
"modified": "2020-06-15 12:43:04.099803",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "HR",
|
"module": "HR",
|
||||||
"name": "Expense Claim",
|
"name": "Expense Claim",
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
import frappe, erpnext, json
|
import frappe, erpnext, json
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils import nowdate, get_first_day, get_last_day, add_months
|
from frappe.utils import nowdate, get_first_day, get_last_day, add_months
|
||||||
from erpnext.accounts.utils import get_fiscal_year
|
|
||||||
|
|
||||||
def get_data():
|
def get_data():
|
||||||
return frappe._dict({
|
return frappe._dict({
|
||||||
|
@ -697,3 +697,4 @@ execute:frappe.rename_doc("Desk Page", "Loan Management", "Loan", force=True)
|
|||||||
erpnext.patches.v12_0.update_uom_conversion_factor
|
erpnext.patches.v12_0.update_uom_conversion_factor
|
||||||
erpnext.patches.v13_0.delete_old_purchase_reports
|
erpnext.patches.v13_0.delete_old_purchase_reports
|
||||||
erpnext.patches.v12_0.set_italian_import_supplier_invoice_permissions
|
erpnext.patches.v12_0.set_italian_import_supplier_invoice_permissions
|
||||||
|
erpnext.patches.v13_0.update_sla_enhancements
|
@ -6,4 +6,6 @@ import frappe
|
|||||||
|
|
||||||
def execute():
|
def execute():
|
||||||
''' Move from due_advance_amount to pending_amount '''
|
''' Move from due_advance_amount to pending_amount '''
|
||||||
frappe.db.sql(''' UPDATE `tabEmployee Advance` SET pending_amount=due_advance_amount ''')
|
|
||||||
|
if frappe.db.has_column("Employee Advance", "due_advance_amount"):
|
||||||
|
frappe.db.sql(''' UPDATE `tabEmployee Advance` SET pending_amount=due_advance_amount ''')
|
||||||
|
93
erpnext/patches/v13_0/update_sla_enhancements.py
Normal file
93
erpnext/patches/v13_0/update_sla_enhancements.py
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
# Copyright (c) 2018, Frappe and Contributors
|
||||||
|
# License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
# add holiday list and employee group fields in SLA
|
||||||
|
# change response and resolution time in priorities child table
|
||||||
|
if frappe.db.exists('DocType', 'Service Level Agreement'):
|
||||||
|
sla_details = frappe.db.get_all('Service Level Agreement', fields=['name', 'service_level'])
|
||||||
|
priorities = frappe.db.get_all('Service Level Priority', fields=['*'], filters={
|
||||||
|
'parenttype': ('in', ['Service Level Agreement', 'Service Level'])
|
||||||
|
})
|
||||||
|
|
||||||
|
frappe.reload_doc('support', 'doctype', 'service_level_agreement')
|
||||||
|
frappe.reload_doc('support', 'doctype', 'pause_sla_on_status')
|
||||||
|
frappe.reload_doc('support', 'doctype', 'service_level_priority')
|
||||||
|
frappe.reload_doc('support', 'doctype', 'service_day')
|
||||||
|
|
||||||
|
for entry in sla_details:
|
||||||
|
values = frappe.db.get_value('Service Level', entry.service_level, ['holiday_list', 'employee_group'])
|
||||||
|
if values:
|
||||||
|
holiday_list = values[0]
|
||||||
|
employee_group = values[1]
|
||||||
|
frappe.db.set_value('Service Level Agreement', entry.name, {
|
||||||
|
'holiday_list': holiday_list,
|
||||||
|
'employee_group': employee_group
|
||||||
|
})
|
||||||
|
|
||||||
|
priority_dict = {}
|
||||||
|
|
||||||
|
for priority in priorities:
|
||||||
|
if priority.parenttype == 'Service Level Agreement':
|
||||||
|
response_time = convert_to_seconds(priority.response_time, priority.response_time_period)
|
||||||
|
resolution_time = convert_to_seconds(priority.resolution_time, priority.resolution_time_period)
|
||||||
|
frappe.db.set_value('Service Level Priority', priority.name, {
|
||||||
|
'response_time': response_time,
|
||||||
|
'resolution_time': resolution_time
|
||||||
|
})
|
||||||
|
if priority.parenttype == 'Service Level':
|
||||||
|
if not priority.parent in priority_dict:
|
||||||
|
priority_dict[priority.parent] = []
|
||||||
|
priority_dict[priority.parent].append(priority)
|
||||||
|
|
||||||
|
|
||||||
|
# copy Service Levels to Service Level Agreements
|
||||||
|
sl = [entry.service_level for entry in sla_details]
|
||||||
|
if frappe.db.exists('DocType', 'Service Level'):
|
||||||
|
service_levels = frappe.db.get_all('Service Level', filters={'service_level': ('not in', sl)}, fields=['*'])
|
||||||
|
for entry in service_levels:
|
||||||
|
sla = frappe.new_doc('Service Level Agreement')
|
||||||
|
sla.service_level = entry.service_level
|
||||||
|
sla.holiday_list = entry.holiday_list
|
||||||
|
sla.employee_group = entry.employee_group
|
||||||
|
sla.flags.ignore_validate = True
|
||||||
|
sla = sla.insert(ignore_mandatory=True)
|
||||||
|
|
||||||
|
frappe.db.sql("""
|
||||||
|
UPDATE
|
||||||
|
`tabService Day`
|
||||||
|
SET
|
||||||
|
parent = %(new_parent)s , parentfield = 'support_and_resolution', parenttype = 'Service Level Agreement'
|
||||||
|
WHERE
|
||||||
|
parent = %(old_parent)s
|
||||||
|
""", {'new_parent': sla.name, 'old_parent': entry.name}, as_dict = 1)
|
||||||
|
|
||||||
|
priority_list = priority_dict.get(entry.name)
|
||||||
|
if priority_list:
|
||||||
|
sla = frappe.get_doc('Service Level Agreement', sla.name)
|
||||||
|
for priority in priority_list:
|
||||||
|
row = sla.append('priorities', {
|
||||||
|
'priority': priority.priority,
|
||||||
|
'default_priority': priority.default_priority,
|
||||||
|
'response_time': convert_to_seconds(priority.response_time, priority.response_time_period),
|
||||||
|
'resolution_time': convert_to_seconds(priority.resolution_time, priority.resolution_time_period)
|
||||||
|
})
|
||||||
|
row.db_update()
|
||||||
|
sla.db_update()
|
||||||
|
|
||||||
|
frappe.delete_doc_if_exists('DocType', 'Service Level')
|
||||||
|
|
||||||
|
|
||||||
|
def convert_to_seconds(value, unit):
|
||||||
|
seconds = 0
|
||||||
|
if unit == "Hour":
|
||||||
|
seconds = value * 3600
|
||||||
|
if unit == "Day":
|
||||||
|
seconds = value * 3600 * 24
|
||||||
|
if unit == "Week":
|
||||||
|
seconds = value * 3600 * 24 * 7
|
||||||
|
return seconds
|
@ -1,4 +1,5 @@
|
|||||||
import frappe
|
import frappe
|
||||||
|
from frappe.utils import cint
|
||||||
from erpnext.portal.product_configurator.item_variants_cache import ItemVariantsCacheManager
|
from erpnext.portal.product_configurator.item_variants_cache import ItemVariantsCacheManager
|
||||||
|
|
||||||
def get_field_filter_data():
|
def get_field_filter_data():
|
||||||
@ -243,6 +244,8 @@ def get_next_attribute_and_values(item_code, selected_attributes):
|
|||||||
else:
|
else:
|
||||||
product_info = None
|
product_info = None
|
||||||
|
|
||||||
|
product_info["allow_items_not_in_stock"] = cint(data.cart_settings.allow_items_not_in_stock)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'next_attribute': next_attribute,
|
'next_attribute': next_attribute,
|
||||||
'valid_options_for_attributes': valid_options_for_attributes,
|
'valid_options_for_attributes': valid_options_for_attributes,
|
||||||
|
@ -64,7 +64,7 @@ class TestTask(unittest.TestCase):
|
|||||||
def assign():
|
def assign():
|
||||||
from frappe.desk.form import assign_to
|
from frappe.desk.form import assign_to
|
||||||
assign_to.add({
|
assign_to.add({
|
||||||
"assign_to": "test@example.com",
|
"assign_to": ["test@example.com"],
|
||||||
"doctype": task.doctype,
|
"doctype": task.doctype,
|
||||||
"name": task.name,
|
"name": task.name,
|
||||||
"description": "Close this task"
|
"description": "Close this task"
|
||||||
|
@ -13,7 +13,7 @@ from erpnext.projects.doctype.timesheet.timesheet import make_salary_slip, make_
|
|||||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||||
from erpnext.hr.doctype.salary_structure.test_salary_structure \
|
from erpnext.hr.doctype.salary_structure.test_salary_structure \
|
||||||
import make_salary_structure, create_salary_structure_assignment
|
import make_salary_structure, create_salary_structure_assignment
|
||||||
|
from erpnext.hr.doctype.employee.test_employee import make_employee
|
||||||
|
|
||||||
class TestTimesheet(unittest.TestCase):
|
class TestTimesheet(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
@ -25,8 +25,10 @@ class TestTimesheet(unittest.TestCase):
|
|||||||
|
|
||||||
|
|
||||||
def test_timesheet_billing_amount(self):
|
def test_timesheet_billing_amount(self):
|
||||||
make_salary_structure_for_timesheet("_T-Employee-00001")
|
emp = make_employee("test_employee_6@salary.com")
|
||||||
timesheet = make_timesheet("_T-Employee-00001", simulate=True, billable=1)
|
|
||||||
|
make_salary_structure_for_timesheet(emp)
|
||||||
|
timesheet = make_timesheet(emp, simulate=True, billable=1)
|
||||||
|
|
||||||
self.assertEqual(timesheet.total_hours, 2)
|
self.assertEqual(timesheet.total_hours, 2)
|
||||||
self.assertEqual(timesheet.total_billable_hours, 2)
|
self.assertEqual(timesheet.total_billable_hours, 2)
|
||||||
@ -35,8 +37,10 @@ class TestTimesheet(unittest.TestCase):
|
|||||||
self.assertEqual(timesheet.total_billable_amount, 100)
|
self.assertEqual(timesheet.total_billable_amount, 100)
|
||||||
|
|
||||||
def test_timesheet_billing_amount_not_billable(self):
|
def test_timesheet_billing_amount_not_billable(self):
|
||||||
make_salary_structure_for_timesheet("_T-Employee-00001")
|
emp = make_employee("test_employee_6@salary.com")
|
||||||
timesheet = make_timesheet("_T-Employee-00001", simulate=True, billable=0)
|
|
||||||
|
make_salary_structure_for_timesheet(emp)
|
||||||
|
timesheet = make_timesheet(emp, simulate=True, billable=0)
|
||||||
|
|
||||||
self.assertEqual(timesheet.total_hours, 2)
|
self.assertEqual(timesheet.total_hours, 2)
|
||||||
self.assertEqual(timesheet.total_billable_hours, 0)
|
self.assertEqual(timesheet.total_billable_hours, 0)
|
||||||
@ -45,8 +49,10 @@ class TestTimesheet(unittest.TestCase):
|
|||||||
self.assertEqual(timesheet.total_billable_amount, 0)
|
self.assertEqual(timesheet.total_billable_amount, 0)
|
||||||
|
|
||||||
def test_salary_slip_from_timesheet(self):
|
def test_salary_slip_from_timesheet(self):
|
||||||
salary_structure = make_salary_structure_for_timesheet("_T-Employee-00001")
|
emp = make_employee("test_employee_6@salary.com")
|
||||||
timesheet = make_timesheet("_T-Employee-00001", simulate = True, billable=1)
|
|
||||||
|
salary_structure = make_salary_structure_for_timesheet(emp)
|
||||||
|
timesheet = make_timesheet(emp, simulate = True, billable=1)
|
||||||
salary_slip = make_salary_slip(timesheet.name)
|
salary_slip = make_salary_slip(timesheet.name)
|
||||||
salary_slip.submit()
|
salary_slip.submit()
|
||||||
|
|
||||||
@ -65,7 +71,9 @@ class TestTimesheet(unittest.TestCase):
|
|||||||
self.assertEqual(timesheet.status, 'Submitted')
|
self.assertEqual(timesheet.status, 'Submitted')
|
||||||
|
|
||||||
def test_sales_invoice_from_timesheet(self):
|
def test_sales_invoice_from_timesheet(self):
|
||||||
timesheet = make_timesheet("_T-Employee-00001", simulate=True, billable=1)
|
emp = make_employee("test_employee_6@salary.com")
|
||||||
|
|
||||||
|
timesheet = make_timesheet(emp, simulate=True, billable=1)
|
||||||
sales_invoice = make_sales_invoice(timesheet.name, '_Test Item', '_Test Customer')
|
sales_invoice = make_sales_invoice(timesheet.name, '_Test Item', '_Test Customer')
|
||||||
sales_invoice.due_date = nowdate()
|
sales_invoice.due_date = nowdate()
|
||||||
sales_invoice.submit()
|
sales_invoice.submit()
|
||||||
@ -80,7 +88,9 @@ class TestTimesheet(unittest.TestCase):
|
|||||||
self.assertEqual(item.rate, 50.00)
|
self.assertEqual(item.rate, 50.00)
|
||||||
|
|
||||||
def test_timesheet_billing_based_on_project(self):
|
def test_timesheet_billing_based_on_project(self):
|
||||||
timesheet = make_timesheet("_T-Employee-00001", simulate=True, billable=1, project = '_Test Project', company='_Test Company')
|
emp = make_employee("test_employee_6@salary.com")
|
||||||
|
|
||||||
|
timesheet = make_timesheet(emp, simulate=True, billable=1, project = '_Test Project', company='_Test Company')
|
||||||
sales_invoice = create_sales_invoice(do_not_save=True)
|
sales_invoice = create_sales_invoice(do_not_save=True)
|
||||||
sales_invoice.project = '_Test Project'
|
sales_invoice.project = '_Test Project'
|
||||||
sales_invoice.submit()
|
sales_invoice.submit()
|
||||||
@ -90,6 +100,8 @@ class TestTimesheet(unittest.TestCase):
|
|||||||
self.assertEqual(ts.time_logs[0].sales_invoice, sales_invoice.name)
|
self.assertEqual(ts.time_logs[0].sales_invoice, sales_invoice.name)
|
||||||
|
|
||||||
def test_timesheet_time_overlap(self):
|
def test_timesheet_time_overlap(self):
|
||||||
|
emp = make_employee("test_employee_6@salary.com")
|
||||||
|
|
||||||
settings = frappe.get_single('Projects Settings')
|
settings = frappe.get_single('Projects Settings')
|
||||||
initial_setting = settings.ignore_employee_time_overlap
|
initial_setting = settings.ignore_employee_time_overlap
|
||||||
settings.ignore_employee_time_overlap = 0
|
settings.ignore_employee_time_overlap = 0
|
||||||
@ -97,7 +109,7 @@ class TestTimesheet(unittest.TestCase):
|
|||||||
|
|
||||||
update_activity_type("_Test Activity Type")
|
update_activity_type("_Test Activity Type")
|
||||||
timesheet = frappe.new_doc("Timesheet")
|
timesheet = frappe.new_doc("Timesheet")
|
||||||
timesheet.employee = "_T-Employee-00001"
|
timesheet.employee = emp
|
||||||
timesheet.append(
|
timesheet.append(
|
||||||
'time_logs',
|
'time_logs',
|
||||||
{
|
{
|
||||||
@ -129,12 +141,14 @@ class TestTimesheet(unittest.TestCase):
|
|||||||
settings.save()
|
settings.save()
|
||||||
|
|
||||||
def test_timesheet_std_working_hours(self):
|
def test_timesheet_std_working_hours(self):
|
||||||
|
emp = make_employee("test_employee_6@salary.com")
|
||||||
|
|
||||||
company = frappe.get_doc('Company', "_Test Company")
|
company = frappe.get_doc('Company', "_Test Company")
|
||||||
company.standard_working_hours = 8
|
company.standard_working_hours = 8
|
||||||
company.save()
|
company.save()
|
||||||
|
|
||||||
timesheet = frappe.new_doc("Timesheet")
|
timesheet = frappe.new_doc("Timesheet")
|
||||||
timesheet.employee = "_T-Employee-00001"
|
timesheet.employee = emp
|
||||||
timesheet.company = '_Test Company'
|
timesheet.company = '_Test Company'
|
||||||
timesheet.append(
|
timesheet.append(
|
||||||
'time_logs',
|
'time_logs',
|
||||||
@ -156,7 +170,7 @@ class TestTimesheet(unittest.TestCase):
|
|||||||
company.save()
|
company.save()
|
||||||
|
|
||||||
timesheet = frappe.new_doc("Timesheet")
|
timesheet = frappe.new_doc("Timesheet")
|
||||||
timesheet.employee = "_T-Employee-00001"
|
timesheet.employee = emp
|
||||||
timesheet.company = '_Test Company'
|
timesheet.company = '_Test Company'
|
||||||
timesheet.append(
|
timesheet.append(
|
||||||
'time_logs',
|
'time_logs',
|
||||||
|
@ -9,14 +9,14 @@ def setup(company=None, patch=True):
|
|||||||
make_custom_fields()
|
make_custom_fields()
|
||||||
add_print_formats()
|
add_print_formats()
|
||||||
|
|
||||||
def make_custom_fields():
|
def make_custom_fields(update=True):
|
||||||
custom_fields = {
|
custom_fields = {
|
||||||
'Supplier': [
|
'Supplier': [
|
||||||
dict(fieldname='irs_1099', fieldtype='Check', insert_after='tax_id',
|
dict(fieldname='irs_1099', fieldtype='Check', insert_after='tax_id',
|
||||||
label='Is IRS 1099 reporting required for supplier?')
|
label='Is IRS 1099 reporting required for supplier?')
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
create_custom_fields(custom_fields)
|
create_custom_fields(custom_fields, update=update)
|
||||||
|
|
||||||
def add_print_formats():
|
def add_print_formats():
|
||||||
frappe.reload_doc("regional", "print_format", "irs_1099_form")
|
frappe.reload_doc("regional", "print_format", "irs_1099_form")
|
||||||
|
@ -105,3 +105,4 @@ def add_company_to_session_defaults():
|
|||||||
"ref_doctype": "Company"
|
"ref_doctype": "Company"
|
||||||
})
|
})
|
||||||
settings.save()
|
settings.save()
|
||||||
|
|
||||||
|
@ -78,8 +78,10 @@ def place_order():
|
|||||||
|
|
||||||
if is_stock_item:
|
if is_stock_item:
|
||||||
item_stock = get_qty_in_stock(item.item_code, "website_warehouse")
|
item_stock = get_qty_in_stock(item.item_code, "website_warehouse")
|
||||||
|
if not cint(item_stock.in_stock):
|
||||||
|
throw(_("{1} Not in Stock").format(item.item_code))
|
||||||
if item.qty > item_stock.stock_qty[0][0]:
|
if item.qty > item_stock.stock_qty[0][0]:
|
||||||
throw(_("Only {0} in stock for item {1}").format(item_stock.stock_qty[0][0], item.item_code))
|
throw(_("Only {0} in Stock for item {1}").format(item_stock.stock_qty[0][0], item.item_code))
|
||||||
|
|
||||||
sales_order.flags.ignore_permissions = True
|
sales_order.flags.ignore_permissions = True
|
||||||
sales_order.insert()
|
sales_order.insert()
|
||||||
|
@ -5,31 +5,26 @@ import frappe
|
|||||||
import json
|
import json
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils import nowdate
|
from frappe.utils import nowdate
|
||||||
from erpnext.accounts.utils import get_fiscal_year
|
from erpnext.accounts.dashboard_fixtures import _get_fiscal_year
|
||||||
|
from erpnext.buying.dashboard_fixtures import get_company_for_dashboards
|
||||||
|
|
||||||
def get_data():
|
def get_data():
|
||||||
|
fiscal_year = _get_fiscal_year(nowdate())
|
||||||
|
|
||||||
|
if not fiscal_year:
|
||||||
|
return frappe._dict()
|
||||||
|
|
||||||
|
company = frappe.get_doc("Company", get_company_for_dashboards())
|
||||||
|
fiscal_year_name = fiscal_year.get("name")
|
||||||
|
start_date = str(fiscal_year.get("year_start_date"))
|
||||||
|
end_date = str(fiscal_year.get("year_end_date"))
|
||||||
|
|
||||||
return frappe._dict({
|
return frappe._dict({
|
||||||
"dashboards": get_dashboards(),
|
"dashboards": get_dashboards(),
|
||||||
"charts": get_charts(),
|
"charts": get_charts(company, fiscal_year_name, start_date, end_date),
|
||||||
"number_cards": get_number_cards(),
|
"number_cards": get_number_cards(company, fiscal_year_name, start_date, end_date),
|
||||||
})
|
})
|
||||||
|
|
||||||
def get_company_for_dashboards():
|
|
||||||
company = frappe.defaults.get_defaults().company
|
|
||||||
if company:
|
|
||||||
return company
|
|
||||||
else:
|
|
||||||
company_list = frappe.get_list("Company")
|
|
||||||
if company_list:
|
|
||||||
return company_list[0].name
|
|
||||||
return None
|
|
||||||
|
|
||||||
company = frappe.get_doc("Company", get_company_for_dashboards())
|
|
||||||
fiscal_year = get_fiscal_year(nowdate(), as_dict=1)
|
|
||||||
fiscal_year_name = fiscal_year.get("name")
|
|
||||||
start_date = str(fiscal_year.get("year_start_date"))
|
|
||||||
end_date = str(fiscal_year.get("year_end_date"))
|
|
||||||
|
|
||||||
def get_dashboards():
|
def get_dashboards():
|
||||||
return [{
|
return [{
|
||||||
"name": "Stock",
|
"name": "Stock",
|
||||||
@ -48,7 +43,7 @@ def get_dashboards():
|
|||||||
]
|
]
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def get_charts():
|
def get_charts(company, fiscal_year_name, start_date, end_date):
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
"doctype": "Dashboard Chart",
|
"doctype": "Dashboard Chart",
|
||||||
@ -133,7 +128,7 @@ def get_charts():
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
def get_number_cards():
|
def get_number_cards(company, fiscal_year_name, start_date, end_date):
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
"name": "Total Active Items",
|
"name": "Total Active Items",
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
{
|
{
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
"label": "Service Level Agreement",
|
"label": "Service Level Agreement",
|
||||||
"links": "[\n {\n \"description\": \"Service Level.\",\n \"label\": \"Service Level\",\n \"name\": \"Service Level\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Service Level Agreement.\",\n \"label\": \"Service Level Agreement\",\n \"name\": \"Service Level Agreement\",\n \"type\": \"doctype\"\n }\n]"
|
"links": "[\n {\n \"description\": \"Service Level Agreement.\",\n \"label\": \"Service Level Agreement\",\n \"name\": \"Service Level Agreement\",\n \"type\": \"doctype\"\n }\n]"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
@ -43,7 +43,7 @@
|
|||||||
"idx": 0,
|
"idx": 0,
|
||||||
"is_standard": 1,
|
"is_standard": 1,
|
||||||
"label": "Support",
|
"label": "Support",
|
||||||
"modified": "2020-05-28 13:51:23.869954",
|
"modified": "2020-06-04 11:54:56.124219",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Support",
|
"module": "Support",
|
||||||
"name": "Support",
|
"name": "Support",
|
||||||
@ -65,8 +65,8 @@
|
|||||||
"type": "DocType"
|
"type": "DocType"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "Service Level",
|
"label": "Service Level Agreement",
|
||||||
"link_to": "Service Level",
|
"link_to": "Service Level Agreement",
|
||||||
"type": "DocType"
|
"type": "DocType"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -38,10 +38,35 @@ frappe.ui.form.on("Issue", {
|
|||||||
},
|
},
|
||||||
|
|
||||||
refresh: function (frm) {
|
refresh: function (frm) {
|
||||||
|
|
||||||
if (frm.doc.status !== "Closed" && frm.doc.agreement_fulfilled === "Ongoing") {
|
if (frm.doc.status !== "Closed" && frm.doc.agreement_fulfilled === "Ongoing") {
|
||||||
if (frm.doc.service_level_agreement) {
|
if (frm.doc.service_level_agreement) {
|
||||||
set_time_to_resolve_and_response(frm);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
frm.add_custom_button(__("Close"), function () {
|
frm.add_custom_button(__("Close"), function () {
|
||||||
@ -55,6 +80,7 @@ frappe.ui.form.on("Issue", {
|
|||||||
frm: frm
|
frm: frm
|
||||||
});
|
});
|
||||||
}, __("Make"));
|
}, __("Make"));
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if (frm.doc.service_level_agreement) {
|
if (frm.doc.service_level_agreement) {
|
||||||
frm.dashboard.clear_headline();
|
frm.dashboard.clear_headline();
|
||||||
|
@ -31,9 +31,13 @@
|
|||||||
"resolution_by",
|
"resolution_by",
|
||||||
"resolution_by_variance",
|
"resolution_by_variance",
|
||||||
"service_level_agreement_creation",
|
"service_level_agreement_creation",
|
||||||
|
"on_hold_since",
|
||||||
|
"total_hold_time",
|
||||||
"response",
|
"response",
|
||||||
"mins_to_first_response",
|
"mins_to_first_response",
|
||||||
"first_responded_on",
|
"first_responded_on",
|
||||||
|
"column_break_26",
|
||||||
|
"avg_response_time",
|
||||||
"additional_info",
|
"additional_info",
|
||||||
"lead",
|
"lead",
|
||||||
"contact",
|
"contact",
|
||||||
@ -50,7 +54,9 @@
|
|||||||
"resolution_date",
|
"resolution_date",
|
||||||
"content_type",
|
"content_type",
|
||||||
"attachment",
|
"attachment",
|
||||||
"via_customer_portal"
|
"via_customer_portal",
|
||||||
|
"resolution_time",
|
||||||
|
"user_resolution_time"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@ -114,7 +120,7 @@
|
|||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"oldfieldname": "status",
|
"oldfieldname": "status",
|
||||||
"oldfieldtype": "Select",
|
"oldfieldtype": "Select",
|
||||||
"options": "Open\nReplied\nHold\nClosed",
|
"options": "Open\nReplied\nHold\nResolved\nClosed",
|
||||||
"search_index": 1
|
"search_index": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -161,6 +167,7 @@
|
|||||||
"options": "Service Level Agreement"
|
"options": "Service Level Agreement"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"depends_on": "eval: doc.status != 'Replied';",
|
||||||
"fieldname": "response_by",
|
"fieldname": "response_by",
|
||||||
"fieldtype": "Datetime",
|
"fieldtype": "Datetime",
|
||||||
"label": "Response By",
|
"label": "Response By",
|
||||||
@ -174,6 +181,7 @@
|
|||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"depends_on": "eval: doc.status != 'Replied';",
|
||||||
"fieldname": "resolution_by",
|
"fieldname": "resolution_by",
|
||||||
"fieldtype": "Datetime",
|
"fieldtype": "Datetime",
|
||||||
"label": "Resolution By",
|
"label": "Resolution By",
|
||||||
@ -328,7 +336,7 @@
|
|||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval: doc.service_level_agreement",
|
"depends_on": "eval: doc.service_level_agreement && doc.status != 'Replied';",
|
||||||
"description": "in hours",
|
"description": "in hours",
|
||||||
"fieldname": "response_by_variance",
|
"fieldname": "response_by_variance",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
@ -336,7 +344,7 @@
|
|||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval: doc.service_level_agreement",
|
"depends_on": "eval: doc.service_level_agreement && doc.status != 'Replied';",
|
||||||
"description": "in hours",
|
"description": "in hours",
|
||||||
"fieldname": "resolution_by_variance",
|
"fieldname": "resolution_by_variance",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
@ -362,12 +370,48 @@
|
|||||||
"label": "Issue Split From",
|
"label": "Issue Split From",
|
||||||
"options": "Issue",
|
"options": "Issue",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_26",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bold": 1,
|
||||||
|
"fieldname": "avg_response_time",
|
||||||
|
"fieldtype": "Duration",
|
||||||
|
"label": "Average Response Time",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "resolution_time",
|
||||||
|
"fieldtype": "Duration",
|
||||||
|
"label": "Resolution Time",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "user_resolution_time",
|
||||||
|
"fieldtype": "Duration",
|
||||||
|
"label": "User Resolution Time",
|
||||||
|
"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
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-ticket",
|
"icon": "fa fa-ticket",
|
||||||
"idx": 7,
|
"idx": 7,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-03-13 02:19:49.477928",
|
"modified": "2020-06-10 12:47:37.146914",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Support",
|
"module": "Support",
|
||||||
"name": "Issue",
|
"name": "Issue",
|
||||||
@ -395,4 +439,4 @@
|
|||||||
"title_field": "subject",
|
"title_field": "subject",
|
||||||
"track_changes": 1,
|
"track_changes": 1,
|
||||||
"track_seen": 1
|
"track_seen": 1
|
||||||
}
|
}
|
@ -7,7 +7,7 @@ import json
|
|||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe import utils
|
from frappe import utils
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.utils import now, time_diff_in_hours, now_datetime, getdate, get_weekdays, add_to_date, today, get_time, get_datetime
|
from frappe.utils import time_diff_in_hours, now_datetime, getdate, get_weekdays, add_to_date, today, get_time, get_datetime, time_diff_in_seconds, time_diff
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from frappe.model.mapper import get_mapped_doc
|
from frappe.model.mapper import get_mapped_doc
|
||||||
from frappe.utils.user import is_website_user
|
from frappe.utils.user import is_website_user
|
||||||
@ -47,8 +47,8 @@ class Issue(Document):
|
|||||||
self.contact = frappe.db.get_value("Contact", {"email_id": email_id})
|
self.contact = frappe.db.get_value("Contact", {"email_id": email_id})
|
||||||
|
|
||||||
if self.contact:
|
if self.contact:
|
||||||
contact = frappe.get_doc('Contact', self.contact)
|
contact = frappe.get_doc("Contact", self.contact)
|
||||||
self.customer = contact.get_link_for('Customer')
|
self.customer = contact.get_link_for("Customer")
|
||||||
|
|
||||||
if not self.company:
|
if not self.company:
|
||||||
self.company = frappe.db.get_value("Lead", self.lead, "company") or \
|
self.company = frappe.db.get_value("Lead", self.lead, "company") or \
|
||||||
@ -56,18 +56,70 @@ class Issue(Document):
|
|||||||
|
|
||||||
def update_status(self):
|
def update_status(self):
|
||||||
status = frappe.db.get_value("Issue", self.name, "status")
|
status = frappe.db.get_value("Issue", self.name, "status")
|
||||||
if self.status!="Open" and status =="Open" and not self.first_responded_on:
|
if self.status != "Open" and status == "Open" and not self.first_responded_on:
|
||||||
self.first_responded_on = frappe.flags.current_time or now_datetime()
|
self.first_responded_on = frappe.flags.current_time or now_datetime()
|
||||||
|
|
||||||
if self.status=="Closed" and status !="Closed":
|
if self.status in ["Closed", "Resolved"] and status not in ["Resolved", "Closed"]:
|
||||||
self.resolution_date = frappe.flags.current_time or now_datetime()
|
self.resolution_date = frappe.flags.current_time or now_datetime()
|
||||||
if frappe.db.get_value("Issue", self.name, "agreement_fulfilled") == "Ongoing":
|
if frappe.db.get_value("Issue", self.name, "agreement_fulfilled") == "Ongoing":
|
||||||
set_service_level_agreement_variance(issue=self.name)
|
set_service_level_agreement_variance(issue=self.name)
|
||||||
self.update_agreement_status()
|
self.update_agreement_status()
|
||||||
|
set_resolution_time(issue=self)
|
||||||
|
set_user_resolution_time(issue=self)
|
||||||
|
|
||||||
if self.status=="Open" and status !="Open":
|
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
|
# if no date, it should be set as None and not a blank string "", as per mysql strict config
|
||||||
self.resolution_date = None
|
self.resolution_date = None
|
||||||
|
self.reset_issue_metrics()
|
||||||
|
# enable SLA and variance on Reopen
|
||||||
|
self.agreement_fulfilled = "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 for status as Replied
|
||||||
|
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 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 Replied to any other 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()
|
||||||
|
update_values['total_hold_time'] = hold_time + time_diff_in_seconds(now_time, self.on_hold_since)
|
||||||
|
|
||||||
|
# re-calculate SLA variables after issue changes from Replied to Open
|
||||||
|
# add hold time to SLA variables
|
||||||
|
if self.status == "Open" and status in hold_statuses:
|
||||||
|
start_date_time = get_datetime(self.service_level_agreement_creation)
|
||||||
|
priority = get_priority(self)
|
||||||
|
now_time = frappe.flags.current_time or now_datetime()
|
||||||
|
hold_time = time_diff_in_seconds(now_time, self.on_hold_since)
|
||||||
|
|
||||||
|
if not self.first_responded_on:
|
||||||
|
response_by = get_expected_time_for(parameter="response", service_level=priority, start_date_time=start_date_time)
|
||||||
|
update_values['response_by'] = add_to_date(response_by, seconds=round(hold_time))
|
||||||
|
response_by_variance = round(time_diff_in_hours(self.response_by, now_time))
|
||||||
|
update_values['response_by_variance'] = response_by_variance + (hold_time // 3600)
|
||||||
|
|
||||||
|
resolution_by = get_expected_time_for(parameter="resolution", service_level=priority, start_date_time=start_date_time)
|
||||||
|
update_values['resolution_by'] = add_to_date(resolution_by, seconds=round(hold_time))
|
||||||
|
resolution_by_variance = round(time_diff_in_hours(self.resolution_by, now_time))
|
||||||
|
update_values['resolution_by_variance'] = resolution_by_variance + (hold_time // 3600)
|
||||||
|
update_values['on_hold_since'] = None
|
||||||
|
|
||||||
|
self.db_set(update_values)
|
||||||
|
|
||||||
def update_agreement_status(self):
|
def update_agreement_status(self):
|
||||||
if self.service_level_agreement and self.agreement_fulfilled == "Ongoing":
|
if self.service_level_agreement and self.agreement_fulfilled == "Ongoing":
|
||||||
@ -128,6 +180,7 @@ class Issue(Document):
|
|||||||
replicated_issue.response_by_variance = None
|
replicated_issue.response_by_variance = None
|
||||||
replicated_issue.resolution_by = None
|
replicated_issue.resolution_by = None
|
||||||
replicated_issue.resolution_by_variance = None
|
replicated_issue.resolution_by_variance = None
|
||||||
|
replicated_issue.reset_issue_metrics()
|
||||||
|
|
||||||
frappe.get_doc(replicated_issue).insert()
|
frappe.get_doc(replicated_issue).insert()
|
||||||
|
|
||||||
@ -137,7 +190,7 @@ class Issue(Document):
|
|||||||
communications = frappe.get_all("Communication",
|
communications = frappe.get_all("Communication",
|
||||||
filters={"reference_doctype": "Issue",
|
filters={"reference_doctype": "Issue",
|
||||||
"reference_name": comm_to_split_from.reference_name,
|
"reference_name": comm_to_split_from.reference_name,
|
||||||
"creation": ('>=', comm_to_split_from.creation)})
|
"creation": (">=", comm_to_split_from.creation)})
|
||||||
|
|
||||||
for communication in communications:
|
for communication in communications:
|
||||||
doc = frappe.get_doc("Communication", communication.name)
|
doc = frappe.get_doc("Communication", communication.name)
|
||||||
@ -173,20 +226,15 @@ class Issue(Document):
|
|||||||
self.service_level_agreement = service_level_agreement.name
|
self.service_level_agreement = service_level_agreement.name
|
||||||
self.priority = service_level_agreement.default_priority if not priority else priority
|
self.priority = service_level_agreement.default_priority if not priority else priority
|
||||||
|
|
||||||
service_level_agreement = frappe.get_doc("Service Level Agreement", service_level_agreement.name)
|
priority = get_priority(self)
|
||||||
priority = service_level_agreement.get_service_level_agreement_priority(self.priority)
|
|
||||||
priority.update({
|
|
||||||
"support_and_resolution": service_level_agreement.support_and_resolution,
|
|
||||||
"holiday_list": service_level_agreement.holiday_list
|
|
||||||
})
|
|
||||||
|
|
||||||
if not self.creation:
|
if not self.creation:
|
||||||
self.creation = now_datetime()
|
self.creation = now_datetime()
|
||||||
self.service_level_agreement_creation = now_datetime()
|
self.service_level_agreement_creation = now_datetime()
|
||||||
|
|
||||||
start_date_time = get_datetime(self.service_level_agreement_creation)
|
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.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.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_hours(self.response_by, now_datetime()))
|
self.response_by_variance = round(time_diff_in_hours(self.response_by, now_datetime()))
|
||||||
self.resolution_by_variance = round(time_diff_in_hours(self.resolution_by, now_datetime()))
|
self.resolution_by_variance = round(time_diff_in_hours(self.resolution_by, now_datetime()))
|
||||||
@ -221,36 +269,41 @@ class Issue(Document):
|
|||||||
self.agreement_fulfilled = "Ongoing"
|
self.agreement_fulfilled = "Ongoing"
|
||||||
self.save()
|
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):
|
def get_expected_time_for(parameter, service_level, start_date_time):
|
||||||
current_date_time = start_date_time
|
current_date_time = start_date_time
|
||||||
expected_time = current_date_time
|
expected_time = current_date_time
|
||||||
start_time = None
|
start_time = None
|
||||||
end_time = None
|
end_time = None
|
||||||
|
|
||||||
# lets assume response time is in days by default
|
if parameter == "response":
|
||||||
if parameter == 'response':
|
allotted_seconds = service_level.get("response_time")
|
||||||
allotted_days = service_level.get("response_time")
|
elif parameter == "resolution":
|
||||||
time_period = service_level.get("response_time_period")
|
allotted_seconds = service_level.get("resolution_time")
|
||||||
elif parameter == 'resolution':
|
|
||||||
allotted_days = service_level.get("resolution_time")
|
|
||||||
time_period = service_level.get("resolution_time_period")
|
|
||||||
else:
|
else:
|
||||||
frappe.throw(_("{0} parameter is invalid").format(parameter))
|
frappe.throw(_("{0} parameter is invalid").format(parameter))
|
||||||
|
|
||||||
allotted_hours = 0
|
expected_time_is_set = 0
|
||||||
if time_period == 'Hour':
|
|
||||||
allotted_hours = allotted_days
|
|
||||||
allotted_days = 0
|
|
||||||
elif time_period == 'Week':
|
|
||||||
allotted_days *= 7
|
|
||||||
|
|
||||||
expected_time_is_set = 1 if allotted_days == 0 and time_period in ['Day', 'Week'] else 0
|
|
||||||
|
|
||||||
support_days = {}
|
support_days = {}
|
||||||
for service in service_level.get("support_and_resolution"):
|
for service in service_level.get("support_and_resolution"):
|
||||||
support_days[service.workday] = frappe._dict({
|
support_days[service.workday] = frappe._dict({
|
||||||
'start_time': service.start_time,
|
"start_time": service.start_time,
|
||||||
'end_time': service.end_time,
|
"end_time": service.end_time,
|
||||||
})
|
})
|
||||||
|
|
||||||
holidays = get_holidays(service_level.get("holiday_list"))
|
holidays = get_holidays(service_level.get("holiday_list"))
|
||||||
@ -264,25 +317,22 @@ def get_expected_time_for(parameter, service_level, start_date_time):
|
|||||||
if getdate(current_date_time) == getdate(start_date_time) and get_time_in_timedelta(current_date_time.time()) > support_days[current_weekday].start_time \
|
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
|
else support_days[current_weekday].start_time
|
||||||
end_time = support_days[current_weekday].end_time
|
end_time = support_days[current_weekday].end_time
|
||||||
time_left_today = time_diff_in_hours(end_time, start_time)
|
time_left_today = time_diff_in_seconds(end_time, start_time)
|
||||||
|
|
||||||
# no time left for support today
|
# no time left for support today
|
||||||
if time_left_today < 0: pass
|
if time_left_today <= 0: pass
|
||||||
elif time_period == 'Hour':
|
elif allotted_seconds:
|
||||||
if time_left_today >= allotted_hours:
|
if time_left_today >= allotted_seconds:
|
||||||
expected_time = datetime.combine(getdate(current_date_time), get_time(start_time))
|
expected_time = datetime.combine(getdate(current_date_time), get_time(start_time))
|
||||||
expected_time = add_to_date(expected_time, hours=allotted_hours)
|
expected_time = add_to_date(expected_time, seconds=allotted_seconds)
|
||||||
expected_time_is_set = 1
|
expected_time_is_set = 1
|
||||||
else:
|
else:
|
||||||
allotted_hours = allotted_hours - time_left_today
|
allotted_seconds = allotted_seconds - time_left_today
|
||||||
else:
|
|
||||||
allotted_days -= 1
|
|
||||||
expected_time_is_set = allotted_days <= 0
|
|
||||||
|
|
||||||
if not expected_time_is_set:
|
if not expected_time_is_set:
|
||||||
current_date_time = add_to_date(current_date_time, days=1)
|
current_date_time = add_to_date(current_date_time, days=1)
|
||||||
|
|
||||||
if end_time and time_period != 'Hour':
|
if end_time and allotted_seconds >= 86400:
|
||||||
current_date_time = datetime.combine(getdate(current_date_time), get_time(end_time))
|
current_date_time = datetime.combine(getdate(current_date_time), get_time(end_time))
|
||||||
else:
|
else:
|
||||||
current_date_time = expected_time
|
current_date_time = expected_time
|
||||||
@ -311,6 +361,36 @@ def set_service_level_agreement_variance(issue=None):
|
|||||||
if variance < 0:
|
if variance < 0:
|
||||||
frappe.db.set_value(dt="Issue", dn=doc.name, field="agreement_fulfilled", val="Failed", update_modified=False)
|
frappe.db.set_value(dt="Issue", dn=doc.name, field="agreement_fulfilled", 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):
|
def get_list_context(context=None):
|
||||||
return {
|
return {
|
||||||
"title": _("Issues"),
|
"title": _("Issues"),
|
||||||
@ -318,7 +398,7 @@ def get_list_context(context=None):
|
|||||||
"row_template": "templates/includes/issue_row.html",
|
"row_template": "templates/includes/issue_row.html",
|
||||||
"show_sidebar": True,
|
"show_sidebar": True,
|
||||||
"show_search": True,
|
"show_search": True,
|
||||||
'no_breadcrumbs': True
|
"no_breadcrumbs": True
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -326,12 +406,12 @@ def get_issue_list(doctype, txt, filters, limit_start, limit_page_length=20, ord
|
|||||||
from frappe.www.list import get_list
|
from frappe.www.list import get_list
|
||||||
|
|
||||||
user = frappe.session.user
|
user = frappe.session.user
|
||||||
contact = frappe.db.get_value('Contact', {'user': user}, 'name')
|
contact = frappe.db.get_value("Contact", {"user": user}, "name")
|
||||||
customer = None
|
customer = None
|
||||||
|
|
||||||
if contact:
|
if contact:
|
||||||
contact_doc = frappe.get_doc('Contact', contact)
|
contact_doc = frappe.get_doc("Contact", contact)
|
||||||
customer = contact_doc.get_link_for('Customer')
|
customer = contact_doc.get_link_for("Customer")
|
||||||
|
|
||||||
ignore_permissions = False
|
ignore_permissions = False
|
||||||
if is_website_user():
|
if is_website_user():
|
||||||
|
@ -5,15 +5,18 @@ from __future__ import unicode_literals
|
|||||||
import frappe
|
import frappe
|
||||||
import unittest
|
import unittest
|
||||||
from erpnext.support.doctype.service_level_agreement.test_service_level_agreement import create_service_level_agreements_for_issues
|
from erpnext.support.doctype.service_level_agreement.test_service_level_agreement import create_service_level_agreements_for_issues
|
||||||
from frappe.utils import now_datetime, get_datetime
|
from frappe.utils import now_datetime, get_datetime, flt
|
||||||
import datetime
|
import datetime
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
class TestIssue(unittest.TestCase):
|
class TestIssue(unittest.TestCase):
|
||||||
def test_response_time_and_resolution_time_based_on_different_sla(self):
|
def setUp(self):
|
||||||
|
frappe.db.sql("delete from `tabService Level Agreement`")
|
||||||
|
frappe.db.sql("delete from `tabEmployee`")
|
||||||
frappe.db.set_value("Support Settings", None, "track_service_level_agreement", 1)
|
frappe.db.set_value("Support Settings", None, "track_service_level_agreement", 1)
|
||||||
create_service_level_agreements_for_issues()
|
create_service_level_agreements_for_issues()
|
||||||
|
|
||||||
|
def test_response_time_and_resolution_time_based_on_different_sla(self):
|
||||||
creation = datetime.datetime(2019, 3, 4, 12, 0)
|
creation = datetime.datetime(2019, 3, 4, 12, 0)
|
||||||
|
|
||||||
# make issue with customer specific SLA
|
# make issue with customer specific SLA
|
||||||
@ -72,8 +75,67 @@ class TestIssue(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(issue.agreement_fulfilled, 'Fulfilled')
|
self.assertEqual(issue.agreement_fulfilled, 'Fulfilled')
|
||||||
|
|
||||||
def make_issue(creation=None, customer=None, index=0):
|
def test_issue_metrics(self):
|
||||||
|
creation = datetime.datetime(2020, 3, 4, 4, 0)
|
||||||
|
|
||||||
|
issue = make_issue(creation, index=1)
|
||||||
|
create_communication(issue.name, "test@example.com", "Received", creation)
|
||||||
|
|
||||||
|
creation = datetime.datetime(2020, 3, 4, 4, 15)
|
||||||
|
create_communication(issue.name, "test@admin.com", "Sent", creation)
|
||||||
|
|
||||||
|
creation = datetime.datetime(2020, 3, 4, 5, 0)
|
||||||
|
create_communication(issue.name, "test@example.com", "Received", creation)
|
||||||
|
|
||||||
|
creation = datetime.datetime(2020, 3, 4, 5, 5)
|
||||||
|
create_communication(issue.name, "test@admin.com", "Sent", creation)
|
||||||
|
|
||||||
|
frappe.flags.current_time = datetime.datetime(2020, 3, 4, 5, 5)
|
||||||
|
issue.reload()
|
||||||
|
issue.status = 'Closed'
|
||||||
|
issue.save()
|
||||||
|
|
||||||
|
self.assertEqual(issue.avg_response_time, 600)
|
||||||
|
self.assertEqual(issue.resolution_time, 3900)
|
||||||
|
self.assertEqual(issue.user_resolution_time, 1200)
|
||||||
|
|
||||||
|
def test_hold_time_on_replied(self):
|
||||||
|
creation = datetime.datetime(2020, 3, 4, 4, 0)
|
||||||
|
|
||||||
|
issue = make_issue(creation, index=1)
|
||||||
|
create_communication(issue.name, "test@example.com", "Received", creation)
|
||||||
|
|
||||||
|
creation = datetime.datetime(2020, 3, 4, 4, 15)
|
||||||
|
create_communication(issue.name, "test@admin.com", "Sent", creation)
|
||||||
|
|
||||||
|
frappe.flags.current_time = datetime.datetime(2020, 3, 4, 4, 15)
|
||||||
|
issue.reload()
|
||||||
|
issue.status = 'Replied'
|
||||||
|
issue.save()
|
||||||
|
|
||||||
|
self.assertEqual(issue.on_hold_since, frappe.flags.current_time)
|
||||||
|
|
||||||
|
creation = datetime.datetime(2020, 3, 4, 5, 0)
|
||||||
|
frappe.flags.current_time = datetime.datetime(2020, 3, 4, 5, 0)
|
||||||
|
create_communication(issue.name, "test@example.com", "Received", creation)
|
||||||
|
|
||||||
|
issue.reload()
|
||||||
|
self.assertEqual(flt(issue.total_hold_time, 2), 2700)
|
||||||
|
self.assertEqual(issue.resolution_by, datetime.datetime(2020, 3, 4, 16, 45))
|
||||||
|
|
||||||
|
creation = datetime.datetime(2020, 3, 4, 5, 5)
|
||||||
|
create_communication(issue.name, "test@admin.com", "Sent", creation)
|
||||||
|
|
||||||
|
frappe.flags.current_time = datetime.datetime(2020, 3, 4, 5, 5)
|
||||||
|
issue.reload()
|
||||||
|
issue.status = 'Closed'
|
||||||
|
issue.save()
|
||||||
|
|
||||||
|
issue.reload()
|
||||||
|
self.assertEqual(flt(issue.total_hold_time, 2), 2700)
|
||||||
|
|
||||||
|
|
||||||
|
def make_issue(creation=None, customer=None, index=0):
|
||||||
issue = frappe.get_doc({
|
issue = frappe.get_doc({
|
||||||
"doctype": "Issue",
|
"doctype": "Issue",
|
||||||
"subject": "Service Level Agreement Issue {0}".format(index),
|
"subject": "Service Level Agreement Issue {0}".format(index),
|
||||||
@ -86,6 +148,7 @@ def make_issue(creation=None, customer=None, index=0):
|
|||||||
|
|
||||||
return issue
|
return issue
|
||||||
|
|
||||||
|
|
||||||
def create_customer(name, customer_group, territory):
|
def create_customer(name, customer_group, territory):
|
||||||
|
|
||||||
create_customer_group(customer_group)
|
create_customer_group(customer_group)
|
||||||
@ -99,6 +162,7 @@ def create_customer(name, customer_group, territory):
|
|||||||
"territory": territory
|
"territory": territory
|
||||||
}).insert(ignore_permissions=True)
|
}).insert(ignore_permissions=True)
|
||||||
|
|
||||||
|
|
||||||
def create_customer_group(customer_group):
|
def create_customer_group(customer_group):
|
||||||
|
|
||||||
if not frappe.db.exists("Customer Group", {"customer_group_name": customer_group}):
|
if not frappe.db.exists("Customer Group", {"customer_group_name": customer_group}):
|
||||||
@ -107,6 +171,7 @@ def create_customer_group(customer_group):
|
|||||||
"customer_group_name": customer_group
|
"customer_group_name": customer_group
|
||||||
}).insert(ignore_permissions=True)
|
}).insert(ignore_permissions=True)
|
||||||
|
|
||||||
|
|
||||||
def create_territory(territory):
|
def create_territory(territory):
|
||||||
|
|
||||||
if not frappe.db.exists("Territory", {"territory_name": territory}):
|
if not frappe.db.exists("Territory", {"territory_name": territory}):
|
||||||
@ -114,3 +179,21 @@ def create_territory(territory):
|
|||||||
"doctype": "Territory",
|
"doctype": "Territory",
|
||||||
"territory_name": territory,
|
"territory_name": territory,
|
||||||
}).insert(ignore_permissions=True)
|
}).insert(ignore_permissions=True)
|
||||||
|
|
||||||
|
|
||||||
|
def create_communication(reference_name, sender, sent_or_received, creation):
|
||||||
|
issue = frappe.get_doc({
|
||||||
|
"doctype": "Communication",
|
||||||
|
"communication_type": "Communication",
|
||||||
|
"communication_medium": "Email",
|
||||||
|
"sent_or_received": sent_or_received,
|
||||||
|
"email_status": "Open",
|
||||||
|
"subject": "Test Issue",
|
||||||
|
"sender": sender,
|
||||||
|
"content": "Test",
|
||||||
|
"status": "Linked",
|
||||||
|
"reference_doctype": "Issue",
|
||||||
|
"creation": creation,
|
||||||
|
"reference_name": reference_name
|
||||||
|
})
|
||||||
|
issue.save()
|
||||||
|
@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"creation": "2020-06-05 13:59:43.265588",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"status"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "status",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Status",
|
||||||
|
"reqd": 1,
|
||||||
|
"show_days": 1,
|
||||||
|
"show_seconds": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"istable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2020-06-05 15:15:29.986608",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Support",
|
||||||
|
"name": "Pause SLA On Status",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [],
|
||||||
|
"quick_entry": 1,
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
class PauseSLAOnStatus(Document):
|
||||||
|
pass
|
@ -1,6 +0,0 @@
|
|||||||
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
|
||||||
// For license information, please see license.txt
|
|
||||||
|
|
||||||
frappe.ui.form.on('Service Level', {
|
|
||||||
|
|
||||||
});
|
|
@ -1,111 +0,0 @@
|
|||||||
{
|
|
||||||
"autoname": "field:service_level",
|
|
||||||
"creation": "2018-11-19 12:44:30.407502",
|
|
||||||
"doctype": "DocType",
|
|
||||||
"editable_grid": 1,
|
|
||||||
"engine": "InnoDB",
|
|
||||||
"field_order": [
|
|
||||||
"service_level",
|
|
||||||
"employee_group",
|
|
||||||
"column_break_2",
|
|
||||||
"holiday_list",
|
|
||||||
"default_priority",
|
|
||||||
"response_and_resoution_time",
|
|
||||||
"priorities",
|
|
||||||
"section_break_01",
|
|
||||||
"support_and_resolution"
|
|
||||||
],
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"fieldname": "service_level",
|
|
||||||
"fieldtype": "Data",
|
|
||||||
"in_list_view": 1,
|
|
||||||
"label": "Level",
|
|
||||||
"reqd": 1,
|
|
||||||
"unique": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "column_break_2",
|
|
||||||
"fieldtype": "Column Break"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "holiday_list",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"in_list_view": 1,
|
|
||||||
"label": "Holiday List (ignored during SLA calculation)",
|
|
||||||
"options": "Holiday List",
|
|
||||||
"reqd": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "employee_group",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"in_list_view": 1,
|
|
||||||
"label": "Employee Group",
|
|
||||||
"options": "Employee Group"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "response_and_resoution_time",
|
|
||||||
"fieldtype": "Section Break",
|
|
||||||
"label": "Response and Resoution Time"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "section_break_01",
|
|
||||||
"fieldtype": "Section Break",
|
|
||||||
"label": "Support Hours"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "support_and_resolution",
|
|
||||||
"fieldtype": "Table",
|
|
||||||
"label": "Support and Resolution",
|
|
||||||
"options": "Service Day",
|
|
||||||
"reqd": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "priorities",
|
|
||||||
"fieldtype": "Table",
|
|
||||||
"label": "Priorities",
|
|
||||||
"options": "Service Level Priority",
|
|
||||||
"reqd": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "default_priority",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"label": "Default Priority",
|
|
||||||
"options": "Issue Priority",
|
|
||||||
"read_only": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"modified": "2019-06-06 12:58:03.464056",
|
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": "Support",
|
|
||||||
"name": "Service Level",
|
|
||||||
"owner": "Administrator",
|
|
||||||
"permissions": [
|
|
||||||
{
|
|
||||||
"create": 1,
|
|
||||||
"delete": 1,
|
|
||||||
"email": 1,
|
|
||||||
"export": 1,
|
|
||||||
"print": 1,
|
|
||||||
"read": 1,
|
|
||||||
"report": 1,
|
|
||||||
"role": "System Manager",
|
|
||||||
"share": 1,
|
|
||||||
"write": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"create": 1,
|
|
||||||
"delete": 1,
|
|
||||||
"email": 1,
|
|
||||||
"export": 1,
|
|
||||||
"print": 1,
|
|
||||||
"read": 1,
|
|
||||||
"report": 1,
|
|
||||||
"role": "All",
|
|
||||||
"share": 1,
|
|
||||||
"write": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"sort_field": "modified",
|
|
||||||
"sort_order": "DESC"
|
|
||||||
}
|
|
@ -1,95 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
|
||||||
# For license information, please see license.txt
|
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
import frappe
|
|
||||||
from frappe import _
|
|
||||||
from frappe.model.document import Document
|
|
||||||
from datetime import datetime
|
|
||||||
from frappe.utils import get_weekdays
|
|
||||||
|
|
||||||
class ServiceLevel(Document):
|
|
||||||
|
|
||||||
def validate(self):
|
|
||||||
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 priority.resolution_time):
|
|
||||||
frappe.throw(_("Set Response Time and Resolution for Priority {0} at index {1}.").format(priority.priority, priority.idx))
|
|
||||||
|
|
||||||
priorities.append(priority.priority)
|
|
||||||
|
|
||||||
if priority.default_priority:
|
|
||||||
default_priority.append(priority.default_priority)
|
|
||||||
|
|
||||||
if priority.response_time_period == "Hour":
|
|
||||||
response = priority.response_time * 0.0416667
|
|
||||||
elif priority.response_time_period == "Day":
|
|
||||||
response = priority.response_time
|
|
||||||
elif priority.response_time_period == "Week":
|
|
||||||
response = priority.response_time * 7
|
|
||||||
|
|
||||||
if priority.resolution_time_period == "Hour":
|
|
||||||
resolution = priority.resolution_time * 0.0416667
|
|
||||||
elif priority.resolution_time_period == "Day":
|
|
||||||
resolution = priority.resolution_time
|
|
||||||
elif priority.resolution_time_period == "Week":
|
|
||||||
resolution = priority.resolution_time * 7
|
|
||||||
|
|
||||||
if response > resolution:
|
|
||||||
frappe.throw(_("Response Time for {0} at index {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)
|
|
||||||
except Exception:
|
|
||||||
frappe.throw(_("Select a Default Priority."))
|
|
||||||
|
|
||||||
def check_support_and_resolution(self):
|
|
||||||
week = get_weekdays()
|
|
||||||
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)))
|
|
||||||
|
|
||||||
# Check for repeated workday
|
|
||||||
if not len(set(support_days)) == len(support_days):
|
|
||||||
repeated_days = get_repeated(support_days)
|
|
||||||
frappe.throw(_("Workday {0} has been repeated.").format(repeated_days))
|
|
||||||
|
|
||||||
def get_repeated(values):
|
|
||||||
unique_list = []
|
|
||||||
diff = []
|
|
||||||
for value in values:
|
|
||||||
if value not in unique_list:
|
|
||||||
unique_list.append(str(value))
|
|
||||||
else:
|
|
||||||
if value not in diff:
|
|
||||||
diff.append(str(value))
|
|
||||||
return " ".join(diff)
|
|
@ -1,12 +0,0 @@
|
|||||||
from frappe import _
|
|
||||||
|
|
||||||
def get_data():
|
|
||||||
return {
|
|
||||||
'fieldname': 'service_level',
|
|
||||||
'transactions': [
|
|
||||||
{
|
|
||||||
'label': _('Service Level Agreement'),
|
|
||||||
'items': ['Service Level Agreement']
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
@ -1,149 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
|
||||||
# See license.txt
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
from erpnext.hr.doctype.employee_group.test_employee_group import make_employee_group
|
|
||||||
from erpnext.support.doctype.issue_priority.test_issue_priority import make_priorities
|
|
||||||
|
|
||||||
import frappe
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
class TestServiceLevel(unittest.TestCase):
|
|
||||||
|
|
||||||
def test_service_level(self):
|
|
||||||
employee_group = make_employee_group()
|
|
||||||
make_holiday_list()
|
|
||||||
make_priorities()
|
|
||||||
|
|
||||||
# Default Service Level
|
|
||||||
test_make_service_level = create_service_level("__Test Service Level", "__Test Holiday List", employee_group, 4, 6)
|
|
||||||
get_make_service_level = get_service_level("__Test Service Level")
|
|
||||||
|
|
||||||
self.assertEqual(test_make_service_level.name, get_make_service_level.name)
|
|
||||||
self.assertEqual(test_make_service_level.holiday_list, get_make_service_level.holiday_list)
|
|
||||||
self.assertEqual(test_make_service_level.employee_group, get_make_service_level.employee_group)
|
|
||||||
|
|
||||||
# Service Level
|
|
||||||
test_make_service_level = create_service_level("_Test Service Level", "__Test Holiday List", employee_group, 2, 3)
|
|
||||||
get_make_service_level = get_service_level("_Test Service Level")
|
|
||||||
|
|
||||||
self.assertEqual(test_make_service_level.name, get_make_service_level.name)
|
|
||||||
self.assertEqual(test_make_service_level.holiday_list, get_make_service_level.holiday_list)
|
|
||||||
self.assertEqual(test_make_service_level.employee_group, get_make_service_level.employee_group)
|
|
||||||
|
|
||||||
|
|
||||||
def create_service_level(service_level, holiday_list, employee_group, response_time, resolution_time):
|
|
||||||
sl = frappe.get_doc({
|
|
||||||
"doctype": "Service Level",
|
|
||||||
"service_level": service_level,
|
|
||||||
"holiday_list": holiday_list,
|
|
||||||
"employee_group": employee_group,
|
|
||||||
"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",
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"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",
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
sl_exists = frappe.db.exists("Service Level", {"service_level": service_level})
|
|
||||||
|
|
||||||
if not sl_exists:
|
|
||||||
sl.insert()
|
|
||||||
return sl
|
|
||||||
else:
|
|
||||||
return frappe.get_doc("Service Level", {"service_level": service_level})
|
|
||||||
|
|
||||||
def get_service_level(service_level):
|
|
||||||
return frappe.get_doc("Service Level", service_level)
|
|
||||||
|
|
||||||
def make_holiday_list():
|
|
||||||
holiday_list = frappe.db.exists("Holiday List", "__Test Holiday List")
|
|
||||||
if not holiday_list:
|
|
||||||
now = frappe.utils.now_datetime()
|
|
||||||
holiday_list = frappe.get_doc({
|
|
||||||
"doctype": "Holiday List",
|
|
||||||
"holiday_list_name": "__Test Holiday List",
|
|
||||||
"from_date": "2019-01-01",
|
|
||||||
"to_date": "2019-12-31",
|
|
||||||
"holidays": [
|
|
||||||
{
|
|
||||||
"description": "Test Holiday 1",
|
|
||||||
"holiday_date": "2019-03-05"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Test Holiday 2",
|
|
||||||
"holiday_date": "2019-03-07"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Test Holiday 3",
|
|
||||||
"holiday_date": "2019-02-11"
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}).insert()
|
|
||||||
|
|
||||||
def create_service_level_for_sla():
|
|
||||||
employee_group = make_employee_group()
|
|
||||||
make_holiday_list()
|
|
||||||
make_priorities()
|
|
||||||
|
|
||||||
# Default Service Level
|
|
||||||
create_service_level("__Test Service Level", "__Test Holiday List", employee_group, 4, 6)
|
|
||||||
|
|
||||||
# Service Level
|
|
||||||
create_service_level("_Test Service Level", "__Test Holiday List", employee_group, 2, 3)
|
|
@ -2,28 +2,15 @@
|
|||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
|
|
||||||
frappe.ui.form.on('Service Level Agreement', {
|
frappe.ui.form.on('Service Level Agreement', {
|
||||||
service_level: function(frm) {
|
setup: function(frm) {
|
||||||
frm.fields_dict.support_and_resolution.grid.remove_all();
|
let allow_statuses = [];
|
||||||
frappe.call({
|
const exclude_statuses = ['Open', 'Closed', 'Resolved'];
|
||||||
"method": "frappe.client.get",
|
|
||||||
args: {
|
frappe.model.with_doctype('Issue', () => {
|
||||||
doctype: "Service Level",
|
let statuses = frappe.meta.get_docfield('Issue', 'status', frm.doc.name).options;
|
||||||
name: frm.doc.service_level
|
statuses = statuses.split('\n');
|
||||||
},
|
allow_statuses = statuses.filter((status) => !exclude_statuses.includes(status));
|
||||||
callback: function(data){
|
frappe.meta.get_docfield('Pause SLA On Status', 'status', frm.doc.name).options = [''].concat(allow_statuses);
|
||||||
let count = Math.max(data.message.priorities.length, data.message.support_and_resolution.length);
|
|
||||||
let i = 0;
|
|
||||||
while (i < count){
|
|
||||||
if (data.message.priorities[i]) {
|
|
||||||
frm.add_child("priorities", data.message.priorities[i]);
|
|
||||||
}
|
|
||||||
if (data.message.support_and_resolution[i]) {
|
|
||||||
frm.add_child("support_and_resolution", data.message.support_and_resolution[i]);
|
|
||||||
}
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
frm.refresh();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
});
|
});
|
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"actions": [],
|
||||||
"autoname": "format:SLA-{service_level}-{####}",
|
"autoname": "format:SLA-{service_level}-{####}",
|
||||||
"creation": "2018-12-26 21:08:15.448812",
|
"creation": "2018-12-26 21:08:15.448812",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
@ -6,12 +7,13 @@
|
|||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
"enable",
|
"enable",
|
||||||
|
"section_break_2",
|
||||||
"service_level",
|
"service_level",
|
||||||
|
"default_priority",
|
||||||
"default_service_level_agreement",
|
"default_service_level_agreement",
|
||||||
"holiday_list",
|
|
||||||
"column_break_2",
|
"column_break_2",
|
||||||
"employee_group",
|
"employee_group",
|
||||||
"default_priority",
|
"holiday_list",
|
||||||
"entity_section",
|
"entity_section",
|
||||||
"entity_type",
|
"entity_type",
|
||||||
"column_break_10",
|
"column_break_10",
|
||||||
@ -21,49 +23,40 @@
|
|||||||
"active",
|
"active",
|
||||||
"column_break_7",
|
"column_break_7",
|
||||||
"end_date",
|
"end_date",
|
||||||
|
"section_break_18",
|
||||||
|
"pause_sla_on",
|
||||||
"response_and_resolution_time_section",
|
"response_and_resolution_time_section",
|
||||||
"priorities",
|
"priorities",
|
||||||
"support_and_resolution_section_break",
|
"support_and_resolution_section_break",
|
||||||
"support_and_resolution"
|
"support_and_resolution"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
|
||||||
"default": "0",
|
|
||||||
"depends_on": "eval: !doc.customer;",
|
|
||||||
"fieldname": "default_service_level_agreement",
|
|
||||||
"fieldtype": "Check",
|
|
||||||
"label": "Default Service Level Agreement"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "service_level",
|
"fieldname": "service_level",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Data",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 1,
|
"in_standard_filter": 1,
|
||||||
"label": "Service Level",
|
"label": "Service Level",
|
||||||
"options": "Service Level",
|
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fetch_from": "service_level.holiday_list",
|
|
||||||
"fieldname": "holiday_list",
|
"fieldname": "holiday_list",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Holiday List",
|
"label": "Holiday List",
|
||||||
"options": "Holiday List",
|
"options": "Holiday List",
|
||||||
"read_only": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "column_break_2",
|
"fieldname": "column_break_2",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fetch_from": "service_level.employee_group",
|
|
||||||
"fieldname": "employee_group",
|
"fieldname": "employee_group",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 1,
|
"in_standard_filter": 1,
|
||||||
"label": "Employee Group",
|
"label": "Employee Group",
|
||||||
"options": "Employee Group",
|
"options": "Employee Group"
|
||||||
"read_only": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "agreement_details_section",
|
"fieldname": "agreement_details_section",
|
||||||
@ -103,21 +96,15 @@
|
|||||||
"fieldname": "support_and_resolution",
|
"fieldname": "support_and_resolution",
|
||||||
"fieldtype": "Table",
|
"fieldtype": "Table",
|
||||||
"label": "Support and Resolution",
|
"label": "Support and Resolution",
|
||||||
"options": "Service Day"
|
"options": "Service Day",
|
||||||
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "priorities",
|
"fieldname": "priorities",
|
||||||
"fieldtype": "Table",
|
"fieldtype": "Table",
|
||||||
"label": "Priorities",
|
"label": "Priorities",
|
||||||
"options": "Service Level Priority"
|
"options": "Service Level Priority",
|
||||||
},
|
"reqd": 1
|
||||||
{
|
|
||||||
"fetch_from": "service_level.default_priority",
|
|
||||||
"fieldname": "default_priority",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"label": "Default Priority",
|
|
||||||
"options": "Issue Priority",
|
|
||||||
"read_only": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "1",
|
"default": "1",
|
||||||
@ -156,9 +143,38 @@
|
|||||||
"fieldname": "enable",
|
"fieldname": "enable",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Enable"
|
"label": "Enable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_2",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "default_service_level_agreement",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Default Service Level Agreement"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "default_priority",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Default Priority",
|
||||||
|
"options": "Issue Priority",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_18",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"hide_border": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "pause_sla_on",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"label": "Pause SLA On",
|
||||||
|
"options": "Pause SLA On Status"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2019-07-09 17:22:16.402939",
|
"links": [],
|
||||||
|
"modified": "2020-06-10 12:30:15.050785",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Support",
|
"module": "Support",
|
||||||
"name": "Service Level Agreement",
|
"name": "Service Level Agreement",
|
||||||
|
@ -6,11 +6,73 @@ from __future__ import unicode_literals
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils import getdate
|
from frappe.utils import getdate, get_weekdays
|
||||||
|
|
||||||
class ServiceLevelAgreement(Document):
|
class ServiceLevelAgreement(Document):
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
|
self.validate_doc()
|
||||||
|
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 priority.resolution_time):
|
||||||
|
frappe.throw(_("Set Response Time and Resolution for Priority {0} at index {1}.").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} at index {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)
|
||||||
|
except Exception:
|
||||||
|
frappe.throw(_("Select a Default Priority."))
|
||||||
|
|
||||||
|
def check_support_and_resolution(self):
|
||||||
|
week = get_weekdays()
|
||||||
|
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)))
|
||||||
|
|
||||||
|
# Check for repeated workday
|
||||||
|
if not len(set(support_days)) == len(support_days):
|
||||||
|
repeated_days = get_repeated(support_days)
|
||||||
|
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"):
|
if not frappe.db.get_single_value("Support Settings", "track_service_level_agreement"):
|
||||||
frappe.throw(_("Service Level Agreement tracking is not enabled."))
|
frappe.throw(_("Service Level Agreement tracking is not enabled."))
|
||||||
|
|
||||||
@ -35,9 +97,7 @@ class ServiceLevelAgreement(Document):
|
|||||||
return frappe._dict({
|
return frappe._dict({
|
||||||
"priority": priority.priority,
|
"priority": priority.priority,
|
||||||
"response_time": priority.response_time,
|
"response_time": priority.response_time,
|
||||||
"response_time_period": priority.response_time_period,
|
"resolution_time": priority.resolution_time
|
||||||
"resolution_time": priority.resolution_time,
|
|
||||||
"resolution_time_period": priority.resolution_time_period
|
|
||||||
})
|
})
|
||||||
|
|
||||||
def check_agreement_status():
|
def check_agreement_status():
|
||||||
@ -110,4 +170,15 @@ def get_service_level_agreement_filters(name, customer=None):
|
|||||||
return {
|
return {
|
||||||
"priority": [priority.priority for priority in frappe.get_list("Service Level Priority", filters={"parent": name}, fields=["priority"])],
|
"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)]
|
"service_level_agreements": [d.name for d in frappe.get_list("Service Level Agreement", filters=filters, or_filters=or_filters)]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def get_repeated(values):
|
||||||
|
unique_list = []
|
||||||
|
diff = []
|
||||||
|
for value in values:
|
||||||
|
if value not in unique_list:
|
||||||
|
unique_list.append(str(value))
|
||||||
|
else:
|
||||||
|
if value not in diff:
|
||||||
|
diff.append(str(value))
|
||||||
|
return " ".join(diff)
|
||||||
|
@ -5,19 +5,20 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
import unittest
|
import unittest
|
||||||
from erpnext.support.doctype.service_level.test_service_level import create_service_level_for_sla
|
from erpnext.hr.doctype.employee_group.test_employee_group import make_employee_group
|
||||||
|
from erpnext.support.doctype.issue_priority.test_issue_priority import make_priorities
|
||||||
|
|
||||||
class TestServiceLevelAgreement(unittest.TestCase):
|
class TestServiceLevelAgreement(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
def test_service_level_agreement(self):
|
frappe.db.sql("delete from `tabService Level Agreement`")
|
||||||
frappe.db.set_value("Support Settings", None, "track_service_level_agreement", 1)
|
frappe.db.set_value("Support Settings", None, "track_service_level_agreement", 1)
|
||||||
|
|
||||||
create_service_level_for_sla()
|
def test_service_level_agreement(self):
|
||||||
|
|
||||||
# Default Service Level Agreement
|
# Default Service Level Agreement
|
||||||
create_default_service_level_agreement = create_service_level_agreement(default_service_level_agreement=1,
|
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",
|
holiday_list="__Test Holiday List", employee_group="_Test Employee Group",
|
||||||
entity_type=None, entity=None, response_time=4, resolution_time=6)
|
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)
|
get_default_service_level_agreement = get_service_level_agreement(default_service_level_agreement=1)
|
||||||
|
|
||||||
self.assertEqual(create_default_service_level_agreement.name, get_default_service_level_agreement.name)
|
self.assertEqual(create_default_service_level_agreement.name, get_default_service_level_agreement.name)
|
||||||
@ -28,8 +29,8 @@ class TestServiceLevelAgreement(unittest.TestCase):
|
|||||||
# Service Level Agreement for Customer
|
# Service Level Agreement for Customer
|
||||||
customer = create_customer()
|
customer = create_customer()
|
||||||
create_customer_service_level_agreement = create_service_level_agreement(default_service_level_agreement=0,
|
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",
|
holiday_list="__Test Holiday List", employee_group="_Test Employee Group",
|
||||||
entity_type="Customer", entity=customer, response_time=2, resolution_time=3)
|
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)
|
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)
|
self.assertEqual(create_customer_service_level_agreement.name, get_customer_service_level_agreement.name)
|
||||||
@ -40,8 +41,8 @@ class TestServiceLevelAgreement(unittest.TestCase):
|
|||||||
# Service Level Agreement for Customer Group
|
# Service Level Agreement for Customer Group
|
||||||
customer_group = create_customer_group()
|
customer_group = create_customer_group()
|
||||||
create_customer_group_service_level_agreement = create_service_level_agreement(default_service_level_agreement=0,
|
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",
|
holiday_list="__Test Holiday List", employee_group="_Test Employee Group",
|
||||||
entity_type="Customer Group", entity=customer_group, response_time=2, resolution_time=3)
|
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)
|
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)
|
self.assertEqual(create_customer_group_service_level_agreement.name, get_customer_group_service_level_agreement.name)
|
||||||
@ -52,8 +53,8 @@ class TestServiceLevelAgreement(unittest.TestCase):
|
|||||||
# Service Level Agreement for Territory
|
# Service Level Agreement for Territory
|
||||||
territory = create_territory()
|
territory = create_territory()
|
||||||
create_territory_service_level_agreement = create_service_level_agreement(default_service_level_agreement=0,
|
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",
|
holiday_list="__Test Holiday List", employee_group="_Test Employee Group",
|
||||||
entity_type="Territory", entity=territory, response_time=2, resolution_time=3)
|
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)
|
get_territory_service_level_agreement = get_service_level_agreement(entity_type="Territory", entity=territory)
|
||||||
|
|
||||||
self.assertEqual(create_territory_service_level_agreement.name, get_territory_service_level_agreement.name)
|
self.assertEqual(create_territory_service_level_agreement.name, get_territory_service_level_agreement.name)
|
||||||
@ -71,14 +72,19 @@ def get_service_level_agreement(default_service_level_agreement=None, entity_typ
|
|||||||
service_level_agreement = frappe.get_doc("Service Level Agreement", filters)
|
service_level_agreement = frappe.get_doc("Service Level Agreement", filters)
|
||||||
return service_level_agreement
|
return service_level_agreement
|
||||||
|
|
||||||
def create_service_level_agreement(default_service_level_agreement, service_level, holiday_list, employee_group,
|
def create_service_level_agreement(default_service_level_agreement, holiday_list, employee_group,
|
||||||
response_time, entity_type, entity, resolution_time):
|
response_time, entity_type, entity, resolution_time):
|
||||||
|
|
||||||
|
employee_group = make_employee_group()
|
||||||
|
make_holiday_list()
|
||||||
|
make_priorities()
|
||||||
|
|
||||||
service_level_agreement = frappe.get_doc({
|
service_level_agreement = frappe.get_doc({
|
||||||
"doctype": "Service Level Agreement",
|
"doctype": "Service Level Agreement",
|
||||||
"enable": 1,
|
"enable": 1,
|
||||||
|
"service_level": "__Test Service Level",
|
||||||
"default_service_level_agreement": default_service_level_agreement,
|
"default_service_level_agreement": default_service_level_agreement,
|
||||||
"service_level": service_level,
|
"default_priority": "Medium",
|
||||||
"holiday_list": holiday_list,
|
"holiday_list": holiday_list,
|
||||||
"employee_group": employee_group,
|
"employee_group": employee_group,
|
||||||
"entity_type": entity_type,
|
"entity_type": entity_type,
|
||||||
@ -109,6 +115,11 @@ def create_service_level_agreement(default_service_level_agreement, service_leve
|
|||||||
"resolution_time_period": "Hour",
|
"resolution_time_period": "Hour",
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"pause_sla_on": [
|
||||||
|
{
|
||||||
|
"status": "Replied"
|
||||||
|
}
|
||||||
|
],
|
||||||
"support_and_resolution": [
|
"support_and_resolution": [
|
||||||
{
|
{
|
||||||
"workday": "Monday",
|
"workday": "Monday",
|
||||||
@ -167,6 +178,7 @@ def create_service_level_agreement(default_service_level_agreement, service_leve
|
|||||||
else:
|
else:
|
||||||
return frappe.get_doc("Service Level Agreement", service_level_agreement_exists)
|
return frappe.get_doc("Service Level Agreement", service_level_agreement_exists)
|
||||||
|
|
||||||
|
|
||||||
def create_customer():
|
def create_customer():
|
||||||
customer = frappe.get_doc({
|
customer = frappe.get_doc({
|
||||||
"doctype": "Customer",
|
"doctype": "Customer",
|
||||||
@ -206,23 +218,41 @@ def create_territory():
|
|||||||
return frappe.db.exists("Territory", {"territory_name": "_Test SLA Territory"})
|
return frappe.db.exists("Territory", {"territory_name": "_Test SLA Territory"})
|
||||||
|
|
||||||
def create_service_level_agreements_for_issues():
|
def create_service_level_agreements_for_issues():
|
||||||
create_service_level_for_sla()
|
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)
|
||||||
create_service_level_agreement(default_service_level_agreement=1,
|
|
||||||
service_level="__Test Service Level", holiday_list="__Test Holiday List", employee_group="_Test Employee Group",
|
|
||||||
entity_type=None, entity=None, response_time=4, resolution_time=6)
|
|
||||||
|
|
||||||
create_customer()
|
create_customer()
|
||||||
create_service_level_agreement(default_service_level_agreement=0,
|
create_service_level_agreement(default_service_level_agreement=0, holiday_list="__Test Holiday List",
|
||||||
service_level="_Test Service Level", holiday_list="__Test Holiday List", employee_group="_Test Employee Group",
|
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=2, resolution_time=3)
|
|
||||||
|
|
||||||
create_customer_group()
|
create_customer_group()
|
||||||
create_service_level_agreement(default_service_level_agreement=0,
|
create_service_level_agreement(default_service_level_agreement=0, holiday_list="__Test Holiday List",
|
||||||
service_level="_Test Service Level", holiday_list="__Test Holiday List", employee_group="_Test Employee Group",
|
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=2, resolution_time=3)
|
|
||||||
|
|
||||||
create_territory()
|
create_territory()
|
||||||
create_service_level_agreement(default_service_level_agreement=0,
|
create_service_level_agreement(default_service_level_agreement=0, holiday_list="__Test Holiday List",
|
||||||
service_level="_Test Service Level", holiday_list="__Test Holiday List", employee_group="_Test Employee Group",
|
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=2, resolution_time=3)
|
|
||||||
|
def make_holiday_list():
|
||||||
|
holiday_list = frappe.db.exists("Holiday List", "__Test Holiday List")
|
||||||
|
if not holiday_list:
|
||||||
|
holiday_list = frappe.get_doc({
|
||||||
|
"doctype": "Holiday List",
|
||||||
|
"holiday_list_name": "__Test Holiday List",
|
||||||
|
"from_date": "2019-01-01",
|
||||||
|
"to_date": "2019-12-31",
|
||||||
|
"holidays": [
|
||||||
|
{
|
||||||
|
"description": "Test Holiday 1",
|
||||||
|
"holiday_date": "2019-03-05"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Test Holiday 2",
|
||||||
|
"holiday_date": "2019-03-07"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Test Holiday 3",
|
||||||
|
"holiday_date": "2019-02-11"
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}).insert()
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"actions": [],
|
||||||
"creation": "2019-05-04 05:54:03.658991",
|
"creation": "2019-05-04 05:54:03.658991",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"editable_grid": 1,
|
"editable_grid": 1,
|
||||||
@ -9,10 +10,8 @@
|
|||||||
"default_priority",
|
"default_priority",
|
||||||
"sb_00",
|
"sb_00",
|
||||||
"response_time",
|
"response_time",
|
||||||
"response_time_period",
|
|
||||||
"cb_00",
|
"cb_00",
|
||||||
"resolution_time",
|
"resolution_time"
|
||||||
"resolution_time_period"
|
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@ -28,16 +27,11 @@
|
|||||||
"fieldtype": "Section Break"
|
"fieldtype": "Section Break"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"columns": 1,
|
"columns": 2,
|
||||||
"fieldname": "response_time",
|
|
||||||
"fieldtype": "Int",
|
|
||||||
"in_list_view": 1,
|
|
||||||
"label": "Response Time"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"columns": 1,
|
|
||||||
"fieldname": "resolution_time",
|
"fieldname": "resolution_time",
|
||||||
"fieldtype": "Int",
|
"fieldtype": "Duration",
|
||||||
|
"hide_days": 1,
|
||||||
|
"hide_seconds": 1,
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Resolution Time"
|
"label": "Resolution Time"
|
||||||
},
|
},
|
||||||
@ -45,36 +39,31 @@
|
|||||||
"fieldname": "cb_00",
|
"fieldname": "cb_00",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"columns": 2,
|
|
||||||
"fieldname": "response_time_period",
|
|
||||||
"fieldtype": "Select",
|
|
||||||
"in_list_view": 1,
|
|
||||||
"label": "Response Time Period",
|
|
||||||
"options": "Hour\nDay\nWeek"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"columns": 2,
|
|
||||||
"fieldname": "resolution_time_period",
|
|
||||||
"fieldtype": "Select",
|
|
||||||
"in_list_view": 1,
|
|
||||||
"label": "Resolution Time Period",
|
|
||||||
"options": "Hour\nDay\nWeek"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "cb_01",
|
"fieldname": "cb_01",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"columns": 1,
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"fieldname": "default_priority",
|
"fieldname": "default_priority",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Default Priority"
|
"label": "Default Priority"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"columns": 2,
|
||||||
|
"fieldname": "response_time",
|
||||||
|
"fieldtype": "Duration",
|
||||||
|
"hide_days": 1,
|
||||||
|
"hide_seconds": 1,
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "First Response Time"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"modified": "2019-05-21 06:54:42.674377",
|
"links": [],
|
||||||
|
"modified": "2020-06-10 12:45:47.545915",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Support",
|
"module": "Support",
|
||||||
"name": "Service Level Priority",
|
"name": "Service Level Priority",
|
||||||
@ -84,4 +73,4 @@
|
|||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,6 @@
|
|||||||
|
|
||||||
frappe.ui.form.on('Support Settings', {
|
frappe.ui.form.on('Support Settings', {
|
||||||
refresh: function(frm) {
|
refresh: function(frm) {
|
||||||
|
//
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"actions": [],
|
||||||
"creation": "2017-02-17 13:07:35.686409",
|
"creation": "2017-02-17 13:07:35.686409",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"editable_grid": 1,
|
"editable_grid": 1,
|
||||||
@ -122,13 +123,15 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
|
"depends_on": "eval:doc.track_service_level_agreement;",
|
||||||
"fieldname": "allow_resetting_service_level_agreement",
|
"fieldname": "allow_resetting_service_level_agreement",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Allow Resetting Service Level Agreement"
|
"label": "Allow Resetting Service Level Agreement"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"modified": "2019-07-10 22:52:39.663873",
|
"links": [],
|
||||||
|
"modified": "2020-06-05 17:56:17.491684",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Support",
|
"module": "Support",
|
||||||
"name": "Support Settings",
|
"name": "Support Settings",
|
||||||
|
@ -193,14 +193,17 @@ class ItemConfigure {
|
|||||||
filtered_items_count === 1 ?
|
filtered_items_count === 1 ?
|
||||||
filtered_items[0] : '';
|
filtered_items[0] : '';
|
||||||
|
|
||||||
|
// Allow Add to Cart if adding out of stock items enabled in Shopping Cart else check stock.
|
||||||
|
const in_stock = product_info.allow_items_not_in_stock ? 1 : product_info.in_stock;
|
||||||
|
const add_to_cart = `<a href data-action="btn_add_to_cart" data-item-code="${one_item}">${__('Add to cart')}</a>`;
|
||||||
|
const product_action = in_stock ? add_to_cart : `<a style="color:#74808b;">${__('Not in Stock')}</a>`;
|
||||||
|
|
||||||
const item_add_to_cart = one_item ? `
|
const item_add_to_cart = one_item ? `
|
||||||
<div class="alert alert-success d-flex justify-content-between align-items-center" role="alert">
|
<div class="alert alert-success d-flex justify-content-between align-items-center" role="alert">
|
||||||
<div>
|
<div>
|
||||||
<div>${one_item} ${product_info && product_info.price ? '(' + product_info.price.formatted_price_sales_uom + ')' : ''}</div>
|
<div>${one_item} ${product_info && product_info.price ? '(' + product_info.price.formatted_price_sales_uom + ')' : ''}</div>
|
||||||
</div>
|
</div>
|
||||||
<a href data-action="btn_add_to_cart" data-item-code="${one_item}">
|
${product_action}
|
||||||
${__('Add to cart')}
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
`: '';
|
`: '';
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user