diff --git a/frappe/desk/doctype/kanban_board/kanban_board.py b/frappe/desk/doctype/kanban_board/kanban_board.py index cc544c9f68..43bb5a41a8 100644 --- a/frappe/desk/doctype/kanban_board/kanban_board.py +++ b/frappe/desk/doctype/kanban_board/kanban_board.py @@ -8,6 +8,7 @@ import json from frappe import _ from frappe.model.document import Document from six import iteritems +from frappe.custom.doctype.custom_field.custom_field import create_custom_field class KanbanBoard(Document): @@ -36,6 +37,14 @@ def has_permission(doc, ptype, user): return False +@frappe.whitelist() +def get_kanban_boards(doctype): + '''Get Kanban Boards for doctype to show in List View''' + return frappe.get_list('Kanban Board', + fields=['name', 'filters', 'reference_doctype', 'private'], + filters={ 'reference_doctype': doctype } + ) + @frappe.whitelist() def add_column(board_name, column_title): '''Adds new column to Kanban Board''' @@ -120,6 +129,14 @@ def quick_kanban_board(doctype, board_name, field_name, project=None): '''Create new KanbanBoard quickly with default options''' doc = frappe.new_doc('Kanban Board') + if field_name == 'kanban_column': + create_custom_field(doctype, { + 'label': 'Kanban Column', + 'fieldname': 'kanban_column', + 'fieldtype': 'Select', + 'hidden': 1 + }) + meta = frappe.get_meta(doctype) options = '' diff --git a/frappe/desk/form/meta.py b/frappe/desk/form/meta.py index b6f95ec981..3db46aeda0 100644 --- a/frappe/desk/form/meta.py +++ b/frappe/desk/form/meta.py @@ -51,7 +51,7 @@ class FormMeta(Meta): for k in ("__js", "__css", "__list_js", "__calendar_js", "__map_js", "__linked_with", "__messages", "__print_formats", "__workflow_docs", "__form_grid_templates", "__listview_template", "__tree_js", - "__dashboard", "__kanban_boards", "__kanban_column_fields", '__templates', + "__dashboard", "__kanban_column_fields", '__templates', '__custom_js'): d[k] = self.get(k) @@ -185,14 +185,8 @@ class FormMeta(Meta): self.set('__dashboard', self.get_dashboard_data()) def load_kanban_meta(self): - self.load_kanban_boards() self.load_kanban_column_fields() - def load_kanban_boards(self): - kanban_boards = frappe.get_list( - 'Kanban Board', fields=['name', 'filters', 'reference_doctype', 'private'], filters={'reference_doctype': self.name}) - self.set("__kanban_boards", kanban_boards, as_value=True) - def load_kanban_column_fields(self): values = frappe.get_list( 'Kanban Board', fields=['field_name'], diff --git a/frappe/public/js/frappe/list/list_sidebar.js b/frappe/public/js/frappe/list/list_sidebar.js index e2717735dc..ba03fd18de 100644 --- a/frappe/public/js/frappe/list/list_sidebar.js +++ b/frappe/public/js/frappe/list/list_sidebar.js @@ -9,14 +9,15 @@ frappe.provide('frappe.views'); // parent // set_filter = function called on click -frappe.views.ListSidebar = Class.extend({ - init: function(opts) { +frappe.views.ListSidebar = class ListSidebar { + constructor(opts) { $.extend(this, opts); this.make(); this.get_stats(); this.cat_tags = []; - }, - make: function() { + } + + make() { var sidebar_content = frappe.render_template("list_sidebar", { doctype: this.doctype }); this.sidebar = $('') @@ -36,8 +37,9 @@ frappe.views.ListSidebar = Class.extend({ if (limits.upgrade_url && limits.expiry && !frappe.flags.upgrade_dismissed) { this.setup_upgrade_box(); } - }, - setup_views: function() { + } + + setup_views() { var show_list_link = false; if (frappe.views.calendar[this.doctype]) { @@ -85,8 +87,9 @@ frappe.views.ListSidebar = Class.extend({ if (show_list_link) { this.sidebar.find('.list-link[data-view="List"]').removeClass('hide'); } - }, - setup_reports: function() { + } + + setup_reports() { // add reports linked to this doctype to the dropdown var me = this; var added = []; @@ -124,167 +127,22 @@ frappe.views.ListSidebar = Class.extend({ // from specially tagged reports add_reports(frappe.boot.user.all_reports || []); - }, + } - setup_list_filter: function() { + setup_list_filter() { this.list_filter = new ListFilter({ wrapper: this.page.sidebar.find('.list-filters'), doctype: this.doctype, list_view: this.list_view }); - }, + } - setup_kanban_boards: function() { - // add kanban boards linked to this doctype to the dropdown - var me = this; - var $dropdown = this.page.sidebar.find('.kanban-dropdown'); - var divider = false; + setup_kanban_boards() { + const $dropdown = this.page.sidebar.find('.kanban-dropdown'); + frappe.views.KanbanView.setup_dropdown_in_sidebar(this.doctype, $dropdown); + } - var meta = frappe.get_meta(this.doctype); - var boards = meta && meta.__kanban_boards; - if (!boards) return; - - boards.forEach(function(board) { - var route = ["List", board.reference_doctype, "Kanban", board.name].join('/'); - if (!divider) { - me.get_divider().appendTo($dropdown); - divider = true; - } - $(`
  • - ${__(board.name)} - ${board.private ? '' : ''} -
  • `).appendTo($dropdown); - }); - - $dropdown.find('.new-kanban-board').click(function() { - // frappe.new_doc('Kanban Board', {reference_doctype: me.doctype}); - var select_fields = frappe.get_meta(me.doctype) - .fields.filter(function(df) { - return df.fieldtype === 'Select' && - df.fieldname !== 'kanban_column'; - }); - - var fields = [{ - fieldtype: 'Data', - fieldname: 'board_name', - label: __('Kanban Board Name'), - reqd: 1 - }]; - - if(me.doctype === 'Task') { - fields.push({ - fieldtype: 'Link', - fieldname: 'project', - label: __('Project'), - options: 'Project' - }); - } - - if(select_fields.length > 0) { - fields = fields.concat([{ - fieldtype: 'Select', - fieldname: 'field_name', - label: __('Columns based on'), - options: select_fields.map(df => df.label).join('\n'), - default: select_fields[0], - depends_on: 'eval:doc.custom_column===0' - }, - { - fieldtype: 'Check', - fieldname: 'custom_column', - label: __('Custom Column'), - default: 0 - }]); - } - - if(['Note', 'ToDo'].includes(me.doctype)) { - fields[0].description = __('This Kanban Board will be private'); - } - - var d = new frappe.ui.Dialog({ - title: __('New Kanban Board'), - fields: fields, - primary_action_label: __('Save'), - primary_action: function(values) { - - var custom_column = values.custom_column !== undefined ? - values.custom_column : 1; - var field_name; - if (custom_column) { - field_name = 'kanban_column'; - } else { - - if (!values.field_name) { - frappe.throw(__('Please select Columns Based On')); - } - var field_name = - select_fields - .find(df => df.label === values.field_name) - .fieldname; - } - - me.add_custom_column_field(custom_column) - .then(function(custom_column) { - return me.make_kanban_board(values.board_name, field_name, values.project); - }) - .then(function() { - d.hide(); - }, function(err) { - frappe.msgprint(err); - }); - } - }); - d.show(); - }); - }, - add_custom_column_field: function(flag) { - var me = this; - return new Promise(function(resolve, reject) { - if (!flag) resolve(false); - frappe.call({ - method: 'frappe.custom.doctype.custom_field.custom_field.add_custom_field', - args: { - doctype: me.doctype, - df: { - label: 'Kanban Column', - fieldname: 'kanban_column', - fieldtype: 'Select', - hidden: 1 - } - } - }).success(function() { - resolve(true); - }).error(function(err) { - reject(err); - }); - }); - }, - make_kanban_board: function(board_name, field_name, project) { - var me = this; - return frappe.call({ - method: 'frappe.desk.doctype.kanban_board.kanban_board.quick_kanban_board', - args: { - doctype: me.doctype, - board_name, - field_name, - project - }, - callback: function(r) { - var kb = r.message; - if (kb.filters) { - frappe.provide('frappe.kanban_filters'); - frappe.kanban_filters[kb.kanban_board_name] = kb.filters; - } - frappe.set_route( - 'List', - me.doctype, - 'Kanban', - kb.kanban_board_name - ); - } - }); - }, - setup_calendar_view: function() { + setup_calendar_view() { const doctype = this.doctype; frappe.db.get_list('Calendar View', { @@ -322,8 +180,9 @@ frappe.views.ListSidebar = Class.extend({ $link_calendar.removeClass('hide'); $link_calendar.html(dropdown_html); }); - }, - setup_email_inbox: function() { + } + + setup_email_inbox() { // get active email account for the user and add in dropdown if (this.doctype != "Communication") return; @@ -352,13 +211,15 @@ frappe.views.ListSidebar = Class.extend({ $dropdown.find('.new-email-account').click(function() { frappe.new_doc("Email Account"); }); - }, - setup_assigned_to_me: function() { + } + + setup_assigned_to_me() { this.page.sidebar.find(".assigned-to-me a").on("click", () => { this.list_view.filter_area.add(this.list_view.doctype, "_assign", "like", `%${frappe.session.user}%`); }); - }, - setup_upgrade_box: function() { + } + + setup_upgrade_box() { let upgrade_list = $(``).appendTo(this.sidebar); // Show Renew/Upgrade button, @@ -386,11 +247,13 @@ frappe.views.ListSidebar = Class.extend({ frappe.flags.upgrade_dismissed = 1; }); } - }, - get_cat_tags: function() { + } + + get_cat_tags() { return this.cat_tags; - }, - get_stats: function() { + } + + get_stats() { var me = this; frappe.call({ method: 'frappe.desk.reportview.get_sidebar_stats', @@ -425,8 +288,9 @@ frappe.views.ListSidebar = Class.extend({ } } }); - }, - render_stat: function(field, stat, tags) { + } + + render_stat(field, stat, tags) { var me = this; var sum = 0; var stats = []; @@ -485,8 +349,9 @@ frappe.views.ListSidebar = Class.extend({ }); }) .insertBefore(this.sidebar.find(".close-sidebar-button")); - }, - set_fieldtype: function(df) { + } + + set_fieldtype(df) { // scrub if (df.fieldname == "docstatus") { @@ -511,12 +376,14 @@ frappe.views.ListSidebar = Class.extend({ if (df.fieldtype === "Data" && (df.options || "").toLowerCase() === "email") { df.options = null; } - }, - reload_stats: function() { + } + + reload_stats() { this.sidebar.find(".sidebar-stat").remove(); this.get_stats(); - }, - get_divider: function() { + } + + get_divider() { return $(''); } -}); \ No newline at end of file +}; diff --git a/frappe/public/js/frappe/views/kanban/kanban_view.js b/frappe/public/js/frappe/views/kanban/kanban_view.js index dcc69e8db1..7b163baf97 100644 --- a/frappe/public/js/frappe/views/kanban/kanban_view.js +++ b/frappe/public/js/frappe/views/kanban/kanban_view.js @@ -72,7 +72,7 @@ frappe.views.KanbanView = class KanbanView extends frappe.views.ListView { render() { const board_name = this.board_name; - if(this.kanban && board_name === this.kanban.board_name) { + if (this.kanban && board_name === this.kanban.board_name) { this.kanban.update(this.data); return; } @@ -95,14 +95,14 @@ frappe.views.KanbanView = class KanbanView extends frappe.views.ListView { var title_field = null; var quick_entry = false; - if(this.meta.title_field) { + if (this.meta.title_field) { title_field = frappe.meta.get_field(this.doctype, this.meta.title_field); } - this.meta.fields.forEach(function(df) { + this.meta.fields.forEach((df) => { const is_valid_field = in_list(['Data', 'Text', 'Small Text', 'Text Editor'], df.fieldtype) - && !df.hidden; + && !df.hidden; if (is_valid_field && !title_field) { // can be mapped to textarea @@ -111,15 +111,13 @@ frappe.views.KanbanView = class KanbanView extends frappe.views.ListView { }); // quick entry - var mandatory = meta.fields.filter(function(df) { - return df.reqd && !doc[df.fieldname]; - }); + var mandatory = meta.fields.filter((df) => df.reqd && !doc[df.fieldname]); - if(mandatory.some(df => df.fieldtype === 'Table') || mandatory.length > 1) { + if (mandatory.some(df => df.fieldtype === 'Table') || mandatory.length > 1) { quick_entry = true; } - if(!title_field) { + if (!title_field) { title_field = frappe.meta.get_field(this.doctype, 'name'); } @@ -136,3 +134,135 @@ frappe.views.KanbanView = class KanbanView extends frappe.views.ListView { ]; } }; + + +frappe.views.KanbanView.setup_dropdown_in_sidebar = function(doctype, $dropdown) { + // get kanban boards and append to dropdown + get_kanban_boards() + .then((kanban_boards) => { + if (!kanban_boards) return; + + $('').appendTo($dropdown); + + kanban_boards.forEach(board => { + const route = ['List', board.reference_doctype, 'Kanban', board.name].join('/'); + + $(`
  • + + ${__(board.name)} + ${board.private ? '' : ''} + +
  • + `).appendTo($dropdown); + }); + }); + + $dropdown.on('click', '.new-kanban-board', () => { + const dialog = new_kanban_dialog(); + dialog.show(); + }); + + function make_kanban_board(board_name, field_name, project) { + return frappe.call({ + method: 'frappe.desk.doctype.kanban_board.kanban_board.quick_kanban_board', + args: { + doctype, + board_name, + field_name, + project + }, + callback: function(r) { + var kb = r.message; + if (kb.filters) { + frappe.provide('frappe.kanban_filters'); + frappe.kanban_filters[kb.kanban_board_name] = kb.filters; + } + frappe.set_route('List', doctype, 'Kanban', kb.kanban_board_name); + } + }); + } + + let dialog = null; + + function new_kanban_dialog() { + if (dialog) return dialog; + + const fields = get_fields_for_dialog(); + + dialog = new frappe.ui.Dialog({ + title: __('New Kanban Board'), + fields: fields, + + primary_action_label: __('Save'), + primary_action(values) { + const custom_column = + values.custom_column !== undefined ? + values.custom_column : 1; + + let field_name = custom_column ? 'kanban_column' : values.field_name; + + make_kanban_board(values.board_name, field_name, values.project) + .then(() => dialog.hide(), (err) => frappe.msgprint(err)); + } + }); + return dialog; + } + + function get_fields_for_dialog() { + + let fields = [{ + fieldtype: 'Data', + fieldname: 'board_name', + label: __('Kanban Board Name'), + reqd: 1, + description: ['Note', 'ToDo'].includes(doctype) ? + __('This Kanban Board will be private') : '' + }]; + + if (doctype === 'Task') { + fields.push({ + fieldtype: 'Link', + fieldname: 'project', + label: __('Project'), + options: 'Project' + }); + } + + const select_fields = + frappe.get_meta(doctype).fields + .filter(df => { + return df.fieldtype === 'Select' && + df.fieldname !== 'kanban_column'; + }); + + if (select_fields.length > 0) { + fields = fields.concat([{ + fieldtype: 'Select', + fieldname: 'field_name', + label: __('Columns based on'), + options: select_fields.map(df => ({label: df.label, value: df.fieldname})), + default: select_fields[0], + depends_on: 'eval:doc.custom_column===0', + reqd: 1 + }, + + { + fieldtype: 'Check', + fieldname: 'custom_column', + label: __('Custom Column'), + default: 0, + onchange() { + const value = this.get_value(); + this.layout.set_df_property('field_name', 'reqd', !value); + } + }]); + } + + return fields; + } + + function get_kanban_boards() { + return frappe.call('frappe.desk.doctype.kanban_board.kanban_board.get_kanban_boards', { doctype }) + .then(r => r.message); + } +};