diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.js b/erpnext/assets/doctype/asset_repair/asset_repair.js index 2920ff7e381..8e36b6d0be9 100644 --- a/erpnext/assets/doctype/asset_repair/asset_repair.js +++ b/erpnext/assets/doctype/asset_repair/asset_repair.js @@ -116,24 +116,39 @@ frappe.ui.form.on("Asset Repair", { }, repair_status: (frm) => { - if (frm.doc.completion_date && frm.doc.repair_status == "Completed") { - frappe.call({ - method: "erpnext.assets.doctype.asset_repair.asset_repair.get_downtime", - args: { - failure_date: frm.doc.failure_date, - completion_date: frm.doc.completion_date, - }, - callback: function (r) { - if (r.message) { - frm.set_value("downtime", r.message + " Hrs"); - } - }, - }); - } - if (frm.doc.repair_status == "Completed" && !frm.doc.completion_date) { frm.set_value("completion_date", frappe.datetime.now_datetime()); } + + frm.events.set_downtime(frm); + }, + + failure_date: (frm) => { + frm.events.set_downtime(frm); + }, + + completion_date: (frm) => { + frm.events.set_downtime(frm); + }, + + set_downtime: (frm) => { + if (frm.doc.repair_status != "Completed" || !frm.doc.failure_date || !frm.doc.completion_date) { + frm.set_value("downtime", null); + return; + } + + frappe.call({ + method: "erpnext.assets.doctype.asset_repair.asset_repair.get_downtime", + args: { + failure_date: frm.doc.failure_date, + completion_date: frm.doc.completion_date, + }, + callback: function (r) { + if (r.message) { + frm.set_value("downtime", r.message + " Hrs"); + } + }, + }); }, stock_items_on_form_rendered() { diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.py b/erpnext/assets/doctype/asset_repair/asset_repair.py index 0b3e1dbe389..cba62683c8e 100644 --- a/erpnext/assets/doctype/asset_repair/asset_repair.py +++ b/erpnext/assets/doctype/asset_repair/asset_repair.py @@ -67,6 +67,7 @@ class AssetRepair(AccountsController): self.calculate_repair_cost() self.calculate_total_repair_cost() self.check_repair_status() + self.set_downtime() def validate_asset(self): if self.asset_doc.status in ("Sold", "Scrapped"): @@ -239,6 +240,13 @@ class AssetRepair(AccountsController): if self.repair_status == "Pending" and self.docstatus == 1: frappe.throw(_("Please update Repair Status.")) + def set_downtime(self): + # keep downtime in sync with the entered dates, regardless of edit order + if self.repair_status == "Completed" and self.failure_date and self.completion_date: + self.downtime = f"{get_downtime(self.failure_date, self.completion_date)} Hrs" + else: + self.downtime = None + def update_asset_value(self): total_repair_cost = self.total_repair_cost if self.docstatus == 1 else -1 * self.total_repair_cost diff --git a/erpnext/assets/doctype/asset_repair/test_asset_repair.py b/erpnext/assets/doctype/asset_repair/test_asset_repair.py index 89f171fd3fe..b84bbfba8aa 100644 --- a/erpnext/assets/doctype/asset_repair/test_asset_repair.py +++ b/erpnext/assets/doctype/asset_repair/test_asset_repair.py @@ -98,6 +98,21 @@ class TestAssetRepair(ERPNextTestSuite): asset_repair = create_asset_repair(submit=1) self.assertNotEqual(asset_repair.repair_status, "Pending") + def test_downtime_stays_in_sync_with_dates(self): + asset = create_asset(submit=1) + asset_repair = create_asset_repair(asset=asset) + + asset_repair.failure_date = "2026-07-31 09:00:00" + asset_repair.completion_date = "2026-07-31 11:00:00" + asset_repair.repair_status = "Completed" + asset_repair.save() + self.assertEqual(asset_repair.downtime, "2.0 Hrs") + + # editing a date must refresh downtime, not leave a stale value + asset_repair.completion_date = "2026-07-31 14:30:00" + asset_repair.save() + self.assertEqual(asset_repair.downtime, "5.5 Hrs") + def test_stock_items(self): asset_repair = create_asset_repair(stock_consumption=1) self.assertTrue(asset_repair.stock_consumption)