Merge branch 'develop' into rcm_develop
This commit is contained in:
commit
0511e8cc03
@ -5,7 +5,22 @@ import frappe
|
||||
import json
|
||||
from frappe.utils import nowdate, add_months, get_date_str
|
||||
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():
|
||||
company = frappe.defaults.get_defaults().company
|
||||
@ -18,10 +33,16 @@ def get_company_for_dashboards():
|
||||
return None
|
||||
|
||||
def get_data():
|
||||
|
||||
fiscal_year = _get_fiscal_year(nowdate())
|
||||
|
||||
if not fiscal_year:
|
||||
return frappe._dict()
|
||||
|
||||
return frappe._dict({
|
||||
"dashboards": get_dashboards(),
|
||||
"charts": get_charts(),
|
||||
"number_cards": get_number_cards()
|
||||
"charts": get_charts(fiscal_year),
|
||||
"number_cards": get_number_cards(fiscal_year)
|
||||
})
|
||||
|
||||
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())
|
||||
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
|
||||
|
||||
return [
|
||||
@ -61,8 +81,8 @@ def get_charts():
|
||||
"filters_json": json.dumps({
|
||||
"company": company.name,
|
||||
"filter_based_on": "Fiscal Year",
|
||||
"from_fiscal_year": fiscal_year[0],
|
||||
"to_fiscal_year": fiscal_year[0],
|
||||
"from_fiscal_year": fiscal_year.get('name'),
|
||||
"to_fiscal_year": fiscal_year.get('name'),
|
||||
"periodicity": "Monthly",
|
||||
"include_default_book_entries": 1
|
||||
}),
|
||||
@ -158,8 +178,8 @@ def get_charts():
|
||||
"report_name": "Budget Variance Report",
|
||||
"filters_json": json.dumps({
|
||||
"company": company.name,
|
||||
"from_fiscal_year": fiscal_year[0],
|
||||
"to_fiscal_year": fiscal_year[0],
|
||||
"from_fiscal_year": fiscal_year.get('name'),
|
||||
"to_fiscal_year": fiscal_year.get('name'),
|
||||
"period": "Monthly",
|
||||
"budget_against": "Cost Center"
|
||||
}),
|
||||
@ -190,10 +210,10 @@ def get_charts():
|
||||
},
|
||||
]
|
||||
|
||||
def get_number_cards():
|
||||
fiscal_year = get_fiscal_year(date=nowdate())
|
||||
year_start_date = get_date_str(fiscal_year[1])
|
||||
year_end_date = get_date_str(fiscal_year[2])
|
||||
def get_number_cards(fiscal_year):
|
||||
|
||||
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 [
|
||||
{
|
||||
"doctype": "Number Card",
|
||||
|
@ -72,7 +72,11 @@ def make_dimension_in_accounting_doctypes(doc):
|
||||
if doctype == "Budget":
|
||||
add_dimension_to_budget_doctype(df, doc)
|
||||
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
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -582,14 +582,14 @@ class SalesInvoice(SellingController):
|
||||
|
||||
def validate_item_code(self):
|
||||
for d in self.get('items'):
|
||||
if not d.item_code:
|
||||
if not d.item_code and self.is_opening == "No":
|
||||
msgprint(_("Item Code required at Row No {0}").format(d.idx), raise_exception=True)
|
||||
|
||||
def validate_warehouse(self):
|
||||
super(SalesInvoice, self).validate_warehouse()
|
||||
|
||||
for d in self.get_item_list():
|
||||
if not d.warehouse and frappe.get_cached_value("Item", d.item_code, "is_stock_item"):
|
||||
if not d.warehouse and d.item_code and frappe.get_cached_value("Item", d.item_code, "is_stock_item"):
|
||||
frappe.throw(_("Warehouse required for stock Item {0}").format(d.item_code))
|
||||
|
||||
def validate_delivery_note(self):
|
||||
|
@ -1745,53 +1745,6 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
|
||||
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):
|
||||
|
||||
if not frappe.db.exists("Customer", "_Test Internal Customer"):
|
||||
|
@ -5,14 +5,23 @@ import frappe
|
||||
import json
|
||||
from frappe.utils import nowdate, add_months, get_date_str
|
||||
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():
|
||||
|
||||
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({
|
||||
"dashboards": get_dashboards(),
|
||||
"charts": get_charts(),
|
||||
"number_cards": get_number_cards(),
|
||||
"charts": get_charts(fiscal_year, year_start_date, year_end_date),
|
||||
"number_cards": get_number_cards(fiscal_year, year_start_date, year_end_date),
|
||||
})
|
||||
|
||||
def get_dashboards():
|
||||
@ -31,12 +40,7 @@ def get_dashboards():
|
||||
]
|
||||
}]
|
||||
|
||||
fiscal_year = get_fiscal_year(date=nowdate())
|
||||
year_start_date = get_date_str(fiscal_year[1])
|
||||
year_end_date = get_date_str(fiscal_year[2])
|
||||
|
||||
|
||||
def get_charts():
|
||||
def get_charts(fiscal_year, year_start_date, year_end_date):
|
||||
company = get_company_for_dashboards()
|
||||
return [
|
||||
{
|
||||
@ -55,8 +59,8 @@ def get_charts():
|
||||
"company": company,
|
||||
"status": "In Location",
|
||||
"filter_based_on": "Fiscal Year",
|
||||
"from_fiscal_year": fiscal_year[0],
|
||||
"to_fiscal_year": fiscal_year[0],
|
||||
"from_fiscal_year": fiscal_year.get('name'),
|
||||
"to_fiscal_year": fiscal_year.get('name'),
|
||||
"period_start_date": year_start_date,
|
||||
"period_end_date": year_end_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 [
|
||||
{
|
||||
"name": "Total Assets",
|
||||
@ -173,13 +177,3 @@ def get_number_cards():
|
||||
"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")
|
||||
args = {
|
||||
'doctype' : 'Asset Maintenance',
|
||||
'assign_to' : team_member,
|
||||
'assign_to' : [team_member],
|
||||
'name' : asset_maintenance_name,
|
||||
'description' : maintenance_task,
|
||||
'date' : next_due_date
|
||||
|
@ -5,13 +5,24 @@ import frappe
|
||||
import json
|
||||
from frappe import _
|
||||
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():
|
||||
|
||||
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({
|
||||
"dashboards": get_dashboards(),
|
||||
"charts": get_charts(),
|
||||
"number_cards": get_number_cards(),
|
||||
"charts": get_charts(company, fiscal_year_name, start_date, end_date),
|
||||
"number_cards": get_number_cards(company, fiscal_year_name, start_date, end_date),
|
||||
})
|
||||
|
||||
def get_company_for_dashboards():
|
||||
@ -24,12 +35,6 @@ def get_company_for_dashboards():
|
||||
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():
|
||||
return [{
|
||||
"name": "Buying",
|
||||
@ -48,7 +53,7 @@ def get_dashboards():
|
||||
]
|
||||
}]
|
||||
|
||||
def get_charts():
|
||||
def get_charts(company, fiscal_year_name, start_date, end_date):
|
||||
return [
|
||||
{
|
||||
"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 [
|
||||
{
|
||||
"name": "Annual Purchase",
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -71,6 +71,15 @@ class PurchaseOrder(BuyingController):
|
||||
"compare_fields": [["project", "="], ["item_code", "="],
|
||||
["uom", "="], ["conversion_factor", "="]],
|
||||
"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):
|
||||
filters = {
|
||||
'company': '_Test Procurement Company',
|
||||
'cost_center': '_Test Cost Center - _TC'
|
||||
'cost_center': 'Main - _TPC'
|
||||
}
|
||||
expected_data = self.generate_expected_data()
|
||||
report = execute(filters)
|
||||
@ -33,24 +33,27 @@ class TestProcurementTracker(unittest.TestCase):
|
||||
country="Pakistan"
|
||||
)).insert()
|
||||
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.supplier = "_Test Supplier"
|
||||
po.get("items")[0].cost_center = "_Test Cost Center - _TC"
|
||||
po.get("items")[0].cost_center = "Main - _TPC"
|
||||
po.submit()
|
||||
pr = make_purchase_receipt(po.name)
|
||||
pr.get("items")[0].cost_center = "Main - _TPC"
|
||||
pr.submit()
|
||||
frappe.db.commit()
|
||||
date_obj = datetime.date(datetime.now())
|
||||
|
||||
po.load_from_db()
|
||||
|
||||
expected_data = {
|
||||
"material_request_date": date_obj,
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
"cost_center": "Main - _TPC",
|
||||
"project": None,
|
||||
"requesting_site": "_Test Procurement Warehouse - _TPC",
|
||||
"requestor": "Administrator",
|
||||
"material_request_no": mr.name,
|
||||
"description": '_Test Item 1',
|
||||
"item_code": '_Test Item',
|
||||
"quantity": 10.0,
|
||||
"unit_of_measurement": "_Test UOM",
|
||||
"status": "To Bill",
|
||||
@ -58,9 +61,9 @@ class TestProcurementTracker(unittest.TestCase):
|
||||
"purchase_order": po.name,
|
||||
"supplier": "_Test Supplier",
|
||||
"estimated_cost": 0.0,
|
||||
"actual_cost": None,
|
||||
"purchase_order_amt": 5000.0,
|
||||
"purchase_order_amt_in_company_currency": 300000.0,
|
||||
"actual_cost": 0.0,
|
||||
"purchase_order_amt": po.net_total,
|
||||
"purchase_order_amt_in_company_currency": po.base_net_total,
|
||||
"expected_delivery_date": date_obj,
|
||||
"actual_delivery_date": date_obj
|
||||
}
|
||||
|
@ -349,7 +349,7 @@ class BuyingController(StockController):
|
||||
})
|
||||
|
||||
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)
|
||||
|
||||
rm.amount = qty * flt(rm.rate)
|
||||
|
@ -19,7 +19,8 @@ class QualityInspectionNotSubmittedError(frappe.ValidationError): pass
|
||||
class StockController(AccountsController):
|
||||
def validate(self):
|
||||
super(StockController, self).validate()
|
||||
self.validate_inspection()
|
||||
if not self.get('is_return'):
|
||||
self.validate_inspection()
|
||||
self.validate_serialized_batch()
|
||||
self.validate_customer_provided_item()
|
||||
|
||||
@ -226,7 +227,9 @@ class StockController(AccountsController):
|
||||
|
||||
def check_expense_account(self, item):
|
||||
if not item.get("expense_account"):
|
||||
frappe.throw(_("Expense Account not set for Item {0}. Please set an Expense Account for the item in the Items table").format(item.item_code))
|
||||
frappe.throw(_("Row #{0}: Expense Account not set for Item {1}. Please set an Expense \
|
||||
Account in the Items table").format(item.idx, frappe.bold(item.item_code)),
|
||||
title=_("Expense Account Missing"))
|
||||
|
||||
else:
|
||||
is_expense_account = frappe.db.get_value("Account",
|
||||
|
@ -30,24 +30,32 @@
|
||||
"fieldname": "text",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Tweet",
|
||||
"mandatory_depends_on": "eval:doc.twitter ==1"
|
||||
"mandatory_depends_on": "eval:doc.twitter ==1",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "image",
|
||||
"fieldtype": "Attach Image",
|
||||
"label": "Image"
|
||||
"label": "Image",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "twitter",
|
||||
"fieldtype": "Check",
|
||||
"label": "Twitter"
|
||||
"label": "Twitter",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "linkedin",
|
||||
"fieldtype": "Check",
|
||||
"label": "LinkedIn"
|
||||
"label": "LinkedIn",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "amended_from",
|
||||
@ -56,13 +64,17 @@
|
||||
"no_copy": 1,
|
||||
"options": "Social Media Post",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
"read_only": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.twitter ==1",
|
||||
"fieldname": "content",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Twitter"
|
||||
"label": "Twitter",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
@ -70,7 +82,9 @@
|
||||
"fieldtype": "Select",
|
||||
"label": "Post Status",
|
||||
"options": "\nScheduled\nPosted\nError",
|
||||
"read_only": 1
|
||||
"read_only": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
@ -78,7 +92,9 @@
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"label": "Twitter Post Id",
|
||||
"read_only": 1
|
||||
"read_only": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
@ -86,68 +102,89 @@
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"label": "LinkedIn Post Id",
|
||||
"read_only": 1
|
||||
"read_only": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "campaign_name",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Campaign",
|
||||
"options": "Campaign"
|
||||
"options": "Campaign",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_6",
|
||||
"fieldtype": "Column Break",
|
||||
"label": "Share On"
|
||||
"label": "Share On",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_14",
|
||||
"fieldtype": "Column Break"
|
||||
"fieldtype": "Column Break",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "tweet_preview",
|
||||
"fieldtype": "HTML"
|
||||
"fieldtype": "HTML",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"depends_on": "eval:doc.linkedin==1",
|
||||
"fieldname": "linkedin_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "LinkedIn"
|
||||
"label": "LinkedIn",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "attachments_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Attachments"
|
||||
"label": "Attachments",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "linkedin_post",
|
||||
"fieldtype": "Text",
|
||||
"label": "Post",
|
||||
"mandatory_depends_on": "eval:doc.linkedin ==1"
|
||||
"mandatory_depends_on": "eval:doc.linkedin ==1",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_15",
|
||||
"fieldtype": "Column Break"
|
||||
"fieldtype": "Column Break",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "scheduled_time",
|
||||
"fieldtype": "Datetime",
|
||||
"label": "Scheduled Time",
|
||||
"read_only_depends_on": "eval:doc.post_status == \"Posted\""
|
||||
"read_only_depends_on": "eval:doc.post_status == \"Posted\"",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
}
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-04-21 15:10:04.953713",
|
||||
"modified": "2020-06-14 10:31:33.961381",
|
||||
"modified_by": "Administrator",
|
||||
"module": "CRM",
|
||||
"name": "Social Media Post",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"cancel": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
@ -157,6 +194,35 @@
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"cancel": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Sales User",
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"cancel": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Sales Manager",
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
|
@ -1,398 +1,119 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"actions": [],
|
||||
"allow_guest_to_view": 1,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 1,
|
||||
"autoname": "",
|
||||
"beta": 0,
|
||||
"creation": "2016-09-13 03:05:27.154713",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "Document",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"title",
|
||||
"route",
|
||||
"column_break_3",
|
||||
"academic_year",
|
||||
"admission_start_date",
|
||||
"admission_end_date",
|
||||
"published",
|
||||
"enable_admission_application",
|
||||
"section_break_5",
|
||||
"program_details",
|
||||
"introduction"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "title",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Title",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
"label": "Title"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "",
|
||||
"fieldname": "route",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Route",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "application_form_route",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Application Form Route",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "academic_year",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Academic Year",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"options": "Academic Year",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "admission_start_date",
|
||||
"fieldtype": "Date",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Admission Start Date",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
"no_copy": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "admission_end_date",
|
||||
"fieldtype": "Date",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Admission End Date",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
"no_copy": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"fieldname": "published",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Publish on website",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
"label": "Publish on website"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "section_break_5",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Eligibility and Details",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
"label": "Eligibility and Details"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "program_details",
|
||||
"fieldtype": "Table",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Eligibility and Details",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Student Admission Program",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
"options": "Student Admission Program"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "introduction",
|
||||
"fieldtype": "Text Editor",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Introduction",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
"label": "Introduction"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "enable_admission_application",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enable Admission Application"
|
||||
}
|
||||
],
|
||||
"has_web_view": 1,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_published_field": "published",
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2017-11-10 18:57:34.570376",
|
||||
"links": [],
|
||||
"modified": "2020-06-15 20:18:38.591626",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Education",
|
||||
"name": "Student Admission",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Academics User",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"restrict_to_domain": "Education",
|
||||
"route": "admissions",
|
||||
"show_name_in_global_search": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"title_field": "title",
|
||||
"track_changes": 0,
|
||||
"track_seen": 0
|
||||
"title_field": "title"
|
||||
}
|
@ -43,8 +43,8 @@
|
||||
<thead>
|
||||
<tr class="active">
|
||||
<th style="width: 90px">Program/Std.</th>
|
||||
<th style="width: 170px">Minumum Age(DOB)</th>
|
||||
<th style="width: 170px">Maximum Age(DOB)</th>
|
||||
<th style="width: 170px">Minumum Age</th>
|
||||
<th style="width: 170px">Maximum Age</th>
|
||||
<th style="width: 100px">Application Fee</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -52,8 +52,8 @@
|
||||
{% for row in program_details %}
|
||||
<tr>
|
||||
<td>{{ row.program }}</td>
|
||||
<td>{{ row.minimum_age }}</td>
|
||||
<td>{{ row.maximum_age }}</td>
|
||||
<td>{{ row.min_age }}</td>
|
||||
<td>{{ row.max_age }}</td>
|
||||
<td>{{ row.application_fee }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
@ -61,12 +61,11 @@
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{%- if application_form_route -%}
|
||||
{%- if doc.enable_admission_application -%}
|
||||
<br>
|
||||
<p>
|
||||
<a class='btn btn-primary'
|
||||
href='/{{ doc.application_form_route }}?new=1'>
|
||||
href='/student-applicant?new=1&student_admission={{doc.name}}'>
|
||||
{{ _("Apply Now") }}</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
|
@ -11,7 +11,7 @@ QUnit.test('Test: Student Admission', function(assert) {
|
||||
{admission_start_date: '2016-04-20'},
|
||||
{admission_end_date: '2016-05-31'},
|
||||
{title: '2016-17 Admissions'},
|
||||
{application_form_route: 'student-applicant'},
|
||||
{enable_admission_application: 1},
|
||||
{introduction: 'Test intro'},
|
||||
{program_details: [
|
||||
[
|
||||
@ -28,7 +28,7 @@ QUnit.test('Test: Student Admission', function(assert) {
|
||||
assert.ok(cur_frm.doc.admission_start_date == '2016-04-20');
|
||||
assert.ok(cur_frm.doc.admission_end_date == '2016-05-31');
|
||||
assert.ok(cur_frm.doc.title == '2016-17 Admissions');
|
||||
assert.ok(cur_frm.doc.application_form_route == 'student-applicant');
|
||||
assert.ok(cur_frm.doc.enable_admission_application == 1);
|
||||
assert.ok(cur_frm.doc.introduction == 'Test intro');
|
||||
assert.ok(cur_frm.doc.program_details[0].program == 'Standard Test', 'Program correctly selected');
|
||||
assert.ok(cur_frm.doc.program_details[0].application_fee == 1000);
|
||||
|
@ -1,237 +1,77 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_events_in_timeline": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"autoname": "",
|
||||
"beta": 0,
|
||||
"actions": [],
|
||||
"creation": "2017-09-15 12:59:43.207923",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"program",
|
||||
"min_age",
|
||||
"max_age",
|
||||
"column_break_4",
|
||||
"application_fee",
|
||||
"applicant_naming_series"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "program",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Program",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Program",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "minimum_age",
|
||||
"fieldtype": "Date",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Minimum Age",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "maximum_age",
|
||||
"fieldtype": "Date",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Maximum Age",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_4",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "application_fee",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Application Fee",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "applicant_naming_series",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Naming Series (for Student Applicant)",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "min_age",
|
||||
"fieldtype": "Int",
|
||||
"in_list_view": 1,
|
||||
"label": "Minimum Age",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "max_age",
|
||||
"fieldtype": "Int",
|
||||
"in_list_view": 1,
|
||||
"label": "Maximum Age",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-11-04 03:37:17.408427",
|
||||
"links": [],
|
||||
"modified": "2020-06-10 23:06:30.037404",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Education",
|
||||
"name": "Student Admission Program",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"restrict_to_domain": "Education",
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
"track_changes": 1
|
||||
}
|
@ -6,7 +6,7 @@ from __future__ import print_function, unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import getdate
|
||||
from frappe.utils import getdate, add_years, nowdate, date_diff
|
||||
|
||||
class StudentApplicant(Document):
|
||||
def autoname(self):
|
||||
@ -31,6 +31,7 @@ class StudentApplicant(Document):
|
||||
def validate(self):
|
||||
self.validate_dates()
|
||||
self.title = " ".join(filter(None, [self.first_name, self.middle_name, self.last_name]))
|
||||
|
||||
if self.student_admission and self.program and self.date_of_birth:
|
||||
self.validation_from_student_admission()
|
||||
|
||||
@ -48,16 +49,16 @@ class StudentApplicant(Document):
|
||||
frappe.throw(_("Please select Student Admission which is mandatory for the paid student applicant"))
|
||||
|
||||
def validation_from_student_admission(self):
|
||||
|
||||
student_admission = get_student_admission_data(self.student_admission, self.program)
|
||||
|
||||
# different validation for minimum and maximum age so that either min/max can also work independently.
|
||||
if student_admission and student_admission.minimum_age and \
|
||||
getdate(student_admission.minimum_age) < getdate(self.date_of_birth):
|
||||
frappe.throw(_("Not eligible for the admission in this program as per DOB"))
|
||||
if student_admission and student_admission.min_age and \
|
||||
date_diff(nowdate(), add_years(getdate(self.date_of_birth), student_admission.min_age)) < 0:
|
||||
frappe.throw(_("Not eligible for the admission in this program as per Date Of Birth"))
|
||||
|
||||
if student_admission and student_admission.maximum_age and \
|
||||
getdate(student_admission.maximum_age) > getdate(self.date_of_birth):
|
||||
frappe.throw(_("Not eligible for the admission in this program as per DOB"))
|
||||
if student_admission and student_admission.max_age and \
|
||||
date_diff(nowdate(), add_years(getdate(self.date_of_birth), student_admission.max_age)) > 0:
|
||||
frappe.throw(_("Not eligible for the admission in this program as per Date Of Birth"))
|
||||
|
||||
|
||||
def on_payment_authorized(self, *args, **kwargs):
|
||||
@ -65,10 +66,12 @@ class StudentApplicant(Document):
|
||||
|
||||
|
||||
def get_student_admission_data(student_admission, program):
|
||||
|
||||
student_admission = frappe.db.sql("""select sa.admission_start_date, sa.admission_end_date,
|
||||
sap.program, sap.minimum_age, sap.maximum_age, sap.applicant_naming_series
|
||||
sap.program, sap.min_age, sap.max_age, sap.applicant_naming_series
|
||||
from `tabStudent Admission` sa, `tabStudent Admission Program` sap
|
||||
where sa.name = sap.parent and sa.name = %s and sap.program = %s""", (student_admission, program), as_dict=1)
|
||||
|
||||
if student_admission:
|
||||
return student_admission[0]
|
||||
else:
|
||||
|
@ -16,7 +16,7 @@
|
||||
"is_standard": 1,
|
||||
"login_required": 1,
|
||||
"max_attachment_size": 0,
|
||||
"modified": "2017-02-21 05:44:46.022738",
|
||||
"modified": "2020-06-11 22:53:45.875310",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Education",
|
||||
"name": "student-applicant",
|
||||
@ -24,12 +24,16 @@
|
||||
"payment_button_label": "Buy Now",
|
||||
"published": 1,
|
||||
"route": "student-applicant",
|
||||
"route_to_success_link": 0,
|
||||
"show_attachments": 0,
|
||||
"show_in_grid": 0,
|
||||
"show_sidebar": 1,
|
||||
"sidebar_items": [],
|
||||
"success_url": "/student-applicant",
|
||||
"title": "Student Applicant",
|
||||
"web_form_fields": [
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "first_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
@ -37,9 +41,11 @@
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"read_only": 0,
|
||||
"reqd": 1
|
||||
"reqd": 1,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "middle_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
@ -47,9 +53,11 @@
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"read_only": 0,
|
||||
"reqd": 0
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "last_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
@ -57,9 +65,11 @@
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"read_only": 0,
|
||||
"reqd": 0
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "image",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
@ -67,9 +77,11 @@
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"read_only": 0,
|
||||
"reqd": 0
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "program",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
@ -78,9 +90,11 @@
|
||||
"max_value": 0,
|
||||
"options": "Program",
|
||||
"read_only": 0,
|
||||
"reqd": 1
|
||||
"reqd": 1,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "academic_year",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
@ -89,9 +103,11 @@
|
||||
"max_value": 0,
|
||||
"options": "Academic Year",
|
||||
"read_only": 0,
|
||||
"reqd": 0
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "date_of_birth",
|
||||
"fieldtype": "Date",
|
||||
"hidden": 0,
|
||||
@ -99,9 +115,11 @@
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"read_only": 0,
|
||||
"reqd": 0
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "blood_group",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
@ -110,9 +128,11 @@
|
||||
"max_value": 0,
|
||||
"options": "\nA+\nA-\nB+\nB-\nO+\nO-\nAB+\nAB-",
|
||||
"read_only": 0,
|
||||
"reqd": 0
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "student_email_id",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
@ -120,9 +140,11 @@
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"read_only": 0,
|
||||
"reqd": 0
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "student_mobile_number",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
@ -130,9 +152,11 @@
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"read_only": 0,
|
||||
"reqd": 0
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"default": "INDIAN",
|
||||
"fieldname": "nationality",
|
||||
"fieldtype": "Data",
|
||||
@ -142,9 +166,11 @@
|
||||
"max_value": 0,
|
||||
"options": "",
|
||||
"read_only": 0,
|
||||
"reqd": 0
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "address_line_1",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
@ -152,9 +178,11 @@
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"read_only": 0,
|
||||
"reqd": 0
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "address_line_2",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
@ -162,9 +190,11 @@
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"read_only": 0,
|
||||
"reqd": 0
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "pincode",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
@ -172,9 +202,11 @@
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"read_only": 0,
|
||||
"reqd": 0
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "guardians",
|
||||
"fieldtype": "Table",
|
||||
"hidden": 0,
|
||||
@ -183,9 +215,11 @@
|
||||
"max_value": 0,
|
||||
"options": "Student Guardian",
|
||||
"read_only": 0,
|
||||
"reqd": 0
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "siblings",
|
||||
"fieldtype": "Table",
|
||||
"hidden": 0,
|
||||
@ -194,7 +228,21 @@
|
||||
"max_value": 0,
|
||||
"options": "Student Sibling",
|
||||
"read_only": 0,
|
||||
"reqd": 0
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "student_admission",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"label": "Student Admission",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"options": "Student Admission",
|
||||
"read_only": 0,
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
}
|
||||
]
|
||||
}
|
@ -15,6 +15,7 @@ class TestInpatientRecord(unittest.TestCase):
|
||||
patient = create_patient()
|
||||
# Schedule Admission
|
||||
ip_record = create_inpatient(patient)
|
||||
ip_record.expected_length_of_stay = 0
|
||||
ip_record.save(ignore_permissions = True)
|
||||
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"))
|
||||
@ -26,7 +27,7 @@ class TestInpatientRecord(unittest.TestCase):
|
||||
self.assertEqual("Occupied", frappe.db.get_value("Healthcare Service Unit", service_unit, "occupancy_status"))
|
||||
|
||||
# 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"))
|
||||
|
||||
ip_record1 = frappe.get_doc("Inpatient Record", ip_record.name)
|
||||
@ -44,8 +45,10 @@ class TestInpatientRecord(unittest.TestCase):
|
||||
patient = create_patient()
|
||||
|
||||
ip_record = create_inpatient(patient)
|
||||
ip_record.expected_length_of_stay = 0
|
||||
ip_record.save(ignore_permissions = True)
|
||||
ip_record_new = create_inpatient(patient)
|
||||
ip_record_new.expected_length_of_stay = 0
|
||||
self.assertRaises(frappe.ValidationError, ip_record_new.save)
|
||||
|
||||
service_unit = get_healthcare_service_unit()
|
||||
|
@ -93,7 +93,7 @@
|
||||
"idx": 0,
|
||||
"is_standard": 1,
|
||||
"label": "HR",
|
||||
"modified": "2020-06-10 12:41:41.695669",
|
||||
"modified": "2020-06-16 19:20:50.976045",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "HR",
|
||||
@ -126,7 +126,7 @@
|
||||
},
|
||||
{
|
||||
"label": "Salary Structure",
|
||||
"link_to": "Payroll Entry",
|
||||
"link_to": "Salary Structure",
|
||||
"type": "DocType"
|
||||
},
|
||||
{
|
||||
|
@ -205,7 +205,7 @@
|
||||
"label": "Status",
|
||||
"oldfieldname": "status",
|
||||
"oldfieldtype": "Select",
|
||||
"options": "\nActive\nLeft",
|
||||
"options": "Active\nLeft",
|
||||
"reqd": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
@ -667,6 +667,7 @@
|
||||
"oldfieldtype": "Date"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.status == \"Left\"",
|
||||
"fieldname": "relieving_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Relieving Date",
|
||||
@ -803,7 +804,7 @@
|
||||
"idx": 24,
|
||||
"image_field": "image",
|
||||
"links": [],
|
||||
"modified": "2020-05-05 18:51:03.152503",
|
||||
"modified": "2020-06-15 12:26:30.003741",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Employee",
|
||||
|
@ -66,6 +66,7 @@
|
||||
"fieldname": "employee",
|
||||
"fieldtype": "Link",
|
||||
"in_global_search": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "From Employee",
|
||||
"oldfieldname": "employee",
|
||||
"oldfieldtype": "Link",
|
||||
@ -164,6 +165,7 @@
|
||||
"default": "Today",
|
||||
"fieldname": "posting_date",
|
||||
"fieldtype": "Date",
|
||||
"in_standard_filter": 1,
|
||||
"label": "Posting Date",
|
||||
"oldfieldname": "posting_date",
|
||||
"oldfieldtype": "Date",
|
||||
@ -236,6 +238,7 @@
|
||||
{
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"in_standard_filter": 1,
|
||||
"label": "Company",
|
||||
"oldfieldname": "company",
|
||||
"oldfieldtype": "Link",
|
||||
@ -368,7 +371,7 @@
|
||||
"idx": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2019-12-14 23:52:05.388458",
|
||||
"modified": "2020-06-15 12:43:04.099803",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Expense Claim",
|
||||
|
@ -4,7 +4,6 @@
|
||||
import frappe, erpnext, json
|
||||
from frappe import _
|
||||
from frappe.utils import nowdate, get_first_day, get_last_day, add_months
|
||||
from erpnext.accounts.utils import get_fiscal_year
|
||||
|
||||
def get_data():
|
||||
return frappe._dict({
|
||||
|
@ -119,6 +119,7 @@ class BOM(WebsiteGenerator):
|
||||
"description": d.description,
|
||||
"time_in_mins": d.time_in_mins,
|
||||
"batch_size": d.batch_size,
|
||||
"operating_cost": d.operating_cost,
|
||||
"idx": d.idx
|
||||
})
|
||||
child.hour_rate = flt(d.hour_rate / self.conversion_rate, 2)
|
||||
|
@ -78,6 +78,7 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:parent.doctype == 'BOM'",
|
||||
"fieldname": "base_hour_rate",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Base Hour Rate(Company Currency)",
|
||||
@ -87,6 +88,7 @@
|
||||
},
|
||||
{
|
||||
"default": "5",
|
||||
"depends_on": "eval:parent.doctype == 'BOM'",
|
||||
"fieldname": "base_operating_cost",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Operating Cost(Company Currency)",
|
||||
@ -108,8 +110,8 @@
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"modified": "2019-07-16 22:35:55.374037",
|
||||
"modified_by": "govindsmenokee@gmail.com",
|
||||
"modified": "2020-06-16 17:01:11.128420",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "BOM Operation",
|
||||
"owner": "Administrator",
|
||||
|
@ -44,7 +44,6 @@ frappe.ui.form.on('BOM Operation', {
|
||||
name: d.workstation
|
||||
},
|
||||
callback: function (data) {
|
||||
frappe.model.set_value(d.doctype, d.name, "base_hour_rate", data.message.hour_rate);
|
||||
frappe.model.set_value(d.doctype, d.name, "hour_rate", data.message.hour_rate);
|
||||
frm.events.calculate_operating_cost(frm, d);
|
||||
}
|
||||
|
@ -697,3 +697,5 @@ execute:frappe.rename_doc("Desk Page", "Loan Management", "Loan", force=True)
|
||||
erpnext.patches.v12_0.update_uom_conversion_factor
|
||||
erpnext.patches.v13_0.delete_old_purchase_reports
|
||||
erpnext.patches.v12_0.set_italian_import_supplier_invoice_permissions
|
||||
erpnext.patches.v13_0.update_sla_enhancements
|
||||
erpnext.patches.v12_0.update_address_template_for_india
|
||||
|
@ -6,4 +6,6 @@ import frappe
|
||||
|
||||
def execute():
|
||||
''' 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 ''')
|
||||
|
12
erpnext/patches/v12_0/update_address_template_for_india.py
Normal file
12
erpnext/patches/v12_0/update_address_template_for_india.py
Normal file
@ -0,0 +1,12 @@
|
||||
# Copyright (c) 2020, Frappe and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from erpnext.regional.address_template.setup import set_up_address_templates
|
||||
|
||||
def execute():
|
||||
if frappe.db.get_value('Company', {'country': 'India'}, 'name'):
|
||||
address_template = frappe.db.get_value('Address Template', 'India', 'template')
|
||||
if not address_template or "gstin" not in address_template:
|
||||
set_up_address_templates(default_country='India')
|
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
|
||||
from frappe.utils import cint
|
||||
from erpnext.portal.product_configurator.item_variants_cache import ItemVariantsCacheManager
|
||||
|
||||
def get_field_filter_data():
|
||||
@ -243,6 +244,8 @@ def get_next_attribute_and_values(item_code, selected_attributes):
|
||||
else:
|
||||
product_info = None
|
||||
|
||||
product_info["allow_items_not_in_stock"] = cint(data.cart_settings.allow_items_not_in_stock)
|
||||
|
||||
return {
|
||||
'next_attribute': next_attribute,
|
||||
'valid_options_for_attributes': valid_options_for_attributes,
|
||||
|
@ -64,7 +64,7 @@ class TestTask(unittest.TestCase):
|
||||
def assign():
|
||||
from frappe.desk.form import assign_to
|
||||
assign_to.add({
|
||||
"assign_to": "test@example.com",
|
||||
"assign_to": ["test@example.com"],
|
||||
"doctype": task.doctype,
|
||||
"name": task.name,
|
||||
"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.hr.doctype.salary_structure.test_salary_structure \
|
||||
import make_salary_structure, create_salary_structure_assignment
|
||||
|
||||
from erpnext.hr.doctype.employee.test_employee import make_employee
|
||||
|
||||
class TestTimesheet(unittest.TestCase):
|
||||
def setUp(self):
|
||||
@ -25,8 +25,10 @@ class TestTimesheet(unittest.TestCase):
|
||||
|
||||
|
||||
def test_timesheet_billing_amount(self):
|
||||
make_salary_structure_for_timesheet("_T-Employee-00001")
|
||||
timesheet = make_timesheet("_T-Employee-00001", simulate=True, billable=1)
|
||||
emp = make_employee("test_employee_6@salary.com")
|
||||
|
||||
make_salary_structure_for_timesheet(emp)
|
||||
timesheet = make_timesheet(emp, simulate=True, billable=1)
|
||||
|
||||
self.assertEqual(timesheet.total_hours, 2)
|
||||
self.assertEqual(timesheet.total_billable_hours, 2)
|
||||
@ -35,8 +37,10 @@ class TestTimesheet(unittest.TestCase):
|
||||
self.assertEqual(timesheet.total_billable_amount, 100)
|
||||
|
||||
def test_timesheet_billing_amount_not_billable(self):
|
||||
make_salary_structure_for_timesheet("_T-Employee-00001")
|
||||
timesheet = make_timesheet("_T-Employee-00001", simulate=True, billable=0)
|
||||
emp = make_employee("test_employee_6@salary.com")
|
||||
|
||||
make_salary_structure_for_timesheet(emp)
|
||||
timesheet = make_timesheet(emp, simulate=True, billable=0)
|
||||
|
||||
self.assertEqual(timesheet.total_hours, 2)
|
||||
self.assertEqual(timesheet.total_billable_hours, 0)
|
||||
@ -45,8 +49,10 @@ class TestTimesheet(unittest.TestCase):
|
||||
self.assertEqual(timesheet.total_billable_amount, 0)
|
||||
|
||||
def test_salary_slip_from_timesheet(self):
|
||||
salary_structure = make_salary_structure_for_timesheet("_T-Employee-00001")
|
||||
timesheet = make_timesheet("_T-Employee-00001", simulate = True, billable=1)
|
||||
emp = make_employee("test_employee_6@salary.com")
|
||||
|
||||
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.submit()
|
||||
|
||||
@ -65,7 +71,9 @@ class TestTimesheet(unittest.TestCase):
|
||||
self.assertEqual(timesheet.status, 'Submitted')
|
||||
|
||||
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.due_date = nowdate()
|
||||
sales_invoice.submit()
|
||||
@ -80,7 +88,9 @@ class TestTimesheet(unittest.TestCase):
|
||||
self.assertEqual(item.rate, 50.00)
|
||||
|
||||
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.project = '_Test Project'
|
||||
sales_invoice.submit()
|
||||
@ -90,6 +100,8 @@ class TestTimesheet(unittest.TestCase):
|
||||
self.assertEqual(ts.time_logs[0].sales_invoice, sales_invoice.name)
|
||||
|
||||
def test_timesheet_time_overlap(self):
|
||||
emp = make_employee("test_employee_6@salary.com")
|
||||
|
||||
settings = frappe.get_single('Projects Settings')
|
||||
initial_setting = settings.ignore_employee_time_overlap
|
||||
settings.ignore_employee_time_overlap = 0
|
||||
@ -97,7 +109,7 @@ class TestTimesheet(unittest.TestCase):
|
||||
|
||||
update_activity_type("_Test Activity Type")
|
||||
timesheet = frappe.new_doc("Timesheet")
|
||||
timesheet.employee = "_T-Employee-00001"
|
||||
timesheet.employee = emp
|
||||
timesheet.append(
|
||||
'time_logs',
|
||||
{
|
||||
@ -129,12 +141,14 @@ class TestTimesheet(unittest.TestCase):
|
||||
settings.save()
|
||||
|
||||
def test_timesheet_std_working_hours(self):
|
||||
emp = make_employee("test_employee_6@salary.com")
|
||||
|
||||
company = frappe.get_doc('Company', "_Test Company")
|
||||
company.standard_working_hours = 8
|
||||
company.save()
|
||||
|
||||
timesheet = frappe.new_doc("Timesheet")
|
||||
timesheet.employee = "_T-Employee-00001"
|
||||
timesheet.employee = emp
|
||||
timesheet.company = '_Test Company'
|
||||
timesheet.append(
|
||||
'time_logs',
|
||||
@ -156,7 +170,7 @@ class TestTimesheet(unittest.TestCase):
|
||||
company.save()
|
||||
|
||||
timesheet = frappe.new_doc("Timesheet")
|
||||
timesheet.employee = "_T-Employee-00001"
|
||||
timesheet.employee = emp
|
||||
timesheet.company = '_Test Company'
|
||||
timesheet.append(
|
||||
'time_logs',
|
||||
|
@ -73,6 +73,8 @@ erpnext.buying.BuyingController = erpnext.TransactionController.extend({
|
||||
me.frm.set_query('contact_person', erpnext.queries.contact_query);
|
||||
me.frm.set_query('supplier_address', erpnext.queries.address_query);
|
||||
|
||||
me.frm.set_query('billing_address', erpnext.queries.company_address_query);
|
||||
|
||||
if(this.frm.fields_dict.supplier) {
|
||||
this.frm.set_query("supplier", function() {
|
||||
return{ query: "erpnext.controllers.queries.supplier_query" }});
|
||||
@ -283,6 +285,11 @@ erpnext.buying.BuyingController = erpnext.TransactionController.extend({
|
||||
"shipping_address_display", true);
|
||||
},
|
||||
|
||||
billing_address: function() {
|
||||
erpnext.utils.get_address_display(this.frm, "billing_address",
|
||||
"billing_address_display", true);
|
||||
},
|
||||
|
||||
tc_name: function() {
|
||||
this.get_terms();
|
||||
},
|
||||
|
@ -1,7 +1,7 @@
|
||||
{{ address_line1 }}<br>{% if address_line2 %}{{ address_line2 }}<br>{% endif -%}{{ city }}<br>
|
||||
{% if gst_state %}{{ gst_state }}{% endif -%}
|
||||
{% if gst_state_number %}, State Code: {{ gst_state_number }}<br>{% endif -%}
|
||||
{% if pincode %}PIN: {{ pincode }}<br>{% endif -%}
|
||||
{% if pincode %}Postal Code: {{ pincode }}<br>{% endif -%}
|
||||
{{ country }}<br>
|
||||
{% if phone %}Phone: {{ phone }}<br>{% endif -%}
|
||||
{% if fax %}Fax: {{ fax }}<br>{% endif -%}
|
||||
|
@ -9,14 +9,14 @@ def setup(company=None, patch=True):
|
||||
make_custom_fields()
|
||||
add_print_formats()
|
||||
|
||||
def make_custom_fields():
|
||||
def make_custom_fields(update=True):
|
||||
custom_fields = {
|
||||
'Supplier': [
|
||||
dict(fieldname='irs_1099', fieldtype='Check', insert_after='tax_id',
|
||||
label='Is IRS 1099 reporting required for supplier?')
|
||||
]
|
||||
}
|
||||
create_custom_fields(custom_fields)
|
||||
create_custom_fields(custom_fields, update=update)
|
||||
|
||||
def add_print_formats():
|
||||
frappe.reload_doc("regional", "print_format", "irs_1099_form")
|
||||
|
@ -1,4 +1,5 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_import": 1,
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:title",
|
||||
@ -49,7 +50,7 @@
|
||||
"fieldname": "terms_and_conditions_help",
|
||||
"fieldtype": "HTML",
|
||||
"label": "Terms and Conditions Help",
|
||||
"options": "<h4>Standard Terms and Conditions Example</h4>\n\n<pre>Delivery Terms for Order number {{ name }}\n\n-Order Date : {{ transaction_date }} \n-Expected Delivery Date : {{ delivery_date }}\n</pre>\n\n<h4>How to get fieldnames</h4>\n\n<p>The fieldnames you can use in your email template are the fields in the document from which you are sending the email. You can find out the fields of any documents via Setup > Customize Form View and selecting the document type (e.g. Sales Invoice)</p>\n\n<h4>Templating</h4>\n\n<p>Templates are compiled using the Jinja Templating Langauge. To learn more about Jinja, <a class=\"strong\" href=\"http://jinja.pocoo.org/docs/dev/templates/\">read this documentation.</a></p>"
|
||||
"options": "<h4>Standard Terms and Conditions Example</h4>\n\n<pre>Delivery Terms for Order number {{ name }}\n\n-Order Date : {{ transaction_date }} \n-Expected Delivery Date : {{ delivery_date }}\n</pre>\n\n<h4>How to get fieldnames</h4>\n\n<p>The fieldnames you can use in your email template are the fields in the document from which you are sending the email. You can find out the fields of any documents via Setup > Customize Form View and selecting the document type (e.g. Sales Invoice)</p>\n\n<h4>Templating</h4>\n\n<p>Templates are compiled using the Jinja Templating Language. To learn more about Jinja, <a class=\"strong\" href=\"http://jinja.pocoo.org/docs/dev/templates/\">read this documentation.</a></p>"
|
||||
},
|
||||
{
|
||||
"fieldname": "applicable_modules_section",
|
||||
@ -81,7 +82,8 @@
|
||||
],
|
||||
"icon": "icon-legal",
|
||||
"idx": 1,
|
||||
"modified": "2019-07-04 13:31:30.393425",
|
||||
"links": [],
|
||||
"modified": "2020-06-16 22:54:38.094844",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Setup",
|
||||
"name": "Terms and Conditions",
|
||||
|
@ -105,3 +105,4 @@ def add_company_to_session_defaults():
|
||||
"ref_doctype": "Company"
|
||||
})
|
||||
settings.save()
|
||||
|
||||
|
@ -42,9 +42,9 @@ def get_cart_quotation(doc=None):
|
||||
|
||||
return {
|
||||
"doc": decorate_quotation_doc(doc),
|
||||
"shipping_addresses": [{"name": address.name, "display": address.display}
|
||||
"shipping_addresses": [{"name": address.name, "title": address.address_title, "display": address.display}
|
||||
for address in addresses if address.address_type == "Shipping"],
|
||||
"billing_addresses": [{"name": address.name, "display": address.display}
|
||||
"billing_addresses": [{"name": address.name, "title": address.address_title, "display": address.display}
|
||||
for address in addresses if address.address_type == "Billing"],
|
||||
"shipping_rules": get_applicable_shipping_rules(party),
|
||||
"cart_settings": frappe.get_cached_doc("Shopping Cart Settings")
|
||||
@ -78,8 +78,10 @@ def place_order():
|
||||
|
||||
if is_stock_item:
|
||||
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]:
|
||||
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.insert()
|
||||
|
@ -5,31 +5,26 @@ import frappe
|
||||
import json
|
||||
from frappe import _
|
||||
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():
|
||||
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({
|
||||
"dashboards": get_dashboards(),
|
||||
"charts": get_charts(),
|
||||
"number_cards": get_number_cards(),
|
||||
"charts": get_charts(company, fiscal_year_name, start_date, end_date),
|
||||
"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():
|
||||
return [{
|
||||
"name": "Stock",
|
||||
@ -48,7 +43,7 @@ def get_dashboards():
|
||||
]
|
||||
}]
|
||||
|
||||
def get_charts():
|
||||
def get_charts(company, fiscal_year_name, start_date, end_date):
|
||||
return [
|
||||
{
|
||||
"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 [
|
||||
{
|
||||
"name": "Total Active Items",
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -13,7 +13,7 @@
|
||||
{
|
||||
"hidden": 0,
|
||||
"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,
|
||||
@ -43,7 +43,7 @@
|
||||
"idx": 0,
|
||||
"is_standard": 1,
|
||||
"label": "Support",
|
||||
"modified": "2020-05-28 13:51:23.869954",
|
||||
"modified": "2020-06-04 11:54:56.124219",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Support",
|
||||
"name": "Support",
|
||||
@ -65,8 +65,8 @@
|
||||
"type": "DocType"
|
||||
},
|
||||
{
|
||||
"label": "Service Level",
|
||||
"link_to": "Service Level",
|
||||
"label": "Service Level Agreement",
|
||||
"link_to": "Service Level Agreement",
|
||||
"type": "DocType"
|
||||
}
|
||||
]
|
||||
|
@ -38,10 +38,35 @@ frappe.ui.form.on("Issue", {
|
||||
},
|
||||
|
||||
refresh: function (frm) {
|
||||
|
||||
if (frm.doc.status !== "Closed" && frm.doc.agreement_fulfilled === "Ongoing") {
|
||||
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 () {
|
||||
@ -55,6 +80,7 @@ frappe.ui.form.on("Issue", {
|
||||
frm: frm
|
||||
});
|
||||
}, __("Make"));
|
||||
|
||||
} else {
|
||||
if (frm.doc.service_level_agreement) {
|
||||
frm.dashboard.clear_headline();
|
||||
|
@ -31,9 +31,13 @@
|
||||
"resolution_by",
|
||||
"resolution_by_variance",
|
||||
"service_level_agreement_creation",
|
||||
"on_hold_since",
|
||||
"total_hold_time",
|
||||
"response",
|
||||
"mins_to_first_response",
|
||||
"first_responded_on",
|
||||
"column_break_26",
|
||||
"avg_response_time",
|
||||
"additional_info",
|
||||
"lead",
|
||||
"contact",
|
||||
@ -50,7 +54,9 @@
|
||||
"resolution_date",
|
||||
"content_type",
|
||||
"attachment",
|
||||
"via_customer_portal"
|
||||
"via_customer_portal",
|
||||
"resolution_time",
|
||||
"user_resolution_time"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@ -114,7 +120,7 @@
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "status",
|
||||
"oldfieldtype": "Select",
|
||||
"options": "Open\nReplied\nHold\nClosed",
|
||||
"options": "Open\nReplied\nHold\nResolved\nClosed",
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
@ -161,6 +167,7 @@
|
||||
"options": "Service Level Agreement"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.status != 'Replied';",
|
||||
"fieldname": "response_by",
|
||||
"fieldtype": "Datetime",
|
||||
"label": "Response By",
|
||||
@ -174,6 +181,7 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.status != 'Replied';",
|
||||
"fieldname": "resolution_by",
|
||||
"fieldtype": "Datetime",
|
||||
"label": "Resolution By",
|
||||
@ -328,7 +336,7 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.service_level_agreement",
|
||||
"depends_on": "eval: doc.service_level_agreement && doc.status != 'Replied';",
|
||||
"description": "in hours",
|
||||
"fieldname": "response_by_variance",
|
||||
"fieldtype": "Float",
|
||||
@ -336,7 +344,7 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.service_level_agreement",
|
||||
"depends_on": "eval: doc.service_level_agreement && doc.status != 'Replied';",
|
||||
"description": "in hours",
|
||||
"fieldname": "resolution_by_variance",
|
||||
"fieldtype": "Float",
|
||||
@ -362,12 +370,48 @@
|
||||
"label": "Issue Split From",
|
||||
"options": "Issue",
|
||||
"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",
|
||||
"idx": 7,
|
||||
"links": [],
|
||||
"modified": "2020-03-13 02:19:49.477928",
|
||||
"modified": "2020-06-10 12:47:37.146914",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Support",
|
||||
"name": "Issue",
|
||||
|
@ -7,7 +7,7 @@ import json
|
||||
from frappe import _
|
||||
from frappe import utils
|
||||
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 frappe.model.mapper import get_mapped_doc
|
||||
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})
|
||||
|
||||
if self.contact:
|
||||
contact = frappe.get_doc('Contact', self.contact)
|
||||
self.customer = contact.get_link_for('Customer')
|
||||
contact = frappe.get_doc("Contact", self.contact)
|
||||
self.customer = contact.get_link_for("Customer")
|
||||
|
||||
if not self.company:
|
||||
self.company = frappe.db.get_value("Lead", self.lead, "company") or \
|
||||
@ -56,18 +56,70 @@ class Issue(Document):
|
||||
|
||||
def update_status(self):
|
||||
status = frappe.db.get_value("Issue", self.name, "status")
|
||||
if self.status!="Open" and status =="Open" and not self.first_responded_on:
|
||||
if self.status != "Open" and status == "Open" and not self.first_responded_on:
|
||||
self.first_responded_on = frappe.flags.current_time or now_datetime()
|
||||
|
||||
if self.status=="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()
|
||||
if frappe.db.get_value("Issue", self.name, "agreement_fulfilled") == "Ongoing":
|
||||
set_service_level_agreement_variance(issue=self.name)
|
||||
self.update_agreement_status()
|
||||
set_resolution_time(issue=self)
|
||||
set_user_resolution_time(issue=self)
|
||||
|
||||
if self.status=="Open" and status !="Open":
|
||||
if self.status == "Open" and status != "Open":
|
||||
# if no date, it should be set as None and not a blank string "", as per mysql strict config
|
||||
self.resolution_date = None
|
||||
self.reset_issue_metrics()
|
||||
# enable SLA and variance on Reopen
|
||||
self.agreement_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):
|
||||
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.resolution_by = None
|
||||
replicated_issue.resolution_by_variance = None
|
||||
replicated_issue.reset_issue_metrics()
|
||||
|
||||
frappe.get_doc(replicated_issue).insert()
|
||||
|
||||
@ -137,7 +190,7 @@ class Issue(Document):
|
||||
communications = frappe.get_all("Communication",
|
||||
filters={"reference_doctype": "Issue",
|
||||
"reference_name": comm_to_split_from.reference_name,
|
||||
"creation": ('>=', comm_to_split_from.creation)})
|
||||
"creation": (">=", comm_to_split_from.creation)})
|
||||
|
||||
for communication in communications:
|
||||
doc = frappe.get_doc("Communication", communication.name)
|
||||
@ -173,20 +226,15 @@ class Issue(Document):
|
||||
self.service_level_agreement = service_level_agreement.name
|
||||
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 = 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
|
||||
})
|
||||
priority = get_priority(self)
|
||||
|
||||
if not self.creation:
|
||||
self.creation = now_datetime()
|
||||
self.service_level_agreement_creation = now_datetime()
|
||||
|
||||
start_date_time = get_datetime(self.service_level_agreement_creation)
|
||||
self.response_by = get_expected_time_for(parameter='response', service_level=priority, start_date_time=start_date_time)
|
||||
self.resolution_by = get_expected_time_for(parameter='resolution', service_level=priority, start_date_time=start_date_time)
|
||||
self.response_by = get_expected_time_for(parameter="response", service_level=priority, start_date_time=start_date_time)
|
||||
self.resolution_by = get_expected_time_for(parameter="resolution", service_level=priority, start_date_time=start_date_time)
|
||||
|
||||
self.response_by_variance = round(time_diff_in_hours(self.response_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.save()
|
||||
|
||||
def reset_issue_metrics(self):
|
||||
self.db_set("resolution_time", None)
|
||||
self.db_set("user_resolution_time", None)
|
||||
|
||||
|
||||
def get_priority(issue):
|
||||
service_level_agreement = frappe.get_doc("Service Level Agreement", issue.service_level_agreement)
|
||||
priority = service_level_agreement.get_service_level_agreement_priority(issue.priority)
|
||||
priority.update({
|
||||
"support_and_resolution": service_level_agreement.support_and_resolution,
|
||||
"holiday_list": service_level_agreement.holiday_list
|
||||
})
|
||||
return priority
|
||||
|
||||
|
||||
def get_expected_time_for(parameter, service_level, start_date_time):
|
||||
current_date_time = start_date_time
|
||||
expected_time = current_date_time
|
||||
start_time = None
|
||||
end_time = None
|
||||
|
||||
# lets assume response time is in days by default
|
||||
if parameter == 'response':
|
||||
allotted_days = service_level.get("response_time")
|
||||
time_period = service_level.get("response_time_period")
|
||||
elif parameter == 'resolution':
|
||||
allotted_days = service_level.get("resolution_time")
|
||||
time_period = service_level.get("resolution_time_period")
|
||||
if parameter == "response":
|
||||
allotted_seconds = service_level.get("response_time")
|
||||
elif parameter == "resolution":
|
||||
allotted_seconds = service_level.get("resolution_time")
|
||||
else:
|
||||
frappe.throw(_("{0} parameter is invalid").format(parameter))
|
||||
|
||||
allotted_hours = 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
|
||||
expected_time_is_set = 0
|
||||
|
||||
support_days = {}
|
||||
for service in service_level.get("support_and_resolution"):
|
||||
support_days[service.workday] = frappe._dict({
|
||||
'start_time': service.start_time,
|
||||
'end_time': service.end_time,
|
||||
"start_time": service.start_time,
|
||||
"end_time": service.end_time,
|
||||
})
|
||||
|
||||
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 \
|
||||
else support_days[current_weekday].start_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
|
||||
if time_left_today < 0: pass
|
||||
elif time_period == 'Hour':
|
||||
if time_left_today >= allotted_hours:
|
||||
if time_left_today <= 0: pass
|
||||
elif allotted_seconds:
|
||||
if time_left_today >= allotted_seconds:
|
||||
expected_time = datetime.combine(getdate(current_date_time), get_time(start_time))
|
||||
expected_time = add_to_date(expected_time, hours=allotted_hours)
|
||||
expected_time = add_to_date(expected_time, seconds=allotted_seconds)
|
||||
expected_time_is_set = 1
|
||||
else:
|
||||
allotted_hours = allotted_hours - time_left_today
|
||||
else:
|
||||
allotted_days -= 1
|
||||
expected_time_is_set = allotted_days <= 0
|
||||
allotted_seconds = allotted_seconds - time_left_today
|
||||
|
||||
if not expected_time_is_set:
|
||||
current_date_time = add_to_date(current_date_time, days=1)
|
||||
|
||||
if end_time and time_period != 'Hour':
|
||||
if end_time and allotted_seconds >= 86400:
|
||||
current_date_time = datetime.combine(getdate(current_date_time), get_time(end_time))
|
||||
else:
|
||||
current_date_time = expected_time
|
||||
@ -311,6 +361,36 @@ def set_service_level_agreement_variance(issue=None):
|
||||
if variance < 0:
|
||||
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):
|
||||
return {
|
||||
"title": _("Issues"),
|
||||
@ -318,7 +398,7 @@ def get_list_context(context=None):
|
||||
"row_template": "templates/includes/issue_row.html",
|
||||
"show_sidebar": 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
|
||||
|
||||
user = frappe.session.user
|
||||
contact = frappe.db.get_value('Contact', {'user': user}, 'name')
|
||||
contact = frappe.db.get_value("Contact", {"user": user}, "name")
|
||||
customer = None
|
||||
|
||||
if contact:
|
||||
contact_doc = frappe.get_doc('Contact', contact)
|
||||
customer = contact_doc.get_link_for('Customer')
|
||||
contact_doc = frappe.get_doc("Contact", contact)
|
||||
customer = contact_doc.get_link_for("Customer")
|
||||
|
||||
ignore_permissions = False
|
||||
if is_website_user():
|
||||
|
@ -5,15 +5,18 @@ from __future__ import unicode_literals
|
||||
import frappe
|
||||
import unittest
|
||||
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
|
||||
from datetime import timedelta
|
||||
|
||||
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)
|
||||
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)
|
||||
|
||||
# make issue with customer specific SLA
|
||||
@ -72,8 +75,67 @@ class TestIssue(unittest.TestCase):
|
||||
|
||||
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({
|
||||
"doctype": "Issue",
|
||||
"subject": "Service Level Agreement Issue {0}".format(index),
|
||||
@ -86,6 +148,7 @@ def make_issue(creation=None, customer=None, index=0):
|
||||
|
||||
return issue
|
||||
|
||||
|
||||
def create_customer(name, customer_group, territory):
|
||||
|
||||
create_customer_group(customer_group)
|
||||
@ -99,6 +162,7 @@ def create_customer(name, customer_group, territory):
|
||||
"territory": territory
|
||||
}).insert(ignore_permissions=True)
|
||||
|
||||
|
||||
def create_customer_group(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
|
||||
}).insert(ignore_permissions=True)
|
||||
|
||||
|
||||
def create_territory(territory):
|
||||
|
||||
if not frappe.db.exists("Territory", {"territory_name": territory}):
|
||||
@ -114,3 +179,21 @@ def create_territory(territory):
|
||||
"doctype": "Territory",
|
||||
"territory_name": territory,
|
||||
}).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
|
||||
|
||||
frappe.ui.form.on('Service Level Agreement', {
|
||||
service_level: function(frm) {
|
||||
frm.fields_dict.support_and_resolution.grid.remove_all();
|
||||
frappe.call({
|
||||
"method": "frappe.client.get",
|
||||
args: {
|
||||
doctype: "Service Level",
|
||||
name: frm.doc.service_level
|
||||
},
|
||||
callback: function(data){
|
||||
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();
|
||||
}
|
||||
setup: function(frm) {
|
||||
let allow_statuses = [];
|
||||
const exclude_statuses = ['Open', 'Closed', 'Resolved'];
|
||||
|
||||
frappe.model.with_doctype('Issue', () => {
|
||||
let statuses = frappe.meta.get_docfield('Issue', 'status', frm.doc.name).options;
|
||||
statuses = statuses.split('\n');
|
||||
allow_statuses = statuses.filter((status) => !exclude_statuses.includes(status));
|
||||
frappe.meta.get_docfield('Pause SLA On Status', 'status', frm.doc.name).options = [''].concat(allow_statuses);
|
||||
});
|
||||
},
|
||||
}
|
||||
});
|
@ -1,4 +1,5 @@
|
||||
{
|
||||
"actions": [],
|
||||
"autoname": "format:SLA-{service_level}-{####}",
|
||||
"creation": "2018-12-26 21:08:15.448812",
|
||||
"doctype": "DocType",
|
||||
@ -6,12 +7,13 @@
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"enable",
|
||||
"section_break_2",
|
||||
"service_level",
|
||||
"default_priority",
|
||||
"default_service_level_agreement",
|
||||
"holiday_list",
|
||||
"column_break_2",
|
||||
"employee_group",
|
||||
"default_priority",
|
||||
"holiday_list",
|
||||
"entity_section",
|
||||
"entity_type",
|
||||
"column_break_10",
|
||||
@ -21,49 +23,40 @@
|
||||
"active",
|
||||
"column_break_7",
|
||||
"end_date",
|
||||
"section_break_18",
|
||||
"pause_sla_on",
|
||||
"response_and_resolution_time_section",
|
||||
"priorities",
|
||||
"support_and_resolution_section_break",
|
||||
"support_and_resolution"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval: !doc.customer;",
|
||||
"fieldname": "default_service_level_agreement",
|
||||
"fieldtype": "Check",
|
||||
"label": "Default Service Level Agreement"
|
||||
},
|
||||
{
|
||||
"fieldname": "service_level",
|
||||
"fieldtype": "Link",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Service Level",
|
||||
"options": "Service Level",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "service_level.holiday_list",
|
||||
"fieldname": "holiday_list",
|
||||
"fieldtype": "Link",
|
||||
"label": "Holiday List",
|
||||
"options": "Holiday List",
|
||||
"read_only": 1
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_2",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fetch_from": "service_level.employee_group",
|
||||
"fieldname": "employee_group",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Employee Group",
|
||||
"options": "Employee Group",
|
||||
"read_only": 1
|
||||
"options": "Employee Group"
|
||||
},
|
||||
{
|
||||
"fieldname": "agreement_details_section",
|
||||
@ -103,21 +96,15 @@
|
||||
"fieldname": "support_and_resolution",
|
||||
"fieldtype": "Table",
|
||||
"label": "Support and Resolution",
|
||||
"options": "Service Day"
|
||||
"options": "Service Day",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "priorities",
|
||||
"fieldtype": "Table",
|
||||
"label": "Priorities",
|
||||
"options": "Service Level Priority"
|
||||
},
|
||||
{
|
||||
"fetch_from": "service_level.default_priority",
|
||||
"fieldname": "default_priority",
|
||||
"fieldtype": "Link",
|
||||
"label": "Default Priority",
|
||||
"options": "Issue Priority",
|
||||
"read_only": 1
|
||||
"options": "Service Level Priority",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
@ -156,9 +143,38 @@
|
||||
"fieldname": "enable",
|
||||
"fieldtype": "Check",
|
||||
"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",
|
||||
"module": "Support",
|
||||
"name": "Service Level Agreement",
|
||||
|
@ -6,11 +6,73 @@ from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
from frappe import _
|
||||
from frappe.utils import getdate
|
||||
from frappe.utils import getdate, get_weekdays
|
||||
|
||||
class ServiceLevelAgreement(Document):
|
||||
|
||||
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"):
|
||||
frappe.throw(_("Service Level Agreement tracking is not enabled."))
|
||||
|
||||
@ -35,9 +97,7 @@ class ServiceLevelAgreement(Document):
|
||||
return frappe._dict({
|
||||
"priority": priority.priority,
|
||||
"response_time": priority.response_time,
|
||||
"response_time_period": priority.response_time_period,
|
||||
"resolution_time": priority.resolution_time,
|
||||
"resolution_time_period": priority.resolution_time_period
|
||||
"resolution_time": priority.resolution_time
|
||||
})
|
||||
|
||||
def check_agreement_status():
|
||||
@ -111,3 +171,14 @@ def get_service_level_agreement_filters(name, customer=None):
|
||||
"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)]
|
||||
}
|
||||
|
||||
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 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):
|
||||
|
||||
def test_service_level_agreement(self):
|
||||
def setUp(self):
|
||||
frappe.db.sql("delete from `tabService Level Agreement`")
|
||||
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
|
||||
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",
|
||||
entity_type=None, entity=None, response_time=4, resolution_time=6)
|
||||
holiday_list="__Test Holiday List", employee_group="_Test Employee Group",
|
||||
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)
|
||||
|
||||
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
|
||||
customer = create_customer()
|
||||
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",
|
||||
entity_type="Customer", entity=customer, response_time=2, resolution_time=3)
|
||||
holiday_list="__Test Holiday List", employee_group="_Test Employee Group",
|
||||
entity_type="Customer", entity=customer, response_time=7200, resolution_time=10800)
|
||||
get_customer_service_level_agreement = get_service_level_agreement(entity_type="Customer", entity=customer)
|
||||
|
||||
self.assertEqual(create_customer_service_level_agreement.name, get_customer_service_level_agreement.name)
|
||||
@ -40,8 +41,8 @@ class TestServiceLevelAgreement(unittest.TestCase):
|
||||
# Service Level Agreement for Customer Group
|
||||
customer_group = create_customer_group()
|
||||
create_customer_group_service_level_agreement = create_service_level_agreement(default_service_level_agreement=0,
|
||||
service_level="_Test Service Level", holiday_list="__Test Holiday List", employee_group="_Test Employee Group",
|
||||
entity_type="Customer Group", entity=customer_group, response_time=2, resolution_time=3)
|
||||
holiday_list="__Test Holiday List", employee_group="_Test Employee Group",
|
||||
entity_type="Customer Group", entity=customer_group, response_time=7200, resolution_time=10800)
|
||||
get_customer_group_service_level_agreement = get_service_level_agreement(entity_type="Customer Group", entity=customer_group)
|
||||
|
||||
self.assertEqual(create_customer_group_service_level_agreement.name, get_customer_group_service_level_agreement.name)
|
||||
@ -52,8 +53,8 @@ class TestServiceLevelAgreement(unittest.TestCase):
|
||||
# Service Level Agreement for Territory
|
||||
territory = create_territory()
|
||||
create_territory_service_level_agreement = create_service_level_agreement(default_service_level_agreement=0,
|
||||
service_level="_Test Service Level", holiday_list="__Test Holiday List", employee_group="_Test Employee Group",
|
||||
entity_type="Territory", entity=territory, response_time=2, resolution_time=3)
|
||||
holiday_list="__Test Holiday List", employee_group="_Test Employee Group",
|
||||
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)
|
||||
|
||||
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)
|
||||
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):
|
||||
|
||||
employee_group = make_employee_group()
|
||||
make_holiday_list()
|
||||
make_priorities()
|
||||
|
||||
service_level_agreement = frappe.get_doc({
|
||||
"doctype": "Service Level Agreement",
|
||||
"enable": 1,
|
||||
"service_level": "__Test Service Level",
|
||||
"default_service_level_agreement": default_service_level_agreement,
|
||||
"service_level": service_level,
|
||||
"default_priority": "Medium",
|
||||
"holiday_list": holiday_list,
|
||||
"employee_group": employee_group,
|
||||
"entity_type": entity_type,
|
||||
@ -109,6 +115,11 @@ def create_service_level_agreement(default_service_level_agreement, service_leve
|
||||
"resolution_time_period": "Hour",
|
||||
}
|
||||
],
|
||||
"pause_sla_on": [
|
||||
{
|
||||
"status": "Replied"
|
||||
}
|
||||
],
|
||||
"support_and_resolution": [
|
||||
{
|
||||
"workday": "Monday",
|
||||
@ -167,6 +178,7 @@ def create_service_level_agreement(default_service_level_agreement, service_leve
|
||||
else:
|
||||
return frappe.get_doc("Service Level Agreement", service_level_agreement_exists)
|
||||
|
||||
|
||||
def create_customer():
|
||||
customer = frappe.get_doc({
|
||||
"doctype": "Customer",
|
||||
@ -206,23 +218,41 @@ def create_territory():
|
||||
return frappe.db.exists("Territory", {"territory_name": "_Test SLA Territory"})
|
||||
|
||||
def create_service_level_agreements_for_issues():
|
||||
create_service_level_for_sla()
|
||||
|
||||
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_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_customer()
|
||||
create_service_level_agreement(default_service_level_agreement=0,
|
||||
service_level="_Test Service Level", holiday_list="__Test Holiday List", employee_group="_Test Employee Group",
|
||||
entity_type="Customer", entity="_Test Customer", response_time=2, resolution_time=3)
|
||||
create_service_level_agreement(default_service_level_agreement=0, holiday_list="__Test Holiday List",
|
||||
employee_group="_Test Employee Group", entity_type="Customer", entity="_Test Customer", response_time=7200, resolution_time=10800)
|
||||
|
||||
create_customer_group()
|
||||
create_service_level_agreement(default_service_level_agreement=0,
|
||||
service_level="_Test Service Level", holiday_list="__Test Holiday List", employee_group="_Test Employee Group",
|
||||
entity_type="Customer Group", entity="_Test SLA Customer Group", response_time=2, resolution_time=3)
|
||||
create_service_level_agreement(default_service_level_agreement=0, holiday_list="__Test Holiday List",
|
||||
employee_group="_Test Employee Group", entity_type="Customer Group", entity="_Test SLA Customer Group", response_time=7200, resolution_time=10800)
|
||||
|
||||
create_territory()
|
||||
create_service_level_agreement(default_service_level_agreement=0,
|
||||
service_level="_Test Service Level", holiday_list="__Test Holiday List", employee_group="_Test Employee Group",
|
||||
entity_type="Territory", entity="_Test SLA Territory", response_time=2, resolution_time=3)
|
||||
create_service_level_agreement(default_service_level_agreement=0, holiday_list="__Test Holiday List",
|
||||
employee_group="_Test Employee Group", entity_type="Territory", entity="_Test SLA Territory", response_time=7200, resolution_time=10800)
|
||||
|
||||
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",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
@ -9,10 +10,8 @@
|
||||
"default_priority",
|
||||
"sb_00",
|
||||
"response_time",
|
||||
"response_time_period",
|
||||
"cb_00",
|
||||
"resolution_time",
|
||||
"resolution_time_period"
|
||||
"resolution_time"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@ -28,16 +27,11 @@
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"columns": 1,
|
||||
"fieldname": "response_time",
|
||||
"fieldtype": "Int",
|
||||
"in_list_view": 1,
|
||||
"label": "Response Time"
|
||||
},
|
||||
{
|
||||
"columns": 1,
|
||||
"columns": 2,
|
||||
"fieldname": "resolution_time",
|
||||
"fieldtype": "Int",
|
||||
"fieldtype": "Duration",
|
||||
"hide_days": 1,
|
||||
"hide_seconds": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Resolution Time"
|
||||
},
|
||||
@ -45,36 +39,31 @@
|
||||
"fieldname": "cb_00",
|
||||
"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",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"columns": 1,
|
||||
"default": "0",
|
||||
"fieldname": "default_priority",
|
||||
"fieldtype": "Check",
|
||||
"in_list_view": 1,
|
||||
"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,
|
||||
"modified": "2019-05-21 06:54:42.674377",
|
||||
"links": [],
|
||||
"modified": "2020-06-10 12:45:47.545915",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Support",
|
||||
"name": "Service Level Priority",
|
||||
|
@ -3,6 +3,6 @@
|
||||
|
||||
frappe.ui.form.on('Support Settings', {
|
||||
refresh: function(frm) {
|
||||
|
||||
//
|
||||
}
|
||||
});
|
||||
|
@ -1,4 +1,5 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2017-02-17 13:07:35.686409",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
@ -122,13 +123,15 @@
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.track_service_level_agreement;",
|
||||
"fieldname": "allow_resetting_service_level_agreement",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow Resetting Service Level Agreement"
|
||||
}
|
||||
],
|
||||
"issingle": 1,
|
||||
"modified": "2019-07-10 22:52:39.663873",
|
||||
"links": [],
|
||||
"modified": "2020-06-05 17:56:17.491684",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Support",
|
||||
"name": "Support Settings",
|
||||
|
@ -193,14 +193,17 @@ class ItemConfigure {
|
||||
filtered_items_count === 1 ?
|
||||
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 ? `
|
||||
<div class="alert alert-success d-flex justify-content-between align-items-center" role="alert">
|
||||
<div>
|
||||
<div>${one_item} ${product_info && product_info.price ? '(' + product_info.price.formatted_price_sales_uom + ')' : ''}</div>
|
||||
</div>
|
||||
<a href data-action="btn_add_to_cart" data-item-code="${one_item}">
|
||||
${__('Add to cart')}
|
||||
</a>
|
||||
${product_action}
|
||||
</div>
|
||||
`: '';
|
||||
|
||||
|
@ -16,10 +16,10 @@
|
||||
<div>{{ introduction }}</div>
|
||||
{% endif %}
|
||||
|
||||
{%- if application_form_route -%}
|
||||
{%- if doc.enable_admission_application -%}
|
||||
<p>
|
||||
<a class='btn btn-primary'
|
||||
href='/{{ doc.application_form_route }}'>
|
||||
href='/student-applicant'>
|
||||
{{ _("Apply Now") }}</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
|
@ -3,7 +3,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-check"><polyline points="20 6 9 17 4 12"></polyline></svg>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">{{ address.name }}</h5>
|
||||
<h5 class="card-title">{{ address.title }}</h5>
|
||||
<p class="card-text text-muted">
|
||||
{{ address.display }}
|
||||
</p>
|
||||
|
@ -109,7 +109,7 @@ frappe.ready(() => {
|
||||
reqd: 1
|
||||
},
|
||||
{
|
||||
label: __('Pin Code'),
|
||||
label: __('Postal Code'),
|
||||
fieldname: 'pincode',
|
||||
fieldtype: 'Data'
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user