diff --git a/frappe/desk/search.py b/frappe/desk/search.py index 0bb6ac0a0b..734da033a3 100644 --- a/frappe/desk/search.py +++ b/frappe/desk/search.py @@ -3,7 +3,7 @@ # Search from __future__ import unicode_literals -import frappe +import frappe, json from frappe.utils import cstr, unique # this is called by the Link Field @@ -16,7 +16,7 @@ def search_link(doctype, txt, query=None, filters=None, page_len=20, searchfield # this is called by the search box @frappe.whitelist() def search_widget(doctype, txt, query=None, searchfield=None, start=0, - page_len=10, filters=None, as_dict=False): + page_len=10, filters=None, filter_fields=None, as_dict=False): if isinstance(filters, basestring): import json filters = json.loads(filters) @@ -76,20 +76,24 @@ def search_widget(doctype, txt, query=None, searchfield=None, start=0, if meta.get("fields", {"fieldname":"disabled", "fieldtype":"Check"}): filters.append([doctype, "disabled", "!=", 1]) + # format a list of fields combining search fields and filter fields fields = get_std_fields_list(meta, searchfield or "name") + if filter_fields: + fields = list(set(fields + json.loads(filter_fields))) + formatted_fields = ['`tab%s`.`%s`' % (meta.name, f.strip()) for f in fields] # find relevance as location of search term from the beginning of string `name`. used for sorting results. - fields.append("""locate("{_txt}", `tab{doctype}`.`name`) as `_relevance`""".format( + formatted_fields.append("""locate("{_txt}", `tab{doctype}`.`name`) as `_relevance`""".format( _txt=frappe.db.escape((txt or "").replace("%", "")), doctype=frappe.db.escape(doctype))) - + # In order_by, `idx` gets second priority, because it stores link count from frappe.model.db_query import get_order_by order_by_based_on_meta = get_order_by(doctype, meta) order_by = "if(_relevance, _relevance, 99999), idx desc, {0}".format(order_by_based_on_meta) - + values = frappe.get_list(doctype, - filters=filters, fields=fields, + filters=filters, fields=formatted_fields, or_filters = or_filters, limit_start = start, limit_page_length=page_len, order_by=order_by, @@ -97,6 +101,7 @@ def search_widget(doctype, txt, query=None, searchfield=None, start=0, as_list=not as_dict) # remove _relevance from results + frappe.response["fields"] = fields frappe.response["values"] = [r[:-1] for r in values] def get_std_fields_list(meta, key): @@ -107,7 +112,7 @@ def get_std_fields_list(meta, key): if not key in sflist: sflist = sflist + [key] - return ['`tab%s`.`%s`' % (meta.name, f.strip()) for f in sflist] + return sflist def build_for_autosuggest(res): results = [] diff --git a/frappe/model/mapper.py b/frappe/model/mapper.py index d8384eee09..72a9a77e3c 100644 --- a/frappe/model/mapper.py +++ b/frappe/model/mapper.py @@ -25,16 +25,21 @@ def make_mapped_doc(method, source_name, selected_children=None): return method(source_name) +@frappe.whitelist() +def map_docs(method, source_names, target_doc): + '''Returns the mapped document calling the given mapper method + with each of the given source docs on the target doc''' + method = frappe.get_attr(method) + if method not in frappe.whitelisted: + raise frappe.PermissionError + + for src in json.loads(source_names): + target_doc = method(src, target_doc) + return target_doc def get_mapped_doc(from_doctype, from_docname, table_maps, target_doc=None, postprocess=None, ignore_permissions=False, ignore_child_tables=False): - source_doc = frappe.get_doc(from_doctype, from_docname) - - if not ignore_permissions: - if not source_doc.has_permission("read"): - source_doc.raise_no_permission_to("read") - # main if not target_doc: target_doc = frappe.new_doc(table_maps[from_doctype]["doctype"]) @@ -44,6 +49,12 @@ def get_mapped_doc(from_doctype, from_docname, table_maps, target_doc=None, if not ignore_permissions and not target_doc.has_permission("create"): target_doc.raise_no_permission_to("create") + source_doc = frappe.get_doc(from_doctype, from_docname) + + if not ignore_permissions: + if not source_doc.has_permission("read"): + source_doc.raise_no_permission_to("read") + map_doc(source_doc, target_doc, table_maps[source_doc.doctype]) row_exists_for_parentfield = {} diff --git a/frappe/public/build.json b/frappe/public/build.json index 11bd5ea34e..598b964574 100755 --- a/frappe/public/build.json +++ b/frappe/public/build.json @@ -30,6 +30,7 @@ "public/js/frappe/ui/field_group.js", "public/js/frappe/form/control.js", "public/js/frappe/form/link_selector.js", + "public/js/frappe/form/multi_select_dialog.js", "public/js/frappe/ui/dialog.js" ], "css/desk.min.css": [ @@ -104,6 +105,7 @@ "public/js/frappe/ui/field_group.js", "public/js/frappe/form/control.js", "public/js/frappe/form/link_selector.js", + "public/js/frappe/form/multi_select_dialog.js", "public/js/frappe/ui/dialog.js", "public/js/frappe/ui/app_icon.js", diff --git a/frappe/public/css/desk.css b/frappe/public/css/desk.css index 76d352a6b9..302fe38647 100644 --- a/frappe/public/css/desk.css +++ b/frappe/public/css/desk.css @@ -978,6 +978,13 @@ input[type="checkbox"]:checked:before { font-size: 13px; color: #3b99fc; } +.multiselect-empty-state { + min-height: 300px; + display: flex; + align-items: center; + justify-content: center; + height: 100%; +} @-moz-document url-prefix() { input[type="checkbox"] { visibility: visible; diff --git a/frappe/public/js/frappe/form/multi_select_dialog.js b/frappe/public/js/frappe/form/multi_select_dialog.js new file mode 100644 index 0000000000..dd4251bd1f --- /dev/null +++ b/frappe/public/js/frappe/form/multi_select_dialog.js @@ -0,0 +1,208 @@ +// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +// MIT License. See license.txt + +frappe.ui.form.MultiSelectDialog = Class.extend({ + init: function(opts) { + /* Options: doctype, target, setters, get_query, action */ + $.extend(this, opts); + + var me = this; + if(this.doctype!="[Select]") { + frappe.model.with_doctype(this.doctype, function(r) { + me.make(); + }); + } else { + this.make(); + } + }, + make: function() { + let me = this; + + let fields = []; + let count = 0; + if(!this.date_field) { + this.date_field = "transaction_date"; + } + Object.keys(this.setters).forEach(function(setter) { + fields.push({ + fieldtype: me.target.fields_dict[setter].df.fieldtype, + label: me.target.fields_dict[setter].df.label, + fieldname: setter, + options: me.target.fields_dict[setter].df.options, + default: me.setters[setter] + }); + if (count++ < Object.keys(me.setters).length - 1) { + fields.push({fieldtype: "Column Break"}); + } + }); + + fields = fields.concat([ + { fieldtype: "Section Break" }, + { fieldtype: "HTML", fieldname: "results_area" }, + { fieldtype: "Button", fieldname: "make_new", label: __("Make a new " + me.doctype) } + ]); + + let doctype_plural = !this.doctype.endsWith('y') ? this.doctype + 's' + : this.doctype.slice(0, -1) + 'ies'; + + this.dialog = new frappe.ui.Dialog({ + title: __("Select {0}", [(this.doctype=='[Select]') ? __("value") : __(doctype_plural)]), + fields: fields, + primary_action_label: __("Get Items"), + primary_action: function() { + me.action(me.get_checked_values(), me.args); + } + }); + + this.$parent = $(this.dialog.body); + this.$wrapper = this.dialog.fields_dict.results_area.$wrapper.append(`
`); + this.$results = this.$wrapper.find('.results'); + this.$make_new_btn = this.dialog.fields_dict.make_new.$wrapper; + + this.$placeholder = $(`No ${this.doctype} found
+ + +