diff --git a/frappe/desk/form/linked_with.py b/frappe/desk/form/linked_with.py index b90a4d8aa6..52d70c905c 100644 --- a/frappe/desk/form/linked_with.py +++ b/frappe/desk/form/linked_with.py @@ -9,24 +9,36 @@ import frappe.desk.form.meta import frappe.desk.form.load @frappe.whitelist() -def get_linked_docs(doctype, name, linkinfo=None): +def get_linked_docs(doctype, name, linkinfo=None, for_doctype=None): key = "linked_with:{doctype}:{name}".format(doctype=doctype, name=name) + if isinstance(linkinfo, basestring): + # additional fields are added in linkinfo + linkinfo = json.loads(linkinfo) + + if for_doctype: + key = "{key}:{for_doctype}".format(key=key, for_doctype=for_doctype) + results = frappe.cache().get_value(key, user=True) + if results: return results meta = frappe.desk.form.meta.get_meta(doctype) results = {} - if isinstance(linkinfo, basestring): - # additional fields are added in linkinfo - linkinfo = json.loads(linkinfo) - if not linkinfo: return results + if for_doctype: + if for_doctype in linkinfo: + # only get linked with for this particular doctype + linkinfo = { for_doctype: linkinfo.get(for_doctype) } + else: + return results + me = frappe.db.get_value(doctype, name, ["parenttype", "parent"], as_dict=True) + for dt, link in linkinfo.items(): link["doctype"] = dt link_meta_bundle = frappe.desk.form.load.get_meta_bundle(dt) @@ -61,11 +73,9 @@ def get_linked_docs(doctype, name, linkinfo=None): else: filters = [[dt, link.get("fieldname"), '=', name]] - # dynamic link if link.get("doctype_fieldname"): filters.append([dt, link.get("doctype_fieldname"), "=", doctype]) - ret = frappe.get_list(doctype=dt, fields=fields, filters=filters) except frappe.PermissionError: diff --git a/frappe/model/document.py b/frappe/model/document.py index 83d7d8c8c1..b303e1e8b7 100644 --- a/frappe/model/document.py +++ b/frappe/model/document.py @@ -693,8 +693,9 @@ class Document(BaseDocument): # clear linked doctypes list cache.hdel("linked_doctypes", doctype) - # delete linked with cache for all users + # for all users, delete linked with cache and per doctype linked with cache cache.delete_value("user:*:linked_with:{doctype}:{name}".format(doctype=doctype, name=name)) + cache.delete_value("user:*:linked_with:{doctype}:{name}:*".format(doctype=doctype, name=name)) _clear_cache(self) for d in self.get_all_children(): diff --git a/frappe/public/build.json b/frappe/public/build.json index e0ae0ad621..e01f97b8d4 100644 --- a/frappe/public/build.json +++ b/frappe/public/build.json @@ -161,12 +161,14 @@ "public/js/frappe/form/templates/set_sharing.html", "public/js/frappe/form/templates/form_sidebar.html", "public/js/frappe/form/templates/form_dashboard.html", + "public/js/frappe/form/templates/form_document_flow.html", "public/js/frappe/form/templates/form_links.html", "public/js/frappe/views/formview.js", "public/js/legacy/form.js", "public/js/legacy/clientscriptAPI.js", "public/js/frappe/form/toolbar.js", "public/js/frappe/form/dashboard.js", + "public/js/frappe/form/document_flow.js", "public/js/frappe/form/save.js", "public/js/frappe/form/script_manager.js", "public/js/frappe/form/grid.js", diff --git a/frappe/public/css/form.css b/frappe/public/css/form.css index eacd8f529a..460805984e 100644 --- a/frappe/public/css/form.css +++ b/frappe/public/css/form.css @@ -30,6 +30,42 @@ .form-message { padding: 15px; } +.document-flow-wrapper { + padding: 40px 15px 30px; + font-size: 12px; + border-bottom: 1px solid #EBEFF2; +} +.document-flow-wrapper .document-flow { + display: inline-block; + position: relative; + left: 50%; + transform: translateX(-50%); +} +.document-flow-wrapper .document-flow .document-flow-link-wrapper:not(:last-child) { + border-top: 1px solid #b8c2cc; + padding-right: 60px; + display: inline-block; + margin-right: -4px; +} +.document-flow-wrapper .document-flow .document-flow-link { + margin-top: -10px; + display: inline-block; +} +.document-flow-wrapper .document-flow .document-flow-link:not(.disabled):hover .document-flow-link-label, +.document-flow-wrapper .document-flow .document-flow-link:not(.disabled):focus .document-flow-link-label, +.document-flow-wrapper .document-flow .document-flow-link:not(.disabled):active .document-flow-link-label { + text-decoration: underline; +} +.document-flow-wrapper .document-flow .document-flow-link-label { + display: inline-block; + margin-left: -50%; + margin-top: 5px; +} +@media (max-width: 767px) { + .document-flow-wrapper { + display: none; + } +} .form-dashboard { border-bottom: 1px solid #EBEFF2; } diff --git a/frappe/public/css/indicator.css b/frappe/public/css/indicator.css index 711df4d87a..2b7f651164 100644 --- a/frappe/public/css/indicator.css +++ b/frappe/public/css/indicator.css @@ -48,6 +48,10 @@ .indicator-right.darkgrey::after { background: #b8c2cc; } +.indicator.black::before, +.indicator-right.black::after { + background: #36414C; +} .indicator.yellow::before, .indicator-right.yellow::after { background: #FEEF72; diff --git a/frappe/public/js/frappe/form/document_flow.js b/frappe/public/js/frappe/form/document_flow.js new file mode 100644 index 0000000000..610adb863b --- /dev/null +++ b/frappe/public/js/frappe/form/document_flow.js @@ -0,0 +1,52 @@ +// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +// MIT License. See license.txt + +frappe.ui.form.DocumentFlow = Class.extend({ + init: function(opts) { + $.extend(this, opts); + this.wrapper = $('
').prependTo(this.frm.layout.wrapper); + }, + + refresh: function() { + this.reset(); + this.render(); + }, + + reset: function() { + this.wrapper.empty().addClass('hidden'); + this.linked_with = {}; + }, + + render: function() { + var me = this; + var module = frappe.get_meta(this.frm.doctype).module + var doctypes = frappe.document_flow[module][this.frm.doctype]; + if (!doctypes) { + return; + } + + $(frappe.render_template('form_document_flow', { + frm: this.frm, + doctypes: doctypes, + })).appendTo(this.wrapper.removeClass('hidden')); + + this.wrapper.on('click', '.document-flow-link', function() { + var doctype = $(this).attr("data-doctype"); + if (me.frm.doctype != doctype) { + me.get_linked_docs(doctype); + return false; + } + }); + }, + + get_linked_docs: function(for_doctype) { + if(!this.linked_with[for_doctype]) { + this.linked_with[for_doctype] = new frappe.ui.form.LinkedWith({ + frm: this.frm, + for_doctype: for_doctype + }); + } + + this.linked_with[for_doctype].show(); + } +}); diff --git a/frappe/public/js/frappe/form/linked_with.js b/frappe/public/js/frappe/form/linked_with.js index 344b79df59..f127659aee 100644 --- a/frappe/public/js/frappe/form/linked_with.js +++ b/frappe/public/js/frappe/form/linked_with.js @@ -110,13 +110,13 @@ frappe.ui.form.LinkedWith = Class.extend({ get_linked_docs: function() { var me = this; - return frappe.call({ method:"frappe.desk.form.linked_with.get_linked_docs", args: { doctype: me.frm.doctype, name: me.frm.docname, - linkinfo: me.frm.__linked_doctypes + linkinfo: me.frm.__linked_doctypes, + for_doctype: me.for_doctype }, callback: function(r) { var parent = me.dialog.fields_dict.list.$wrapper.empty(); diff --git a/frappe/public/js/frappe/form/templates/form_document_flow.html b/frappe/public/js/frappe/form/templates/form_document_flow.html new file mode 100644 index 0000000000..42e2b91efd --- /dev/null +++ b/frappe/public/js/frappe/form/templates/form_document_flow.html @@ -0,0 +1,12 @@ +