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