diff --git a/frappe/desk/doctype/bulk_update/bulk_update.py b/frappe/desk/doctype/bulk_update/bulk_update.py index 27ffb4ffb8..a0f5a45326 100644 --- a/frappe/desk/doctype/bulk_update/bulk_update.py +++ b/frappe/desk/doctype/bulk_update/bulk_update.py @@ -6,6 +6,7 @@ from frappe import _ from frappe.core.doctype.submission_queue.submission_queue import queue_submission from frappe.model.document import Document from frappe.utils import cint +from frappe.utils.deprecations import deprecated from frappe.utils.scheduler import is_scheduler_inactive @@ -24,6 +25,7 @@ class BulkUpdate(Document): limit: DF.Int update_value: DF.SmallText # end: auto-generated types + @frappe.whitelist() def bulk_update(self): self.check_permission("write") @@ -45,12 +47,12 @@ class BulkUpdate(Document): @frappe.whitelist() -def submit_cancel_or_update_docs(doctype, docnames, action="submit", data=None): +def submit_cancel_or_update_docs(doctype, docnames, action="submit", data=None, task_id=None): if isinstance(docnames, str): docnames = frappe.parse_json(docnames) if len(docnames) < 20: - return _bulk_action(doctype, docnames, action, data) + return _bulk_action(doctype, docnames, action, data, task_id) elif len(docnames) <= 500: frappe.msgprint(_("Bulk operation is enqueued in background."), alert=True) frappe.enqueue( @@ -59,6 +61,7 @@ def submit_cancel_or_update_docs(doctype, docnames, action="submit", data=None): docnames=docnames, action=action, data=data, + task_id=task_id, queue="short", timeout=1000, ) @@ -68,14 +71,15 @@ def submit_cancel_or_update_docs(doctype, docnames, action="submit", data=None): ) -def _bulk_action(doctype, docnames, action, data): +def _bulk_action(doctype, docnames, action, data, task_id=None): if data: data = frappe.parse_json(data) failed = [] + num_documents = len(docnames) - for i, d in enumerate(docnames, 1): - doc = frappe.get_doc(doctype, d) + for idx, docname in enumerate(docnames, 1): + doc = frappe.get_doc(doctype, docname) try: message = "" if action == "submit" and doc.docstatus.is_draft(): @@ -93,17 +97,23 @@ def _bulk_action(doctype, docnames, action, data): doc.save() message = _("Updating {0}").format(doctype) else: - failed.append(d) + failed.append(docname) frappe.db.commit() - show_progress(docnames, message, i, d) + frappe.publish_progress( + percent=idx / num_documents * 100, + title=message, + description=docname, + task_id=task_id, + ) except Exception: - failed.append(d) + failed.append(docname) frappe.db.rollback() return failed +@deprecated def show_progress(docnames, message, i, description): n = len(docnames) frappe.publish_progress(float(i) * 100 / n, title=message, description=description) diff --git a/frappe/public/js/frappe/list/bulk_operations.js b/frappe/public/js/frappe/list/bulk_operations.js index a0271967b4..22cf51166d 100644 --- a/frappe/public/js/frappe/list/bulk_operations.js +++ b/frappe/public/js/frappe/list/bulk_operations.js @@ -209,28 +209,36 @@ export default class BulkOperations { submit_or_cancel(docnames, action = "submit", done = null) { action = action.toLowerCase(); - frappe - .call({ - method: "frappe.desk.doctype.bulk_update.bulk_update.submit_cancel_or_update_docs", - args: { - doctype: this.doctype, - action: action, - docnames: docnames, - }, + const task_id = Math.random().toString(36).slice(-5); + frappe.realtime.task_subscribe(task_id); + return frappe + .xcall("frappe.desk.doctype.bulk_update.bulk_update.submit_cancel_or_update_docs", { + doctype: this.doctype, + action: action, + docnames: docnames, + task_id: task_id, }) - .then((r) => { - let failed = r.message; - if (!failed) failed = []; - - if (failed.length && !r._server_messages) { - frappe.throw( - __("Cannot {0} {1}", [action, failed.map((f) => f.bold()).join(", ")]) - ); + .then((failed_docnames) => { + if (failed_docnames?.length) { + const comma_separated_records = frappe.utils.comma_and(failed_docnames); + switch (action) { + case "submit": + frappe.throw(__("Cannot submit {0}.", [comma_separated_records])); + break; + case "cancel": + frappe.throw(__("Cannot cancel {0}.", [comma_separated_records])); + break; + default: + frappe.throw(__("Cannot {0} {1}.", [action, comma_separated_records])); + } } - if (failed.length < docnames.length) { + if (failed_docnames?.length < docnames.length) { frappe.utils.play_sound(action); if (done) done(); } + }) + .finally(() => { + frappe.realtime.task_unsubscribe(task_id); }); } diff --git a/frappe/realtime.py b/frappe/realtime.py index b795df3782..fe62571c05 100644 --- a/frappe/realtime.py +++ b/frappe/realtime.py @@ -9,13 +9,16 @@ import frappe from frappe.utils.data import cstr -def publish_progress(percent, title=None, doctype=None, docname=None, description=None): +def publish_progress( + percent, title=None, doctype=None, docname=None, description=None, task_id=None +): publish_realtime( "progress", {"percent": percent, "title": title, "description": description}, user=None if doctype and docname else frappe.session.user, doctype=doctype, docname=docname, + task_id=task_id, ) @@ -41,8 +44,11 @@ def publish_realtime( if message is None: message = {} + if not task_id and hasattr(frappe.local, "task_id"): + task_id = frappe.local.task_id + if event is None: - event = "task_progress" if frappe.local.task_id else "global" + event = "task_progress" if task_id else "global" elif event == "msgprint" and not user: user = frappe.session.user elif event == "list_update": @@ -51,9 +57,6 @@ def publish_realtime( elif event == "docinfo_update": room = get_doc_room(doctype, docname) - if not task_id and hasattr(frappe.local, "task_id"): - task_id = frappe.local.task_id - if not room: if task_id: after_commit = False