List view bulk edit option (#5107)
* added actions button for bulk operations also included bulk edit option * page html refactor and added show/hide action option * copy field object to avoid mutation * commonify update method and other refactor * fixed bug with progress bar level and minor fix with 'value' field display * fixed frappe/erpnext#13063 * commonify field control method and removed unwanted code * separate method for submit and cancel * [minor] msgprint change * refresh list on complete * requested changes and refactor * codacy fix * code formatting, changed var name * conflict fix * description option for progress dialog * extracted bulk operations to a separate file * Refactor commonified redundant method get_json -> parse_json * rename change_df -> replace_field
This commit is contained in:
parent
2fc96e1419
commit
1202ff1fdb
15 changed files with 527 additions and 282 deletions
|
|
@ -1468,4 +1468,8 @@ def get_version(doctype, name, limit = None, head = False, raise_err = True):
|
|||
|
||||
@whitelist(allow_guest = True)
|
||||
def ping():
|
||||
return "pong"
|
||||
return "pong"
|
||||
|
||||
def parse_json(val):
|
||||
from frappe.utils import parse_json
|
||||
return parse_json(val)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
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
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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 = $('<div class="form-clickable-section text-center">\
|
||||
|
|
@ -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({
|
|||
</form>\
|
||||
</div>').appendTo(this.section.body)
|
||||
.find("form")
|
||||
.on("submit", function() { return false; });
|
||||
.on("submit", function() {
|
||||
return false;
|
||||
});
|
||||
|
||||
if(this.df.label) {
|
||||
$('<label class="control-label">'+ __(this.df.label)
|
||||
+'</label>').appendTo(this.wrapper);
|
||||
if (this.df.label) {
|
||||
$('<label class="control-label">' + __(this.df.label)
|
||||
+ '</label>').appendTo(this.wrapper);
|
||||
}
|
||||
},
|
||||
resize_all_columns: function() {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
187
frappe/public/js/frappe/list/bulk_operations.js
Normal file
187
frappe/public/js/frappe/list/bulk_operations.js
Normal file
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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 `
|
||||
// <div class="list-row freeze-row level">
|
||||
// <div class="level-left">
|
||||
// <div class="list-row-col list-subject"></div>
|
||||
// ${columns.slice(1).map(c => `<div class="list-row-col"></div>`).join('')}
|
||||
// </div>
|
||||
// <div class="level-right"></div>
|
||||
// </div>
|
||||
// `;
|
||||
// }
|
||||
// }
|
||||
|
||||
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);
|
||||
});
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -241,11 +241,12 @@ frappe.show_progress = function(title, count, total=100, description) {
|
|||
var dialog = new frappe.ui.Dialog({
|
||||
title: title,
|
||||
});
|
||||
dialog.progress = $(`<div class="progress">
|
||||
<div class="progress-bar"></div>
|
||||
dialog.progress = $(`<div>
|
||||
<div class="progress">
|
||||
<div class="progress-bar"></div>
|
||||
</div>
|
||||
<p class="description text-muted small"></p>
|
||||
</div>`)
|
||||
.appendTo(dialog.body);
|
||||
</div`).appendTo(dialog.body);
|
||||
dialog.progress_bar = dialog.progress.css({"margin-top": "10px"})
|
||||
.find(".progress-bar");
|
||||
dialog.$wrapper.removeClass("fade");
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@
|
|||
<div class="col-md-7 col-sm-8 col-xs-6 page-title">
|
||||
<!-- title -->
|
||||
<h1>
|
||||
<div class="title-image hide hidden-md hidden-lg">
|
||||
</div>
|
||||
<div class="ellipsis title-text"></div>
|
||||
<span class="indicator hide"></span>
|
||||
<div class="title-image hide hidden-md hidden-lg">
|
||||
</div>
|
||||
<div class="ellipsis title-text"></div>
|
||||
<span class="indicator hide"></span>
|
||||
</h1>
|
||||
</div>
|
||||
<div class="text-right col-md-5 col-sm-4 col-xs-6 page-actions">
|
||||
|
|
@ -18,27 +18,28 @@
|
|||
|
||||
<!-- buttons -->
|
||||
<div class="btn-group menu-btn-group hide">
|
||||
<button type="button" class="btn btn-default btn-sm dropdown-toggle"
|
||||
data-toggle="dropdown" aria-expanded="false">
|
||||
<button type="button" class="btn btn-default btn-sm dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
|
||||
<span class="hidden-xs">
|
||||
<span class="menu-btn-group-label">{%= __("Menu") %}</span>
|
||||
<span class="caret"></span></span>
|
||||
<span class="visible-xs"><i class="octicon octicon-triangle-down"></i></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
</ul>
|
||||
<span class="caret"></span>
|
||||
</span>
|
||||
<span class="visible-xs">
|
||||
<i class="octicon octicon-triangle-down"></i>
|
||||
</span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" role="menu"></ul>
|
||||
</div>
|
||||
<button class="btn btn-secondary btn-default btn-sm hide"></button>
|
||||
<div class="btn-group actions-btn-group hide">
|
||||
<button type="button" class="btn btn-primary btn-sm dropdown-toggle"
|
||||
data-toggle="dropdown" aria-expanded="false">
|
||||
<span class="hidden-xs">
|
||||
{%= __("Actions") %} <span class="caret"></span>
|
||||
</span>
|
||||
<span class="visible-xs octicon octicon-check"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
</ul>
|
||||
<button type="button" class="btn btn-primary btn-sm dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
|
||||
<span class="hidden-xs">
|
||||
{%= __("Actions") %}
|
||||
<span class="caret"></span>
|
||||
</span>
|
||||
<span class="visible-xs octicon octicon-check"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
</ul>
|
||||
</div>
|
||||
<button class="btn btn-primary btn-sm hide primary-action"></button>
|
||||
</div>
|
||||
|
|
@ -56,4 +57,4 @@
|
|||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -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 --//
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue