diff --git a/frappe/__init__.py b/frappe/__init__.py index 7778068fc0..e03650aa93 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -1468,4 +1468,8 @@ def get_version(doctype, name, limit = None, head = False, raise_err = True): @whitelist(allow_guest = True) def ping(): - return "pong" \ No newline at end of file + return "pong" + +def parse_json(val): + from frappe.utils import parse_json + return parse_json(val) diff --git a/frappe/async.py b/frappe/async.py index 15ba8a4af6..6af91d9301 100644 --- a/frappe/async.py +++ b/frappe/async.py @@ -47,8 +47,8 @@ def remove_old_task_logs(): def is_file_old(file_path): return ((time.time() - os.stat(file_path).st_mtime) > TASK_LOG_MAX_AGE) -def publish_progress(percent, title=None, doctype=None, docname=None): - publish_realtime('progress', {'percent': percent, 'title': title}, +def publish_progress(percent, title=None, doctype=None, docname=None, description=None): + publish_realtime('progress', {'percent': percent, 'title': title, 'description': description}, user=frappe.session.user, doctype=doctype, docname=docname) def publish_realtime(event=None, message=None, room=None, @@ -200,6 +200,6 @@ def get_task_progress_room(task_id): # frappe.chat def get_chat_room(room): room = ''.join([frappe.local.site, ":room:", room]) - + return room # end frappe.chat room \ No newline at end of file diff --git a/frappe/core/doctype/data_import/data_import.js b/frappe/core/doctype/data_import/data_import.js index 9762663957..02b5adc54e 100644 --- a/frappe/core/doctype/data_import/data_import.js +++ b/frappe/core/doctype/data_import/data_import.js @@ -33,10 +33,13 @@ frappe.ui.form.on('Data Import', { frm.disable_save(); frm.dashboard.clear_headline(); if (frm.doc.reference_doctype && !frm.doc.import_file) { - frm.dashboard.add_comment(__('Please attach a file to import')); + frm.page.set_indicator(__('Please attach a file to import'), 'orange'); } else { if (frm.doc.import_status) { - frm.dashboard.add_comment(frm.doc.import_status); + const listview_settings = frappe.listview_settings['Data Import']; + const indicator = listview_settings.get_indicator(frm.doc); + + frm.page.set_indicator(indicator[0], indicator[1]); if (frm.doc.import_status==="In Progress") { frm.dashboard.add_progress("Data Import Progress", "0"); diff --git a/frappe/desk/doctype/bulk_update/bulk_update.js b/frappe/desk/doctype/bulk_update/bulk_update.js index cfee158ac7..0526fc0f5d 100644 --- a/frappe/desk/doctype/bulk_update/bulk_update.js +++ b/frappe/desk/doctype/bulk_update/bulk_update.js @@ -4,10 +4,9 @@ frappe.ui.form.on('Bulk Update', { refresh: function(frm) { frm.page.set_primary_action(__('Update'), function() { - if(!frm.doc.update_value){ + if (!frm.doc.update_value) { frappe.throw(__('Field "value" is mandatory. Please specify value to be updated')); - } - else{ + } else { frappe.call({ method: 'frappe.desk.doctype.bulk_update.bulk_update.update', args: { @@ -17,13 +16,19 @@ frappe.ui.form.on('Bulk Update', { condition: frm.doc.condition, limit: frm.doc.limit }, - callback: function() { - frappe.hide_progress(); + }).then(r => { + let failed = r.message; + if (!failed) failed = []; + + if (failed.length && !r._server_messages) { + frappe.throw(__('Cannot update {0}', [failed.map(f => f.bold ? f.bold(): f).join(', ')])); } + frappe.hide_progress(); }); } }); }, + document_type: function(frm) { // set field options if(!frm.doc.document_type) return; diff --git a/frappe/desk/doctype/bulk_update/bulk_update.py b/frappe/desk/doctype/bulk_update/bulk_update.py index ae6c73d8e0..c92d892d8d 100644 --- a/frappe/desk/doctype/bulk_update/bulk_update.py +++ b/frappe/desk/doctype/bulk_update/bulk_update.py @@ -22,23 +22,47 @@ def update(doctype, field, value, condition='', limit=500): if ';' in condition: frappe.throw(_('; not allowed in condition')) - items = frappe.db.sql_list('''select name from `tab{0}`{1} limit 0, {2}'''.format(doctype, - condition, limit), debug=1) - n = len(items) + docnames = frappe.db.sql_list( + '''select name from `tab{0}`{1} limit 0, {2}'''.format(doctype, condition, limit) + ) + data = {} + data[field] = value + return submit_cancel_or_update_docs(doctype, docnames, 'update', data) - for i, d in enumerate(items): +@frappe.whitelist() +def submit_cancel_or_update_docs(doctype, docnames, action='submit', data=None): + docnames = frappe.parse_json(docnames) + + if data: + data = frappe.parse_json(data) + + failed = [] + + for i, d in enumerate(docnames, 1): doc = frappe.get_doc(doctype, d) - doc.set(field, value) - try: - doc.save() - except Exception as e: - frappe.msgprint(_("Validation failed for {0}").format(frappe.bold(doc.name))) - raise e + if action == 'submit': + doc.submit() + message = _('Submiting {0}').format(doctype) + elif action == 'cancel': + doc.cancel() + message = _('Cancelling {0}').format(doctype) + elif action == 'update': + doc.update(data) + doc.save() + message = _('Updating {0}').format(doctype) - frappe.publish_progress(float(i)*100/n, - title = _('Updating Records'), doctype='Bulk Update', docname='Bulk Update') + show_progress(docnames, message, i, d) - # clear messages - frappe.local.message_log = [] - frappe.msgprint(_('{0} records updated').format(n), title=_('Success'), indicator='green') \ No newline at end of file + except Exception: + failed.append(d) + return failed + +def show_progress(docnames, message, i, description): + n = len(docnames) + if n >= 10: + frappe.publish_progress( + float(i) * 100 / n, + title = message, + description = description + ) diff --git a/frappe/desk/reportview.py b/frappe/desk/reportview.py index 3136107608..e9e994f0bb 100644 --- a/frappe/desk/reportview.py +++ b/frappe/desk/reportview.py @@ -224,8 +224,8 @@ def delete_items(): frappe.delete_doc(doctype, d) if len(il) >= 5: frappe.publish_realtime("progress", - dict(progress=[i+1, len(il)], title=_('Deleting {0}').format(doctype)), - user=frappe.session.user) + dict(progress=[i+1, len(il)], title=_('Deleting {0}').format(doctype), description=d), + user=frappe.session.user) except Exception: failed.append(d) diff --git a/frappe/public/js/frappe/form/layout.js b/frappe/public/js/frappe/form/layout.js index 9b559a644b..b56857d86a 100644 --- a/frappe/public/js/frappe/form/layout.js +++ b/frappe/public/js/frappe/form/layout.js @@ -119,19 +119,27 @@ frappe.ui.form.Layout = Class.extend({ }); }, - make_field: function(df, colspan, render = false) { + replace_field: function(fieldname, df, render) { + df.fieldname = fieldname; // change of fieldname is avoided + if (this.fields_dict[fieldname] && this.fields_dict[fieldname].df) { + const fieldobj = this.init_field(df, render); + this.fields_dict[fieldname].$wrapper.remove(); + this.fields_list.splice(this.fields_dict[fieldname], 1, fieldobj); + this.fields_dict[fieldname] = fieldobj; + if (this.frm) { + fieldobj.perm = this.frm.perm; + } + this.section.fields_list.splice(this.section.fields_dict[fieldname], 1, fieldobj); + this.section.fields_dict[fieldname] = fieldobj; + this.refresh(); + } + }, + + make_field: function(df, colspan, render) { !this.section && this.make_section(); !this.column && this.make_column(); - var fieldobj = frappe.ui.form.make_control({ - df: df, - doctype: this.doctype, - parent: this.column.wrapper.get(0), - frm: this.frm, - render_input: render - }); - - fieldobj.layout = this; + const fieldobj = this.init_field(df, render); this.fields_list.push(fieldobj); this.fields_dict[df.fieldname] = fieldobj; if(this.frm) { @@ -141,6 +149,20 @@ frappe.ui.form.Layout = Class.extend({ this.section.fields_list.push(fieldobj); this.section.fields_dict[df.fieldname] = fieldobj; }, + + init_field: function(df, render = false) { + const fieldobj = frappe.ui.form.make_control({ + df: df, + doctype: this.doctype, + parent: this.column.wrapper.get(0), + frm: this.frm, + render_input: render + }); + + fieldobj.layout = this; + return fieldobj; + }, + make_page: function(df) { var me = this, head = $('
\ @@ -234,7 +256,7 @@ frappe.ui.form.Layout = Class.extend({ if(cnt % 2) { $this.addClass("shaded-section"); } - cnt ++; + cnt++; } }); }, @@ -414,11 +436,9 @@ frappe.ui.form.Layout = Class.extend({ } else { field.grid.grid_rows[0].toggle_view(true); } - } - else if(field.editor) { + } else if(field.editor) { field.editor.set_focus(); - } - else if(field.$input) { + } else if(field.$input) { field.$input.focus(); } }, @@ -632,11 +652,13 @@ frappe.ui.form.Column = Class.extend({ \
').appendTo(this.section.body) .find("form") - .on("submit", function() { return false; }); + .on("submit", function() { + return false; + }); - if(this.df.label) { - $('').appendTo(this.wrapper); + if (this.df.label) { + $('').appendTo(this.wrapper); } }, resize_all_columns: function() { diff --git a/frappe/public/js/frappe/list/base_list.js b/frappe/public/js/frappe/list/base_list.js index ebce0101c5..9abcb076d8 100644 --- a/frappe/public/js/frappe/list/base_list.js +++ b/frappe/public/js/frappe/list/base_list.js @@ -41,7 +41,6 @@ frappe.views.BaseList = class BaseList { this.method = 'frappe.desk.reportview.get'; this.can_create = frappe.model.can_create(this.doctype); - this.can_delete = frappe.model.can_delete(this.doctype); this.can_write = frappe.model.can_write(this.doctype); this.fields = []; @@ -146,34 +145,9 @@ frappe.views.BaseList = class BaseList { setup_page_head() { this.page.set_title(this.page_title); - this.set_menu_items(); this.set_breadcrumbs(); } - set_menu_items() { - this.page.clear_menu(); - const $secondary_action = this.page.set_secondary_action( - this.secondary_action.label, - this.secondary_action.action, - this.secondary_action.icon - ); - if (!this.secondary_action.icon) { - $secondary_action.addClass('hidden-xs'); - } else { - $secondary_action.addClass('visible-xs'); - } - - this.menu_items.map(item => { - if (item.condition && item.condition() === false) { - return; - } - const $item = this.page.add_menu_item(item.label, item.action, item.standard); - if (item.class) { - $item && $item.addClass(item.class); - } - }); - } - set_breadcrumbs() { frappe.breadcrumbs.add(this.meta.module, this.doctype); } diff --git a/frappe/public/js/frappe/list/bulk_operations.js b/frappe/public/js/frappe/list/bulk_operations.js new file mode 100644 index 0000000000..58b7d3b3ea --- /dev/null +++ b/frappe/public/js/frappe/list/bulk_operations.js @@ -0,0 +1,187 @@ +export default class BulkOperations { + constructor({ doctype }) { + if (!doctype) frappe.throw(__('Doctype required')); + this.doctype = doctype; + } + + print(docs) { + const print_settings = frappe.model.get_doc(':Print Settings', 'Print Settings'); + const allow_print_for_draft = cint(print_settings.allow_print_for_draft); + const is_submittable = frappe.model.is_submittable(this.doctype); + const allow_print_for_cancelled = cint(print_settings.allow_print_for_cancelled); + + const valid_docs = docs.filter(doc => { + return !is_submittable || doc.docstatus === 1 || + (allow_print_for_cancelled && doc.docstatus == 2) || + (allow_print_for_draft && doc.docstatus == 0) || + frappe.user.has_role('Administrator'); + }).map(doc => doc.name); + + const invalid_docs = docs.filter(doc => !valid_docs.includes(doc.name)); + + if (invalid_docs.length > 0) { + frappe.msgprint(__('You selected Draft or Cancelled documents')); + return; + } + + if (valid_docs.length > 0) { + const dialog = new frappe.ui.Dialog({ + title: __('Print Documents'), + fields: [{ + 'fieldtype': 'Check', + 'label': __('With Letterhead'), + 'fieldname': 'with_letterhead' + }, + { + 'fieldtype': 'Select', + 'label': __('Print Format'), + 'fieldname': 'print_sel', + options: frappe.meta.get_print_formats(this.doctype) + }] + }); + + dialog.set_primary_action(__('Print'), args => { + if (!args) return; + const default_print_format = frappe.get_meta(this.doctype).default_print_format; + const with_letterhead = args.with_letterhead ? 1 : 0; + const print_format = args.print_sel ? args.print_sel : default_print_format; + const json_string = JSON.stringify(valid_docs); + + const w = window.open('/api/method/frappe.utils.print_format.download_multi_pdf?' + + 'doctype=' + encodeURIComponent(this.doctype) + + '&name=' + encodeURIComponent(json_string) + + '&format=' + encodeURIComponent(print_format) + + '&no_letterhead=' + (with_letterhead ? '0' : '1')); + if (!w) { + frappe.msgprint(__('Please enable pop-ups')); + return; + } + }); + + dialog.show(); + } else { + frappe.msgprint(__('Select atleast 1 record for printing')); + } + } + + delete(docnames, done = null) { + frappe + .call({ + method: 'frappe.desk.reportview.delete_items', + freeze: true, + args: { + items: docnames, + doctype: this.doctype + } + }) + .then((r) => { + let failed = r.message; + if (!failed) failed = []; + + if (failed.length && !r._server_messages) { + frappe.throw(__('Cannot delete {0}', [failed.map(f => f.bold()).join(', ')])); + } + if (failed.length < docnames.length) { + frappe.utils.play_sound('delete'); + if (done) done(); + } + }); + } + + assign(docnames, done) { + if (docnames.length > 0) { + const dialog = new frappe.ui.form.AssignToDialog({ + obj: this, + method: 'frappe.desk.form.assign_to.add_multiple', + doctype: this.doctype, + docname: docnames, + bulk_assign: true, + re_assign: true, + callback: done + }); + dialog.clear(); + dialog.show(); + } else { + frappe.msgprint(__('Select records for assignment')); + } + } + + submit_or_cancel(docnames, action='submit', done=null) { + action = action.toLowerCase(); + frappe + .call({ + method: 'frappe.desk.doctype.bulk_update.bulk_update.submit_cancel_or_update_docs', + args: { + doctype: this.doctype, + action: action, + docnames: docnames + }, + }) + .then((r) => { + let failed = r.message; + if (!failed) failed = []; + + if (failed.length && !r._server_messages) { + frappe.throw(__('Cannot {0} {1}', [action, failed.map(f => f.bold()).join(', ')])); + } + if (failed.length < docnames.length) { + frappe.utils.play_sound(action); + if (done) done(); + } + }); + } + + edit(docnames, field_mappings, done) { + let field_options = Object.keys(field_mappings).sort(); + const dialog = new frappe.ui.Dialog({ + title: __('Edit'), + fields: [ + { + 'fieldtype': 'Select', 'options': field_options, + 'label': __('Field'), 'fieldname': 'field', 'reqd': 1, + 'onchange': () => { + const new_df = Object.assign({}, + field_mappings[dialog.get_value('field')]); + new_df.label = __('Value'); + new_df.reqd = 1; + delete new_df.depends_on; + dialog.replace_field('value', new_df); + } + }, + { + 'fieldtype': 'Data', + 'label': __('Value'), + 'fieldname': 'value', + 'reqd': 1 + } + ], + primary_action: ({ value }) => { + const fieldname = field_mappings[dialog.get_value('field')].fieldname; + + frappe.call({ + method: 'frappe.desk.doctype.bulk_update.bulk_update.submit_cancel_or_update_docs', + args: { + doctype: this.doctype, + docnames: docnames, + action: 'update', + data: { + [fieldname]: value + } + } + }).then(r => { + let failed = r.message || []; + + if (failed.length && !r._server_messages) { + frappe.throw(__('Cannot update {0}', [failed.map(f => f.bold ? f.bold() : f).join(', ')])); + } + done(); + dialog.hide(); + }); + }, + primary_action_label: __('Update') + }); + + dialog.refresh(); + dialog.show(); + } +} \ No newline at end of file diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js index fc2ea761df..147c368106 100644 --- a/frappe/public/js/frappe/list/list_view.js +++ b/frappe/public/js/frappe/list/list_view.js @@ -1,3 +1,5 @@ +import BulkOperations from "./bulk_operations"; + frappe.provide('frappe.views'); frappe.views.ListView = class ListView extends frappe.views.BaseList { @@ -55,10 +57,12 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { } // initialize with saved order by this.order_by = this.view_user_settings.order_by || 'modified desc'; - // buld menu items) + + // build menu items this.menu_items = this.menu_items.concat(this.get_menu_items()); this.freeze_on_refresh = true; + this.actions_menu_items = this.get_actions_menu_items(); this.patch_refresh_and_load_lib(); } @@ -68,6 +72,45 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { super.setup_page(); } + setup_page_head() { + super.setup_page_head(); + this.set_primary_action(); + this.set_menu_items(); + this.set_actions_menu_items(); + } + + set_menu_items() { + const $secondary_action = this.page.set_secondary_action( + this.secondary_action.label, + this.secondary_action.action, + this.secondary_action.icon + ); + if (!this.secondary_action.icon) { + $secondary_action.addClass('hidden-xs'); + } else { + $secondary_action.addClass('visible-xs'); + } + + this.menu_items.map(item => { + if (item.condition && item.condition() === false) { + return; + } + const $item = this.page.add_menu_item(item.label, item.action, item.standard); + if (item.class) { + $item && $item.addClass(item.class); + } + }); + } + + set_actions_menu_items() { + this.actions_menu_items.map(item => { + const $item = this.page.add_actions_menu_item(item.label, item.action, item.standard); + if (item.class) { + $item.addClass(item.class); + } + }); + } + set_fields() { let fields = [].concat( frappe.model.std_fields_list, @@ -103,10 +146,6 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { }, interval); } - setup_page_head() { - super.setup_page_head(); - this.set_primary_action(); - } set_primary_action() { if (this.can_create) { @@ -220,43 +259,23 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { }); } - // freeze(toggle) { - // if (this.view_name !== 'List') return; - - // this.$freeze.toggle(toggle); - // this.$result.toggle(!toggle); - - // const columns = this.columns; - // if (toggle) { - // if (this.$freeze.find('.freeze-row').length > 0) return; - - // const html = ` - // ${this.get_header_html()} - // ${Array.from(new Array(10)).map(loading_row).join('')} - // `; - // this.$freeze.html(html); - // } - - // function loading_row() { - // return ` - //
- //
- //
- // ${columns.slice(1).map(c => `
`).join('')} - //
- //
- //
- // `; - // } - // } - toggle_result_area() { super.toggle_result_area(); - this.toggle_delete_button( + this.toggle_actions_menu_button( this.$result.find('.list-row-check:checked').length > 0 ); } + toggle_actions_menu_button(toggle) { + if (toggle) { + this.page.show_actions_menu(); + this.page.clear_primary_action(); + } else { + this.page.hide_actions_menu(); + this.set_primary_action(); + } + } + before_render() { this.settings.before_render && this.settings.before_render(); frappe.model.user_settings.save(this.doctype, 'last_view', this.view_name); @@ -734,56 +753,13 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { this.$checkbox_actions.show(); this.$list_head_subject.hide(); } - - if (this.can_delete) { - this.toggle_delete_button(this.$checks.length > 0); - } - } - - toggle_delete_button(toggle) { - if (toggle) { - this.page.set_primary_action(__('Delete'), - () => this.delete_items(), - 'octicon octicon-trashcan' - ).addClass('btn-danger'); - } else { - this.page.btn_primary.removeClass('btn-danger'); - this.set_primary_action(); - } + this.toggle_actions_menu_button(this.$checks.length > 0); } toggle_tags() { this.$result.toggleClass('tags-shown'); } - delete_items() { - const docnames = this.get_checked_items(true); - - frappe.confirm(__('Delete {0} items permanently?', [docnames.length]), - () => { - frappe.call({ - method: 'frappe.desk.reportview.delete_items', - freeze: true, - args: { - items: docnames, - doctype: this.doctype - } - }).then((r) => { - let failed = r.message; - if (!failed) failed = []; - - if (failed.length && !r._server_messages) { - frappe.throw(__('Cannot delete {0}', [failed.map(f => f.bold()).join(', ')])); - } - if (failed.length < docnames.length) { - frappe.utils.play_sound('delete'); - this.refresh(true); - } - }); - } - ); - } - get_checked_items(only_docnames) { const docnames = Array.from(this.$checks || []) .map(check => $(check).data().name); @@ -816,6 +792,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { standard: true }); } + if (frappe.model.can_set_user_permissions(doctype)) { items.push({ label: __('User Permissions'), @@ -825,6 +802,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { standard: true }); } + if (frappe.user_roles.includes('System Manager')) { items.push({ label: __('Role Permissions Manager'), @@ -849,107 +827,6 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { standard: true }); - // utility - const bulk_assignment = () => { - return { - label: __('Assign To'), - action: () => { - const docnames = this.get_checked_items(true); - if (docnames.length > 0) { - const dialog = new frappe.ui.form.AssignToDialog({ - obj: this, - method: 'frappe.desk.form.assign_to.add_multiple', - doctype: this.doctype, - docname: docnames, - bulk_assign: true, - re_assign: true, - callback: () => this.refresh(true) - }); - dialog.clear(); - dialog.show(); - } else { - frappe.msgprint(__('Select records for assignment')); - } - }, - standard: true - }; - }; - - const bulk_printing = () => { - const print_settings = frappe.model.get_doc(':Print Settings', 'Print Settings'); - const allow_print_for_draft = cint(print_settings.allow_print_for_draft); - const is_submittable = frappe.model.is_submittable(this.doctype); - const allow_print_for_cancelled = cint(print_settings.allow_print_for_cancelled); - - return { - label: __('Print'), - action: () => { - const items = this.get_checked_items(); - - const valid_docs = items.filter(doc => { - return !is_submittable || doc.docstatus === 1 || - (allow_print_for_cancelled && doc.docstatus == 2) || - (allow_print_for_draft && doc.docstatus == 0) || - frappe.user.has_role('Administrator'); - }).map(doc => doc.name); - - var invalid_docs = items.filter(doc => !valid_docs.includes(doc.name)); - - if (invalid_docs.length > 0) { - frappe.msgprint(__('You selected Draft or Cancelled documents')); - return; - } - - if (valid_docs.length > 0) { - const dialog = new frappe.ui.Dialog({ - title: __('Print Documents'), - fields: [{ - 'fieldtype': 'Check', - 'label': __('With Letterhead'), - 'fieldname': 'with_letterhead' - }, - { - 'fieldtype': 'Select', - 'label': __('Print Format'), - 'fieldname': 'print_sel', - options: frappe.meta.get_print_formats(this.doctype) - }] - }); - - dialog.set_primary_action(__('Print'), args => { - if (!args) return; - const default_print_format = frappe.get_meta(this.doctype).default_print_format; - const with_letterhead = args.with_letterhead ? 1 : 0; - const print_format = args.print_sel ? args.print_sel : default_print_format; - const json_string = JSON.stringify(valid_docs); - - const w = window.open('/api/method/frappe.utils.print_format.download_multi_pdf?' + - 'doctype=' + encodeURIComponent(this.doctype) + - '&name=' + encodeURIComponent(json_string) + - '&format=' + encodeURIComponent(print_format) + - '&no_letterhead=' + (with_letterhead ? '0' : '1')); - if (!w) { - frappe.msgprint(__('Please enable pop-ups')); - return; - } - }); - - dialog.show(); - } else { - frappe.msgprint(__('Select atleast 1 record for printing')); - } - }, - standard: true - }; - }; - - // bulk assignment - items.push(bulk_assignment()); - - if (frappe.model.can_print(doctype)) { - items.push(bulk_printing()); - } - // add to desktop items.push({ label: __('Add to Desktop'), @@ -969,6 +846,131 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { return items; } + get_actions_menu_items() { + const doctype = this.doctype; + const actions_menu_items = []; + const bulk_operations = new BulkOperations({ doctype: this.doctype }); + + const is_field_editable = (field_doc) => { + return field_doc.fieldname && frappe.model.is_value_type(field_doc) + && field_doc.fieldtype !== 'Read Only' && !field_doc.hidden && !field_doc.read_only; + }; + + const has_editable_fields = () => { + return frappe.meta.get_docfields(doctype).some(field_doc => is_field_editable(field_doc)); + }; + + const has_submit_permission = () => { + return frappe.perm.has_perm('Sales Order', 0, 'submit'); + }; + + // utility + const bulk_assignment = () => { + return { + label: __('Assign To'), + action: () => bulk_operations.assign(this.get_checked_items(true), this.refresh), + standard: true + }; + }; + + const bulk_printing = () => { + return { + label: __('Print'), + action: () => bulk_operations.print(this.get_checked_items()), + standard: true + }; + }; + + const bulk_delete = () => { + return { + label: __('Delete'), + action: () => { + const docnames = this.get_checked_items(true); + frappe.confirm(__('Delete {0} items permanently?', [docnames.length]), + () => bulk_operations.delete(docnames, this.refresh)); + }, + standard: true, + }; + }; + + const bulk_cancel = () => { + return { + label: __('Cancel'), + action: () => { + const docnames = this.get_checked_items(true); + if (docnames.length > 0) { + frappe.confirm(__('Cancel {0} documents?', [docnames.length]), + () => bulk_operations.submit_or_cancel(docnames, 'cancel', this.refresh)); + } + }, + standard: true + }; + }; + + const bulk_submit = () => { + return { + label: __('Submit'), + action: () => { + const docnames = this.get_checked_items(true); + if (docnames.length > 0) { + frappe.confirm(__('Submit {0} documents?', [docnames.length]), + () => bulk_operations.submit_or_cancel(docnames, 'submit', this.refresh)); + } + }, + standard: true + }; + }; + + const bulk_edit = () => { + return { + label: __('Edit'), + action: () => { + let field_mappings = {}; + + frappe.meta.get_docfields(doctype).forEach(field_doc => { + if (is_field_editable(field_doc)) { + field_mappings[field_doc.label] = Object.assign({}, field_doc); + } + }); + + const docnames = this.get_checked_items(true); + + bulk_operations.edit(docnames, field_mappings, this.refresh); + }, + standard: true + }; + }; + // bulk assignment + actions_menu_items.push(bulk_assignment()); + + // bulk printing + if (frappe.model.can_print(doctype)) { + actions_menu_items.push(bulk_printing()); + } + + // Bulk cancel + if (frappe.model.can_cancel(doctype)) { + actions_menu_items.push(bulk_cancel()); + } + + // Bulk submit + if (frappe.model.is_submittable(doctype) && has_submit_permission()) { + actions_menu_items.push(bulk_submit()); + } + + // bulk delete + if (frappe.model.can_delete(doctype)) { + actions_menu_items.push(bulk_delete()); + } + + // bulk edit + if (has_editable_fields()) { + actions_menu_items.push(bulk_edit()); + } + + return actions_menu_items; + } + set_filters_from_route_options() { const filters = []; for (let field in frappe.route_options) { @@ -1021,6 +1023,6 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { } }; -$(document).on('save', function (event, doc) { +$(document).on('save', (event, doc) => { frappe.views.ListView.trigger_list_update(doc); }); \ No newline at end of file diff --git a/frappe/public/js/frappe/socketio_client.js b/frappe/public/js/frappe/socketio_client.js index 39c199d634..b06286b028 100644 --- a/frappe/public/js/frappe/socketio_client.js +++ b/frappe/public/js/frappe/socketio_client.js @@ -43,7 +43,7 @@ frappe.socketio = { if(data.percent==100) { frappe.hide_progress(); } else { - frappe.show_progress(data.title || __("Progress"), data.percent, 100); + frappe.show_progress(data.title || __("Progress"), data.percent, 100, data.description); } } }); diff --git a/frappe/public/js/frappe/ui/messages.js b/frappe/public/js/frappe/ui/messages.js index f786d76d7c..44c526e02b 100644 --- a/frappe/public/js/frappe/ui/messages.js +++ b/frappe/public/js/frappe/ui/messages.js @@ -241,11 +241,12 @@ frappe.show_progress = function(title, count, total=100, description) { var dialog = new frappe.ui.Dialog({ title: title, }); - dialog.progress = $(`
-
+ dialog.progress = $(`
+
+
+

-
`) - .appendTo(dialog.body); +

- -
- + +
+

@@ -18,27 +18,28 @@
- - + +
@@ -56,4 +57,4 @@
- + \ No newline at end of file diff --git a/frappe/public/js/frappe/ui/page.js b/frappe/public/js/frappe/ui/page.js index e5714ea933..67182dceae 100644 --- a/frappe/public/js/frappe/ui/page.js +++ b/frappe/public/js/frappe/ui/page.js @@ -235,16 +235,30 @@ frappe.ui.Page = Class.extend({ this.icon_group.addClass("hide"); }, - //--- Actions (workflow) --// + //--- Actions Menu--// + + show_actions_menu: function() { + this.actions_btn_group.removeClass("hide"); + }, + hide_actions_menu: function() { + this.actions_btn_group.addClass("hide"); + }, + + add_action_item: function(label, click, standard) { return this.add_dropdown_item(label, click, standard, this.actions); }, + add_actions_menu_item: function(label, click, standard) { + return this.add_dropdown_item(label, click, standard, this.actions); + }, + clear_actions_menu: function() { this.clear_btn_group(this.actions); }, + //-- Generic --// /* diff --git a/frappe/utils/__init__.py b/frappe/utils/__init__.py index 5ab9ccd4cd..9c384e00d5 100644 --- a/frappe/utils/__init__.py +++ b/frappe/utils/__init__.py @@ -548,3 +548,11 @@ def get_site_info(): # dumps -> loads to prevent datatype conflicts return json.loads(frappe.as_json(site_info)) + +def parse_json(val): + """ + Parses json if string else return + """ + if isinstance(val, string_types): + return json.loads(val) + return val