diff --git a/frappe/desk/form/linked_with.py b/frappe/desk/form/linked_with.py index 734b99a003..3921378565 100644 --- a/frappe/desk/form/linked_with.py +++ b/frappe/desk/form/linked_with.py @@ -2,13 +2,71 @@ # MIT License. See license.txt from __future__ import unicode_literals -import frappe, json +import json +from collections import defaultdict + +from six import string_types + +import frappe +import frappe.desk.form.load +import frappe.desk.form.meta +from frappe import _ from frappe.model.meta import is_single from frappe.modules import load_doctype_module -import frappe.desk.form.meta -import frappe.desk.form.load -from six import string_types -from collections import defaultdict + + +@frappe.whitelist() +def get_submitted_linked_docs(doctype, name, docs=None): + if not docs: + docs = [] + + linkinfo = get_linked_doctypes(doctype) + linked_docs = get_linked_docs(doctype, name, linkinfo) + + link_count = 0 + CANCEL_EXEMPT_DOCTYPES = [] + for doctypes in frappe.get_hooks('cancel_exempt_doctypes'): + CANCEL_EXEMPT_DOCTYPES.extend(doctypes) + + for link_doctype, link_names in linked_docs.items(): + # skip non-submittable doctypes since they don't need to be cancelled + if not frappe.get_meta(link_doctype).is_submittable: + continue + + # skip other doctypes since they don't need to be cancelled + if link_doctype in CANCEL_EXEMPT_DOCTYPES: + continue + + for link in link_names: + if link.docstatus != 1: + continue + + link_count += 1 + if link.name in [doc.get("name") for doc in docs]: + continue + + links = get_submitted_linked_docs(link_doctype, link.name, docs) + docs.append({ + "doctype": link_doctype, + "name": link.name, + "link_count": links.get("count") + }) + + # sort linked documents by ascending number of links + docs.sort(key=lambda doc: doc.get("link_count")) + return { + "docs": docs, + "count": link_count + } + + +@frappe.whitelist() +def cancel_all_linked_docs(docs): + docs = json.loads(docs) + for i, doc in enumerate(docs, 1): + frappe.publish_progress(percent=i * 100 / len(docs), title=_("Cancelling documents")) + linked_doc = frappe.get_doc(doc.get("doctype"), doc.get("name")) + linked_doc.cancel() @frappe.whitelist() @@ -203,4 +261,4 @@ def get_dynamic_linked_fields(doctype, without_ignore_user_permissions_enabled=F "doctype_fieldname": df.doctype_fieldname } - return ret \ No newline at end of file + return ret diff --git a/frappe/public/js/frappe/form/form.js b/frappe/public/js/frappe/form/form.js index 02aa8b78dc..3ed39f28c0 100644 --- a/frappe/public/js/frappe/form/form.js +++ b/frappe/public/js/frappe/form/form.js @@ -648,19 +648,103 @@ frappe.ui.form.Form = class FrappeForm { } savecancel(btn, callback, on_error) { - var me = this; + const me = this; + const handle_fail = () => { + $(btn).prop('disabled', false); + if (on_error) { + on_error(); + } + }; this.validate_form_action('Cancel'); - frappe.confirm(__("Permanently Cancel {0}?", [this.docname]), function() { + + frappe.call({ + method: "frappe.desk.form.linked_with.get_submitted_linked_docs", + args: { + doctype: me.doc.doctype, + name: me.doc.name + }, + freeze: true, + callback: (r) => { + if (!r.exc && r.message.count > 0) { + me._cancel_all(r, btn, callback, handle_fail); + } else { + me._cancel(btn, callback, handle_fail, false); + } + } + }); + } + + _cancel_all(r, btn, callback, handle_fail) { + const me = this; + + // add confirmation message for cancelling all linked docs + let links_text = ""; + let links = r.message.docs; + const doctypes = Array.from(new Set(links.map(link => link.doctype))); + + for (let doctype of doctypes) { + let docnames = links + .filter((link) => link.doctype == doctype) + .map((link) => frappe.utils.get_form_link(link.doctype, link.name, true)) + .join(", "); + links_text += `