@@ -9,24 +9,36 @@ import frappe.desk.form.meta | |||||
import frappe.desk.form.load | import frappe.desk.form.load | ||||
@frappe.whitelist() | @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) | 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) | results = frappe.cache().get_value(key, user=True) | ||||
if results: | if results: | ||||
return results | return results | ||||
meta = frappe.desk.form.meta.get_meta(doctype) | meta = frappe.desk.form.meta.get_meta(doctype) | ||||
results = {} | results = {} | ||||
if isinstance(linkinfo, basestring): | |||||
# additional fields are added in linkinfo | |||||
linkinfo = json.loads(linkinfo) | |||||
if not linkinfo: | if not linkinfo: | ||||
return results | 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) | me = frappe.db.get_value(doctype, name, ["parenttype", "parent"], as_dict=True) | ||||
for dt, link in linkinfo.items(): | for dt, link in linkinfo.items(): | ||||
link["doctype"] = dt | link["doctype"] = dt | ||||
link_meta_bundle = frappe.desk.form.load.get_meta_bundle(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: | else: | ||||
filters = [[dt, link.get("fieldname"), '=', name]] | filters = [[dt, link.get("fieldname"), '=', name]] | ||||
# dynamic link | # dynamic link | ||||
if link.get("doctype_fieldname"): | if link.get("doctype_fieldname"): | ||||
filters.append([dt, link.get("doctype_fieldname"), "=", doctype]) | filters.append([dt, link.get("doctype_fieldname"), "=", doctype]) | ||||
ret = frappe.get_list(doctype=dt, fields=fields, filters=filters) | ret = frappe.get_list(doctype=dt, fields=fields, filters=filters) | ||||
except frappe.PermissionError: | except frappe.PermissionError: | ||||
@@ -693,8 +693,9 @@ class Document(BaseDocument): | |||||
# clear linked doctypes list | # clear linked doctypes list | ||||
cache.hdel("linked_doctypes", doctype) | 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)) | ||||
cache.delete_value("user:*:linked_with:{doctype}:{name}:*".format(doctype=doctype, name=name)) | |||||
_clear_cache(self) | _clear_cache(self) | ||||
for d in self.get_all_children(): | for d in self.get_all_children(): | ||||
@@ -161,12 +161,14 @@ | |||||
"public/js/frappe/form/templates/set_sharing.html", | "public/js/frappe/form/templates/set_sharing.html", | ||||
"public/js/frappe/form/templates/form_sidebar.html", | "public/js/frappe/form/templates/form_sidebar.html", | ||||
"public/js/frappe/form/templates/form_dashboard.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/form/templates/form_links.html", | ||||
"public/js/frappe/views/formview.js", | "public/js/frappe/views/formview.js", | ||||
"public/js/legacy/form.js", | "public/js/legacy/form.js", | ||||
"public/js/legacy/clientscriptAPI.js", | "public/js/legacy/clientscriptAPI.js", | ||||
"public/js/frappe/form/toolbar.js", | "public/js/frappe/form/toolbar.js", | ||||
"public/js/frappe/form/dashboard.js", | "public/js/frappe/form/dashboard.js", | ||||
"public/js/frappe/form/document_flow.js", | |||||
"public/js/frappe/form/save.js", | "public/js/frappe/form/save.js", | ||||
"public/js/frappe/form/script_manager.js", | "public/js/frappe/form/script_manager.js", | ||||
"public/js/frappe/form/grid.js", | "public/js/frappe/form/grid.js", | ||||
@@ -30,6 +30,42 @@ | |||||
.form-message { | .form-message { | ||||
padding: 15px; | 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 { | .form-dashboard { | ||||
border-bottom: 1px solid #EBEFF2; | border-bottom: 1px solid #EBEFF2; | ||||
} | } | ||||
@@ -48,6 +48,10 @@ | |||||
.indicator-right.darkgrey::after { | .indicator-right.darkgrey::after { | ||||
background: #b8c2cc; | background: #b8c2cc; | ||||
} | } | ||||
.indicator.black::before, | |||||
.indicator-right.black::after { | |||||
background: #36414C; | |||||
} | |||||
.indicator.yellow::before, | .indicator.yellow::before, | ||||
.indicator-right.yellow::after { | .indicator-right.yellow::after { | ||||
background: #FEEF72; | background: #FEEF72; | ||||
@@ -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 = $('<div class="document-flow-wrapper hidden"></div>').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(); | |||||
} | |||||
}); |
@@ -110,13 +110,13 @@ frappe.ui.form.LinkedWith = Class.extend({ | |||||
get_linked_docs: function() { | get_linked_docs: function() { | ||||
var me = this; | var me = this; | ||||
return frappe.call({ | return frappe.call({ | ||||
method:"frappe.desk.form.linked_with.get_linked_docs", | method:"frappe.desk.form.linked_with.get_linked_docs", | ||||
args: { | args: { | ||||
doctype: me.frm.doctype, | doctype: me.frm.doctype, | ||||
name: me.frm.docname, | name: me.frm.docname, | ||||
linkinfo: me.frm.__linked_doctypes | |||||
linkinfo: me.frm.__linked_doctypes, | |||||
for_doctype: me.for_doctype | |||||
}, | }, | ||||
callback: function(r) { | callback: function(r) { | ||||
var parent = me.dialog.fields_dict.list.$wrapper.empty(); | var parent = me.dialog.fields_dict.list.$wrapper.empty(); | ||||
@@ -0,0 +1,12 @@ | |||||
<div class="document-flow"> | |||||
{% for dt in doctypes %} | |||||
<span class="document-flow-link-wrapper"> | |||||
<a data-doctype="{{ dt }}" | |||||
class="document-flow-link {% if (dt===frm.doctype) { %} strong disabled {% } %} " | |||||
style="color: inherit;"> | |||||
<span class="indicator {% if (dt===frm.doctype) { %} blue {% } else { %} darkgrey {% } %}"></span><br> | |||||
<span class="document-flow-link-label">{{ __(dt) }}</span> | |||||
</a> | |||||
</span> | |||||
{% endfor %} | |||||
</div> |
@@ -90,7 +90,6 @@ frappe.ui.Page = Class.extend({ | |||||
this.page_form = $('<div class="page-form row hide"></div>').prependTo(this.main); | this.page_form = $('<div class="page-form row hide"></div>').prependTo(this.main); | ||||
this.inner_toolbar = $('<div class="form-inner-toolbar hide"></div>').prependTo(this.main); | this.inner_toolbar = $('<div class="form-inner-toolbar hide"></div>').prependTo(this.main); | ||||
this.icon_group = this.page_actions.find(".page-icon-group"); | this.icon_group = this.page_actions.find(".page-icon-group"); | ||||
}, | }, | ||||
set_indicator: function(label, color) { | set_indicator: function(label, color) { | ||||
@@ -409,5 +408,15 @@ frappe.ui.Page = Class.extend({ | |||||
this.views[name].toggle(true); | this.views[name].toggle(true); | ||||
this.wrapper.trigger('view-change'); | this.wrapper.trigger('view-change'); | ||||
}, | |||||
}); | |||||
frappe.ui.scroll = function(element, animate, additional_offset) { | |||||
var header_offset = $(".navbar").height() + $(".page-head").height(); | |||||
var top = $(element).offset().top - header_offset - cint(additional_offset); | |||||
if (animate) { | |||||
$("html, body").animate({ scrollTop: top }); | |||||
} else { | |||||
$(window).scrollTop(top); | |||||
} | } | ||||
}); | |||||
} |
@@ -253,6 +253,10 @@ _f.Frm.prototype.setup_std_layout = function() { | |||||
this.fields_dict = this.layout.fields_dict; | this.fields_dict = this.layout.fields_dict; | ||||
this.fields = this.layout.fields_list; | this.fields = this.layout.fields_list; | ||||
this.document_flow = new frappe.ui.form.DocumentFlow({ | |||||
frm: this | |||||
}); | |||||
this.dashboard = new frappe.ui.form.Dashboard({ | this.dashboard = new frappe.ui.form.Dashboard({ | ||||
frm: this, | frm: this, | ||||
}); | }); | ||||
@@ -341,6 +345,7 @@ _f.Frm.prototype.refresh_header = function(is_a_different_doc) { | |||||
this.toolbar.refresh(); | this.toolbar.refresh(); | ||||
} | } | ||||
this.document_flow.refresh(); | |||||
this.dashboard.reset(); | this.dashboard.reset(); | ||||
this.clear_custom_buttons(); | this.clear_custom_buttons(); | ||||
@@ -40,6 +40,51 @@ | |||||
padding: 15px; | padding: 15px; | ||||
} | } | ||||
.document-flow-wrapper { | |||||
padding: 40px 15px 30px; | |||||
font-size: @text-medium; | |||||
border-bottom: 1px solid @light-border-color; | |||||
.document-flow { | |||||
display: inline-block; | |||||
position: relative; | |||||
left: 50%; | |||||
transform: translateX(-50%); | |||||
.document-flow-link-wrapper:not(:last-child) { | |||||
border-top: 1px solid @indicator-darkgrey; | |||||
padding-right: 60px; | |||||
display: inline-block; | |||||
margin-right: -4px; | |||||
} | |||||
.document-flow-link { | |||||
margin-top: -10px; | |||||
display: inline-block; | |||||
} | |||||
.document-flow-link:not(.disabled):hover, | |||||
.document-flow-link:not(.disabled):focus, | |||||
.document-flow-link:not(.disabled):active { | |||||
.document-flow-link-label { | |||||
text-decoration: underline; | |||||
} | |||||
} | |||||
.document-flow-link-label { | |||||
display: inline-block; | |||||
margin-left: -50%; | |||||
margin-top: 5px; | |||||
} | |||||
} | |||||
} | |||||
@media(max-width: @screen-xs) { | |||||
.document-flow-wrapper { | |||||
display: none; | |||||
} | |||||
} | |||||
.form-dashboard { | .form-dashboard { | ||||
border-bottom: 1px solid @light-border-color; | border-bottom: 1px solid @light-border-color; | ||||
} | } | ||||
@@ -55,6 +55,11 @@ | |||||
background: @indicator-darkgrey; | background: @indicator-darkgrey; | ||||
} | } | ||||
.indicator.black::before, | |||||
.indicator-right.black::after { | |||||
background: @text-color; | |||||
} | |||||
.indicator.yellow::before, | .indicator.yellow::before, | ||||
.indicator-right.yellow::after { | .indicator-right.yellow::after { | ||||
background: @indicator-yellow; | background: @indicator-yellow; | ||||