diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 54646ca4296..062e39ed615 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -402,5 +402,6 @@ erpnext.patches.v15_0.sync_auto_reconcile_config execute:frappe.db.set_single_value("Accounts Settings", "exchange_gain_loss_posting_date", "Payment") erpnext.patches.v14_0.disable_add_row_in_gross_profit erpnext.patches.v14_0.update_posting_datetime +erpnext.patches.v15_0.rename_sla_fields erpnext.stock.doctype.stock_ledger_entry.patches.ensure_sle_indexes erpnext.patches.v15_0.update_query_report diff --git a/erpnext/patches/v15_0/rename_sla_fields.py b/erpnext/patches/v15_0/rename_sla_fields.py new file mode 100644 index 00000000000..5e3e92d85c2 --- /dev/null +++ b/erpnext/patches/v15_0/rename_sla_fields.py @@ -0,0 +1,13 @@ +import frappe +from frappe.custom.doctype.custom_field.custom_field import rename_fieldname +from frappe.model.utils.rename_field import rename_field + + +def execute(): + doctypes = frappe.get_all("Service Level Agreement", pluck="document_type") + for doctype in doctypes: + rename_fieldname(doctype + "-resolution_by", "sla_resolution_by") + rename_fieldname(doctype + "-resolution_date", "sla_resolution_date") + + rename_field("Issue", "resolution_by", "sla_resolution_by") + rename_field("Issue", "resolution_date", "sla_resolution_date") diff --git a/erpnext/support/doctype/issue/issue.json b/erpnext/support/doctype/issue/issue.json index d3244a7cdd6..20c3853f487 100644 --- a/erpnext/support/doctype/issue/issue.json +++ b/erpnext/support/doctype/issue/issue.json @@ -27,7 +27,7 @@ "reset_service_level_agreement", "cb", "agreement_status", - "resolution_by", + "sla_resolution_by", "service_level_agreement_creation", "on_hold_since", "total_hold_time", @@ -41,7 +41,7 @@ "column_break1", "opening_date", "opening_time", - "resolution_date", + "sla_resolution_date", "resolution_time", "user_resolution_time", "additional_info", @@ -176,13 +176,6 @@ "options": "fa fa-pushpin", "read_only": 1 }, - { - "depends_on": "eval: doc.status != 'Replied' && doc.service_level_agreement;", - "fieldname": "resolution_by", - "fieldtype": "Datetime", - "label": "Resolution By", - "read_only": 1 - }, { "collapsible": 1, "fieldname": "response", @@ -287,16 +280,6 @@ "oldfieldtype": "Time", "read_only": 1 }, - { - "depends_on": "eval:!doc.__islocal", - "fieldname": "resolution_date", - "fieldtype": "Datetime", - "label": "Resolution Date", - "no_copy": 1, - "oldfieldname": "resolution_date", - "oldfieldtype": "Date", - "read_only": 1 - }, { "fieldname": "content_type", "fieldtype": "Data", @@ -386,12 +369,29 @@ "fieldtype": "Duration", "label": "First Response Time", "read_only": 1 + }, + { + "depends_on": "eval: doc.status != 'Replied' && doc.service_level_agreement;", + "fieldname": "sla_resolution_by", + "fieldtype": "Datetime", + "label": "Resolution By", + "read_only": 1 + }, + { + "depends_on": "eval:!doc.__islocal", + "fieldname": "sla_resolution_date", + "fieldtype": "Datetime", + "label": "Resolution Date", + "no_copy": 1, + "oldfieldname": "resolution_date", + "oldfieldtype": "Date", + "read_only": 1 } ], "icon": "fa fa-ticket", "idx": 7, "links": [], - "modified": "2024-03-27 13:09:52.921791", + "modified": "2025-02-18 21:18:52.797745", "modified_by": "Administrator", "module": "Support", "name": "Issue", diff --git a/erpnext/support/doctype/issue/issue.py b/erpnext/support/doctype/issue/issue.py index c8b85dba4da..e5b158166cc 100644 --- a/erpnext/support/doctype/issue/issue.py +++ b/erpnext/support/doctype/issue/issue.py @@ -48,13 +48,13 @@ class Issue(Document): priority: DF.Link | None project: DF.Link | None raised_by: DF.Data | None - resolution_by: DF.Datetime | None - resolution_date: DF.Datetime | None resolution_details: DF.TextEditor | None resolution_time: DF.Duration | None response_by: DF.Datetime | None service_level_agreement: DF.Link | None service_level_agreement_creation: DF.Datetime | None + sla_resolution_by: DF.Datetime | None + sla_resolution_date: DF.Datetime | None status: DF.Literal["Open", "Replied", "On Hold", "Resolved", "Closed"] subject: DF.Data total_hold_time: DF.Duration | None diff --git a/erpnext/support/doctype/issue/test_issue.py b/erpnext/support/doctype/issue/test_issue.py index 5da2c470a27..1f8ec6afa3d 100644 --- a/erpnext/support/doctype/issue/test_issue.py +++ b/erpnext/support/doctype/issue/test_issue.py @@ -33,48 +33,48 @@ class TestIssue(TestSetUp): issue = make_issue(creation, "_Test Customer", 1) self.assertEqual(issue.response_by, get_datetime("2019-03-04 14:00")) - self.assertEqual(issue.resolution_by, get_datetime("2019-03-04 15:00")) + self.assertEqual(issue.sla_resolution_by, get_datetime("2019-03-04 15:00")) # make issue with customer_group specific SLA create_customer("__Test Customer", "_Test SLA Customer Group", "__Test SLA Territory") issue = make_issue(creation, "__Test Customer", 2) self.assertEqual(issue.response_by, get_datetime("2019-03-04 14:00")) - self.assertEqual(issue.resolution_by, get_datetime("2019-03-04 15:00")) + self.assertEqual(issue.sla_resolution_by, get_datetime("2019-03-04 15:00")) # make issue with territory specific SLA create_customer("___Test Customer", "__Test SLA Customer Group", "_Test SLA Territory") issue = make_issue(creation, "___Test Customer", 3) self.assertEqual(issue.response_by, get_datetime("2019-03-04 14:00")) - self.assertEqual(issue.resolution_by, get_datetime("2019-03-04 15:00")) + self.assertEqual(issue.sla_resolution_by, get_datetime("2019-03-04 15:00")) # make issue with default SLA issue = make_issue(creation=creation, index=4) self.assertEqual(issue.response_by, get_datetime("2019-03-04 16:00")) - self.assertEqual(issue.resolution_by, get_datetime("2019-03-04 18:00")) + self.assertEqual(issue.sla_resolution_by, get_datetime("2019-03-04 18:00")) # make issue with default SLA before working hours creation = get_datetime("2019-03-04 7:00") issue = make_issue(creation=creation, index=5) self.assertEqual(issue.response_by, get_datetime("2019-03-04 14:00")) - self.assertEqual(issue.resolution_by, get_datetime("2019-03-04 16:00")) + self.assertEqual(issue.sla_resolution_by, get_datetime("2019-03-04 16:00")) # make issue with default SLA after working hours creation = get_datetime("2019-03-04 20:00") issue = make_issue(creation, index=6) self.assertEqual(issue.response_by, get_datetime("2019-03-06 14:00")) - self.assertEqual(issue.resolution_by, get_datetime("2019-03-06 16:00")) + self.assertEqual(issue.sla_resolution_by, get_datetime("2019-03-06 16:00")) # make issue with default SLA next day creation = get_datetime("2019-03-04 14:00") issue = make_issue(creation=creation, index=7) self.assertEqual(issue.response_by, get_datetime("2019-03-04 18:00")) - self.assertEqual(issue.resolution_by, get_datetime("2019-03-06 12:00")) + self.assertEqual(issue.sla_resolution_by, get_datetime("2019-03-06 12:00")) frappe.flags.current_time = get_datetime("2019-03-04 15:00") issue.reload() @@ -98,7 +98,7 @@ class TestIssue(TestSetUp): issue.save() self.assertEqual(issue.on_hold_since, frappe.flags.current_time) - self.assertFalse(issue.resolution_by) + self.assertFalse(issue.sla_resolution_by) creation = get_datetime("2020-03-04 5:00") frappe.flags.current_time = get_datetime("2020-03-04 5:00") @@ -106,7 +106,7 @@ class TestIssue(TestSetUp): issue.reload() self.assertEqual(flt(issue.total_hold_time, 2), 2700) - self.assertEqual(issue.resolution_by, get_datetime("2020-03-04 16:45")) + self.assertEqual(issue.sla_resolution_by, get_datetime("2020-03-04 16:45")) creation = get_datetime("2020-03-04 5:05") create_communication(issue.name, "test@admin.com", "Sent", creation) @@ -140,8 +140,8 @@ class TestIssue(TestSetUp): issue.status = "Closed" issue.save() - self.assertEqual(issue.resolution_by, get_datetime("2021-11-22 06:00:00")) - self.assertEqual(issue.resolution_date, get_datetime("2021-11-22 01:00:00")) + self.assertEqual(issue.sla_resolution_by, get_datetime("2021-11-22 06:00:00")) + self.assertEqual(issue.sla_resolution_date, get_datetime("2021-11-22 01:00:00")) self.assertEqual(issue.agreement_status, "Fulfilled") def test_issue_open_after_closed(self): @@ -153,7 +153,7 @@ class TestIssue(TestSetUp): create_communication(issue.name, "test@example.com", "Received", frappe.flags.current_time) self.assertEqual(issue.agreement_status, "First Response Due") self.assertEqual(issue.response_by, get_datetime("2021-11-01 17:00")) - self.assertEqual(issue.resolution_by, get_datetime("2021-11-01 19:00")) + self.assertEqual(issue.sla_resolution_by, get_datetime("2021-11-01 19:00")) # Replied on → 2 pm frappe.flags.current_time = get_datetime("2021-11-01 14:00") @@ -173,7 +173,7 @@ class TestIssue(TestSetUp): # Hold Time + 1 Hrs self.assertEqual(issue.total_hold_time, 3600) # Resolution By should increase by one hrs - self.assertEqual(issue.resolution_by, get_datetime("2021-11-01 20:00")) + self.assertEqual(issue.sla_resolution_by, get_datetime("2021-11-01 20:00")) # Replied on → 4 pm, Open → 1 hr, Resolution Due → 8 pm frappe.flags.current_time = get_datetime("2021-11-01 16:00") @@ -190,9 +190,9 @@ class TestIssue(TestSetUp): # Hold Time + 6 Hrs self.assertEqual(issue.total_hold_time, 3600 + 21600) # Resolution By should increase by 6 hrs - self.assertEqual(issue.resolution_by, get_datetime("2021-11-02 02:00")) + self.assertEqual(issue.sla_resolution_by, get_datetime("2021-11-02 02:00")) self.assertEqual(issue.agreement_status, "Fulfilled") - self.assertEqual(issue.resolution_date, frappe.flags.current_time) + self.assertEqual(issue.sla_resolution_date, frappe.flags.current_time) # Customer Open → 3 am i.e after resolution by is crossed frappe.flags.current_time = get_datetime("2021-11-02 03:00") @@ -201,17 +201,17 @@ class TestIssue(TestSetUp): # Since issue was Resolved, Resolution By should be increased by 5 hrs (3am - 10pm) self.assertEqual(issue.total_hold_time, 3600 + 21600 + 18000) # Resolution By should increase by 5 hrs - self.assertEqual(issue.resolution_by, get_datetime("2021-11-02 07:00")) + self.assertEqual(issue.sla_resolution_by, get_datetime("2021-11-02 07:00")) self.assertEqual(issue.agreement_status, "Resolution Due") - self.assertFalse(issue.resolution_date) + self.assertFalse(issue.sla_resolution_date) # We Closed → 4 am, SLA should be Fulfilled frappe.flags.current_time = get_datetime("2021-11-02 04:00") issue.status = "Closed" issue.save() - self.assertEqual(issue.resolution_by, get_datetime("2021-11-02 07:00")) + self.assertEqual(issue.sla_resolution_by, get_datetime("2021-11-02 07:00")) self.assertEqual(issue.agreement_status, "Fulfilled") - self.assertEqual(issue.resolution_date, frappe.flags.current_time) + self.assertEqual(issue.sla_resolution_date, frappe.flags.current_time) def test_recording_of_assignment_on_first_reponse_failure(self): from frappe.desk.form.assign_to import add as add_assignment diff --git a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py index 9b7d134847d..02441e26571 100644 --- a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py +++ b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py @@ -514,7 +514,7 @@ def apply(doc, method=None): def remove_sla_if_applied(doc): doc.service_level_agreement = None doc.response_by = None - doc.resolution_by = None + doc.sla_resolution_by = None def process_sla(doc, sla): @@ -557,7 +557,7 @@ def handle_status_change(doc, apply_sla_for_resolution): # In case issue was closed and after few days it has been opened # The hold time should be calculated from resolution_date - on_hold_since = doc.resolution_date or doc.on_hold_since + on_hold_since = doc.sla_resolution_date or doc.on_hold_since if on_hold_since: current_hold_hours = time_diff_in_seconds(now_time, on_hold_since) doc.total_hold_time = (doc.total_hold_time or 0) + current_hold_hours @@ -582,7 +582,7 @@ def handle_status_change(doc, apply_sla_for_resolution): # Open to Closed if is_open_status(prev_status) and is_fulfilled_status(doc.status): # Issue is closed -> Set resolution_date - doc.resolution_date = now_time + doc.sla_resolution_date = now_time set_resolution_time(doc) # Closed to Open @@ -606,7 +606,7 @@ def handle_status_change(doc, apply_sla_for_resolution): calculate_hold_hours() # Issue is closed -> Set resolution_date if apply_sla_for_resolution: - doc.resolution_date = now_time + doc.sla_resolution_date = now_time set_resolution_time(doc) @@ -713,7 +713,7 @@ def get_support_days(service_level): def set_resolution_time(doc): start_date_time = get_datetime(doc.get("service_level_agreement_creation") or doc.creation) if doc.meta.has_field("resolution_time"): - doc.resolution_time = time_diff_in_seconds(doc.resolution_date, start_date_time) + doc.resolution_time = time_diff_in_seconds(doc.sla_resolution_date, start_date_time) # total time taken by a user to close the issue apart from wait_time if not doc.meta.has_field("user_resolution_time"): @@ -737,7 +737,7 @@ def set_resolution_time(doc): pending_time.append(wait_time) total_pending_time = sum(pending_time) - resolution_time_in_secs = time_diff_in_seconds(doc.resolution_date, start_date_time) + resolution_time_in_secs = time_diff_in_seconds(doc.sla_resolution_date, start_date_time) doc.user_resolution_time = resolution_time_in_secs - total_pending_time @@ -791,8 +791,8 @@ def reset_service_level_agreement(doctype: str, docname: str, reason, user): def reset_resolution_metrics(doc): - if doc.meta.has_field("resolution_date"): - doc.resolution_date = None + if doc.meta.has_field("sla_resolution_date"): + doc.sla_resolution_date = None if doc.meta.has_field("resolution_time"): doc.resolution_time = None @@ -859,8 +859,8 @@ def on_communication_update(doc, status): def reset_expected_response_and_resolution(doc): if doc.meta.has_field("first_responded_on") and not doc.get("first_responded_on"): doc.response_by = None - if doc.meta.has_field("resolution_by") and not doc.get("resolution_date"): - doc.resolution_by = None + if doc.meta.has_field("sla_resolution_by") and not doc.get("sla_resolution_date"): + doc.sla_resolution_by = None def set_response_by(doc, start_date_time, priority): @@ -877,12 +877,14 @@ def set_response_by(doc, start_date_time, priority): def set_resolution_by(doc, start_date_time, priority): - if doc.meta.has_field("resolution_by"): - doc.resolution_by = get_expected_time_for( + if doc.meta.has_field("sla_resolution_by"): + doc.sla_resolution_by = get_expected_time_for( parameter="resolution", service_level=priority, start_date_time=start_date_time ) if doc.meta.has_field("total_hold_time") and doc.get("total_hold_time"): - doc.resolution_by = add_to_date(doc.resolution_by, seconds=round(doc.get("total_hold_time"))) + doc.sla_resolution_by = add_to_date( + doc.sla_resolution_by, seconds=round(doc.get("total_hold_time")) + ) def record_assigned_users_on_failure(doc): @@ -941,7 +943,7 @@ def get_service_level_agreement_fields(): "read_only": 1, }, { - "fieldname": "resolution_by", + "fieldname": "sla_resolution_by", "fieldtype": "Datetime", "label": "Resolution By", "read_only": 1, @@ -955,7 +957,7 @@ def get_service_level_agreement_fields(): }, { "depends_on": "eval:!doc.__islocal", - "fieldname": "resolution_date", + "fieldname": "sla_resolution_date", "fieldtype": "Datetime", "label": "Resolution Date", "no_copy": 1, @@ -975,9 +977,9 @@ def update_agreement_status(doc, apply_sla_for_resolution): if apply_sla_for_resolution: if doc.meta.has_field("first_responded_on") and not doc.get("first_responded_on"): doc.agreement_status = "First Response Due" - elif doc.meta.has_field("resolution_date") and not doc.get("resolution_date"): + elif doc.meta.has_field("sla_resolution_date") and not doc.get("sla_resolution_date"): doc.agreement_status = "Resolution Due" - elif get_datetime(doc.get("resolution_date")) <= get_datetime(doc.get("resolution_by")): + elif get_datetime(doc.get("sla_resolution_date")) <= get_datetime(doc.get("sla_resolution_by")): doc.agreement_status = "Fulfilled" else: doc.agreement_status = "Failed" diff --git a/erpnext/support/doctype/service_level_agreement/test_service_level_agreement.py b/erpnext/support/doctype/service_level_agreement/test_service_level_agreement.py index 7e2aa888553..988bd54e9f6 100644 --- a/erpnext/support/doctype/service_level_agreement/test_service_level_agreement.py +++ b/erpnext/support/doctype/service_level_agreement/test_service_level_agreement.py @@ -227,7 +227,7 @@ class TestServiceLevelAgreement(IntegrationTestCase): self.assertEqual(lead.service_level_agreement, lead_sla.name) self.assertEqual(lead.response_by, datetime.datetime(2019, 3, 4, 16, 0)) - self.assertEqual(lead.resolution_by, datetime.datetime(2019, 3, 4, 18, 0)) + self.assertEqual(lead.sla_resolution_by, datetime.datetime(2019, 3, 4, 18, 0)) frappe.flags.current_time = datetime.datetime(2019, 3, 4, 15, 0) lead.reload() @@ -268,7 +268,7 @@ class TestServiceLevelAgreement(IntegrationTestCase): lead.reload() self.assertEqual(flt(lead.total_hold_time, 2), 3000) - self.assertEqual(lead.resolution_by, datetime.datetime(2020, 3, 4, 16, 50)) + self.assertEqual(lead.sla_resolution_by, datetime.datetime(2020, 3, 4, 16, 50)) def test_failed_sla_for_response_only(self): doctype = "Lead"