chore: Limit Update Cost jobs & `db_update` only if changed values

- If `Update Cost` job is ongoing, then block creation of new ones since all BOMs are updated
- `db_update` in `calculate_rm_cost` only if changed values to reduce redundant row updates
- Misc: Use variable for batch size
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index 7055efa..d4e0d4b 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -644,6 +644,7 @@
 		base_total_rm_cost = 0
 
 		for d in self.get("items"):
+			old_rate = d.rate
 			d.rate = self.get_rm_rate(
 				{
 					"company": self.company,
@@ -656,6 +657,7 @@
 					"sourced_by_supplier": d.sourced_by_supplier,
 				}
 			)
+
 			d.base_rate = flt(d.rate) * flt(self.conversion_rate)
 			d.amount = flt(d.rate, d.precision("rate")) * flt(d.qty, d.precision("qty"))
 			d.base_amount = d.amount * flt(self.conversion_rate)
@@ -665,7 +667,7 @@
 
 			total_rm_cost += d.amount
 			base_total_rm_cost += d.base_amount
-			if save:
+			if save and (old_rate != d.rate):
 				d.db_update()
 
 		self.raw_material_cost = total_rm_cost
diff --git a/erpnext/manufacturing/doctype/bom_update_log/bom_update_log.py b/erpnext/manufacturing/doctype/bom_update_log/bom_update_log.py
index 639628a..f61f863 100644
--- a/erpnext/manufacturing/doctype/bom_update_log/bom_update_log.py
+++ b/erpnext/manufacturing/doctype/bom_update_log/bom_update_log.py
@@ -26,6 +26,8 @@
 			self.validate_boms_are_specified()
 			self.validate_same_bom()
 			self.validate_bom_items()
+		else:
+			self.validate_bom_cost_update_in_progress()
 
 		self.status = "Queued"
 
@@ -48,6 +50,21 @@
 		if current_bom_item != new_bom_item:
 			frappe.throw(_("The selected BOMs are not for the same item"))
 
+	def validate_bom_cost_update_in_progress(self):
+		"If another Cost Updation Log is still in progress, dont make new ones."
+
+		wip_log = frappe.get_all(
+			"BOM Update Log",
+			{"update_type": "Update Cost", "status": ["in", ["Queued", "In Progress", "Paused"]]},
+			limit_page_length=1,
+		)
+		if wip_log:
+			log_link = frappe.utils.get_link_to_form("BOM Update Log", wip_log[0].name)
+			frappe.throw(
+				_("BOM Updation already in progress. Please wait until {0} is complete.").format(log_link),
+				title=_("Note"),
+			)
+
 	def on_submit(self):
 		if frappe.flags.in_test:
 			return
@@ -124,10 +141,11 @@
 	current_boms_list = [bom for bom in current_boms]
 
 	while current_boms_list:
-		boms_to_process = current_boms_list[:20000]  # slice out batch of 20k BOMs
+		batch_size = 20_000
+		boms_to_process = current_boms_list[:batch_size]  # slice out batch of 20k BOMs
 
 		# update list to exclude 20K (queued) BOMs
-		current_boms_list = current_boms_list[20000:] if len(current_boms_list) > 20000 else []
+		current_boms_list = current_boms_list[batch_size:] if len(current_boms_list) > batch_size else []
 		frappe.enqueue(
 			method="erpnext.manufacturing.doctype.bom_update_log.bom_updation_utils.update_cost_in_level",
 			doc=update_doc,
diff --git a/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py
index 4a2e03f..758d8ed 100644
--- a/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py
+++ b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py
@@ -38,7 +38,13 @@
 def auto_update_latest_price_in_all_boms() -> None:
 	"""Called via hooks.py."""
 	if frappe.db.get_single_value("Manufacturing Settings", "update_bom_costs_automatically"):
-		create_bom_update_log(update_type="Update Cost")
+		wip_log = frappe.get_all(
+			"BOM Update Log",
+			{"update_type": "Update Cost", "status": ["in", ["Queued", "In Progress", "Paused"]]},
+			limit_page_length=1,
+		)
+		if not wip_log:
+			create_bom_update_log(update_type="Update Cost")
 
 
 def create_bom_update_log(