Procházet zdrojové kódy

Merge branch 'develop' of github.com:frappe/frappe into misc-fixes-1

version-14
Gavin D'souza před 3 roky
rodič
revize
1aa4fa77ac
9 změnil soubory, kde provedl 400 přidání a 3081 odebrání
  1. +58
    -0
      cypress/integration/multi_select_dialog.js
  2. +1
    -1
      frappe/model/base_document.py
  3. +282
    -72
      frappe/public/js/frappe/form/multi_select_dialog.js
  4. +1
    -1
      frappe/public/js/frappe/ui/dialog.js
  5. +1
    -4
      frappe/public/js/frappe/views/reports/query_report.js
  6. +5
    -2
      frappe/templates/print_formats/standard_macros.html
  7. +1
    -1
      package.json
  8. +3
    -1
      socketio.js
  9. +48
    -2999
      yarn.lock

+ 58
- 0
cypress/integration/multi_select_dialog.js Zobrazit soubor

@@ -0,0 +1,58 @@
context('MultiSelectDialog', () => {
before(() => {
cy.login();
cy.visit('/app');
});

function open_multi_select_dialog() {
cy.window().its('frappe').then(frappe => {
new frappe.ui.form.MultiSelectDialog({
doctype: "Assignment Rule",
target: {},
setters: {
document_type: null,
priority: null
},
add_filters_group: 1,
allow_child_item_selection: 1,
child_fieldname: "assignment_days",
child_columns: ["day"]
});
});
}

it('multi select dialog api works', () => {
open_multi_select_dialog();
cy.get_open_dialog().should('contain', 'Select Assignment Rules');
});

it('checks for filters', () => {
['search_term', 'document_type', 'priority'].forEach(fieldname => {
cy.get_open_dialog().get(`.frappe-control[data-fieldname="${fieldname}"]`).should('exist');
});

// add_filters_group: 1 should add a filter group
cy.get_open_dialog().get(`.frappe-control[data-fieldname="filter_area"]`).should('exist');

});

it('checks for child item selection', () => {
cy.get_open_dialog()
.get(`.dt-row-header`).should('not.exist');

cy.get_open_dialog()
.get(`.frappe-control[data-fieldname="allow_child_item_selection"]`)
.should('exist')
.click();

cy.get_open_dialog()
.get(`.frappe-control[data-fieldname="child_selection_area"]`)
.should('exist');

cy.get_open_dialog()
.get(`.dt-row-header`).should('contain', 'Assignment Rule');

cy.get_open_dialog()
.get(`.dt-row-header`).should('contain', 'Day');
});
});

+ 1
- 1
frappe/model/base_document.py Zobrazit soubor

@@ -307,7 +307,7 @@ class BaseDocument(object):
doc["doctype"] = self.doctype
for df in self.meta.get_table_fields():
children = self.get(df.fieldname) or []
doc[df.fieldname] = [d.as_dict(convert_dates_to_str=convert_dates_to_str, no_nulls=no_nulls) for d in children]
doc[df.fieldname] = [d.as_dict(convert_dates_to_str=convert_dates_to_str, no_nulls=no_nulls, no_default_fields=no_default_fields) for d in children]

if no_nulls:
for k in list(doc):


+ 282
- 72
frappe/public/js/frappe/form/multi_select_dialog.js Zobrazit soubor

@@ -2,86 +2,191 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog {
constructor(opts) {
/* Options: doctype, target, setters, get_query, action, add_filters_group, data_fields, primary_action_label */
Object.assign(this, opts);
var me = this;
if (this.doctype != "[Select]") {
frappe.model.with_doctype(this.doctype, function () {
me.make();
});
this.for_select = this.doctype == "[Select]";
if (!this.for_select) {
frappe.model.with_doctype(this.doctype, () => this.init());
} else {
this.make();
this.init();
}
}

make() {
let me = this;
init() {
this.page_length = 20;
this.start = 0;
let fields = this.get_primary_filters();
this.fields = this.get_fields();

this.make();
}

get_fields() {
const primary_fields = this.get_primary_filters();
const result_fields = this.get_result_fields();
const data_fields = this.get_data_fields();
const child_selection_fields = this.get_child_selection_fields();

// Make results area
fields = fields.concat([
{ fieldtype: "HTML", fieldname: "results_area" },
return [...primary_fields, ...result_fields, ...data_fields, ...child_selection_fields];
}

get_result_fields() {
const show_next_page = () => {
this.start += 20;
this.get_results();
};
return [
{
fieldtype: "Button", fieldname: "more_btn", label: __("More"),
click: () => {
this.start += 20;
this.get_results();
}
fieldtype: "HTML", fieldname: "results_area"
},
{
fieldtype: "Button", fieldname: "more_btn",
label: __("More"), click: show_next_page.bind(this)
}
]);
];
}

// Custom Data Fields
if (this.data_fields) {
fields.push({ fieldtype: "Section Break" });
fields = fields.concat(this.data_fields);
get_data_fields() {
if (this.data_fields && this.data_fields.length) {
// Custom Data Fields
return [
{ fieldtype: "Section Break" },
...this.data_fields
];
} else {
return [];
}
}

get_child_selection_fields() {
const fields = [];
if (this.allow_child_item_selection && this.child_fieldname) {
fields.push({ fieldtype: "HTML", fieldname: "child_selection_area" });
}
return fields;
}

make() {
let doctype_plural = this.doctype.plural();
let title = __("Select {0}", [this.for_select ? __("value") : __(doctype_plural)]);

this.dialog = new frappe.ui.Dialog({
title: __("Select {0}", [(this.doctype == '[Select]') ? __("value") : __(doctype_plural)]),
fields: fields,
title: title,
fields: this.fields,
primary_action_label: this.primary_action_label || __("Get Items"),
secondary_action_label: __("Make {0}", [__(me.doctype)]),
primary_action: function () {
let filters_data = me.get_custom_filters();
me.action(me.get_checked_values(), cur_dialog.get_values(), me.args, filters_data);
secondary_action_label: __("Make {0}", [__(this.doctype)]),
primary_action: () => {
let filters_data = this.get_custom_filters();
const data_values = cur_dialog.get_values(); // to pass values of data fields
const filtered_children = this.get_selected_child_names();
const selected_documents = [...this.get_checked_values(), ...this.get_parent_name_of_selected_children()];
this.action(selected_documents, {
...this.args,
...data_values,
...filters_data,
filtered_children
});
},
secondary_action: function (e) {
// If user wants to close the modal
if (e) {
frappe.route_options = {};
if (Array.isArray(me.setters)) {
for (let df of me.setters) {
frappe.route_options[df.fieldname] = me.dialog.fields_dict[df.fieldname].get_value() || undefined;
}
} else {
Object.keys(me.setters).forEach(function (setter) {
frappe.route_options[setter] = me.dialog.fields_dict[setter].get_value() || undefined;
});
}

frappe.new_doc(me.doctype, true);
}
}
secondary_action: this.make_new_document.bind(this)
});

if (this.add_filters_group) {
this.make_filter_area();
}

this.args = {};

this.setup_results();
this.bind_events();
this.get_results();
this.dialog.show();
}

make_new_document(e) {
// If user wants to close the modal
if (e) {
this.set_route_options();
frappe.new_doc(this.doctype, true);
}
}

set_route_options() {
// set route options to get pre-filled form fields
frappe.route_options = {};
if (Array.isArray(this.setters)) {
for (let df of this.setters) {
frappe.route_options[df.fieldname] = this.dialog.fields_dict[df.fieldname].get_value() || undefined;
}
} else {
Object.keys(this.setters).forEach(setter => {
frappe.route_options[setter] = this.dialog.fields_dict[setter].get_value() || undefined;
});
}
}

setup_results() {
this.$parent = $(this.dialog.body);
this.$wrapper = this.dialog.fields_dict.results_area.$wrapper.append(`<div class="results"
this.$wrapper = this.dialog.fields_dict.results_area.$wrapper.append(`<div class="results mt-3"
style="border: 1px solid #d1d8dd; border-radius: 3px; height: 300px; overflow: auto;"></div>`);

this.$results = this.$wrapper.find('.results');
this.$results.append(this.make_list_row());
}

this.args = {};
toggle_child_selection() {
if (this.dialog.fields_dict['allow_child_item_selection'].get_value()) {
this.get_child_result().then(r => {
this.child_results = r.message || [];
this.render_child_datatable();
this.$wrapper.addClass('hidden');
this.$child_wrapper.removeClass('hidden');
this.dialog.fields_dict.more_btn.$wrapper.hide();
});
} else {
this.child_results = [];
this.get_results();
this.$wrapper.removeClass('hidden');
this.$child_wrapper.addClass('hidden');
}
}

this.bind_events();
this.get_results();
this.dialog.show();
render_child_datatable() {
if (!this.child_datatable) {
this.setup_child_datatable();
} else {
setTimeout(() => {
this.child_datatable.rowmanager.checkMap = [];
this.child_datatable.refresh(this.get_child_datatable_rows());
this.$child_wrapper.find('.dt-scrollable').css('height', '300px');
}, 500);
}
}

get_child_datatable_columns() {
const parent = this.doctype;
return [parent, ...this.child_columns].map(d => ({ name: frappe.unscrub(d), editable: false }));
}

get_child_datatable_rows() {
return this.child_results.map(d => Object.values(d).slice(1)); // slice name field
}

setup_child_datatable() {
const header_columns = this.get_child_datatable_columns();
const rows = this.get_child_datatable_rows();
this.$child_wrapper = this.dialog.fields_dict.child_selection_area.$wrapper;
this.$child_wrapper.addClass('mt-3');

this.child_datatable = new frappe.DataTable(this.$child_wrapper.get(0), {
columns: header_columns,
data: rows,
layout: 'fluid',
inlineFilters: true,
serialNoColumn: false,
checkboxColumn: true,
cellHeight: 35,
noDataMessage: __('No Data'),
disableReorderColumn: true
});
this.$child_wrapper.find('.dt-scrollable').css('height', '300px');
}

get_primary_filters() {
@@ -94,7 +199,7 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog {
columns[0] = [
{
fieldtype: "Data",
label: __("Search"),
label: __("Name"),
fieldname: "search_term"
}
];
@@ -127,6 +232,16 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog {
// now a is a fixed-size array with mutable entries
}

if (this.allow_child_item_selection) {
this.child_doctype = frappe.meta.get_docfield(this.doctype, this.child_fieldname).options;
columns[0].push({
fieldtype: "Check",
label: __("Select {0}", [this.child_doctype]),
fieldname: "allow_child_item_selection",
onchange: this.toggle_child_selection.bind(this)
});
}

fields = [
...columns[0],
{ fieldtype: "Column Break" },
@@ -156,6 +271,9 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog {
this.get_results();
}
});
// 'Apply Filter' breaks since the filers are not in a popover
// Hence keeping it hidden
this.filter_group.wrapper.find('.apply-filters').hide();
}

get_custom_filters() {
@@ -166,7 +284,7 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog {
});
}, {});
} else {
return [];
return {};
}
}

@@ -200,6 +318,34 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog {
});
}

get_parent_name_of_selected_children() {
if (!this.child_datatable || !this.child_datatable.datamanager.rows.length) return [];

let parent_names = this.child_datatable.rowmanager.checkMap.reduce((parent_names, checked, index) => {
if (checked == 1) {
const parent_name = this.child_results[index].parent;
parent_names.push(parent_name);
}
return parent_names;
}, []);

return parent_names;
}

get_selected_child_names() {
if (!this.child_datatable || !this.child_datatable.datamanager.rows.length) return [];

let checked_names = this.child_datatable.rowmanager.checkMap.reduce((checked_names, checked, index) => {
if (checked == 1) {
const child_row_name = this.child_results[index].name;
checked_names.push(child_row_name);
}
return checked_names;
}, []);

return checked_names;
}

get_checked_values() {
// Return name of checked value.
return this.$results.find('.list-item-container').map(function () {
@@ -276,6 +422,8 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog {
me.$results.append(me.make_list_row(result));
});

this.$results.find(".list-item--head").css("z-index", 0);

if (frappe.flags.auto_scroll) {
this.$results.animate({ scrollTop: me.$results.prop('scrollHeight') }, 500);
}
@@ -297,7 +445,7 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog {
this.render_result_list(checked, 0, false);
}

get_results() {
get_filters_from_setters() {
let me = this;
let filters = this.get_query ? this.get_query().filters : {} || {};
let filter_fields = [];
@@ -321,12 +469,18 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog {
});
}

let filter_group = this.get_custom_filters();
Object.assign(filters, filter_group);
return [filters, filter_fields];
}

get_args_for_search() {
let [filters, filter_fields] = this.get_filters_from_setters();

let custom_filters = this.get_custom_filters();
Object.assign(filters, custom_filters);

let args = {
doctype: me.doctype,
txt: me.dialog.fields_dict["search_term"].get_value(),
return {
doctype: this.doctype,
txt: this.dialog.fields_dict["search_term"].get_value(),
filters: filters,
filter_fields: filter_fields,
start: this.start,
@@ -334,25 +488,81 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog {
query: this.get_query ? this.get_query().query : '',
as_dict: 1
};
frappe.call({
}

async perform_search(args) {
const res = await frappe.call({
type: "GET",
method: 'frappe.desk.search.search_widget',
no_spinner: true,
args: args,
callback: function (r) {
let more = 0;
me.results = [];
if (r.values.length) {
if (r.values.length > me.page_length) {
r.values.pop();
more = 1;
}
r.values.forEach(function (result) {
result.checked = 0;
me.results.push(result);
});
});
const more = res.values.length && res.values.length > this.page_length ? 1 : 0;
if (more) {
res.values.pop();
}

return [res, more];
}

async get_results() {
const args = this.get_args_for_search();
const [res, more] = await this.perform_search(args);

this.results = [];
if (res.values.length) {
res.values.forEach(result => {
result.checked = 0;
this.results.push(result);
});
}
this.render_result_list(this.results, more);
}

async get_filtered_parents_for_child_search() {
const parent_search_args = this.get_args_for_search();
parent_search_args.filter_fields = ['name'];
// eslint-disable-next-line no-unused-vars
const [response, _] = await this.perform_search(parent_search_args);

let parent_names = [];
if (response.values.length) {
parent_names = response.values.map(v => v.name);
}
return parent_names;
}

async add_parent_filters(filters) {
const parent_names = await this.get_filtered_parents_for_child_search();
if (parent_names.length) {
filters.push([ "parent", "in", parent_names ]);
}
}

add_custom_child_filters(filters) {
if (this.add_filters_group && this.filter_group) {
this.filter_group.get_filters().forEach(filter => {
if (filter[0] == this.child_doctype) {
filters.push([filter[1], filter[2], filter[3]]);
}
me.render_result_list(me.results, more);
});
}
}

async get_child_result() {
let filters = [["parentfield", "=", this.child_fieldname]];

await this.add_parent_filters(filters);
this.add_custom_child_filters(filters);

return frappe.call({
method: "frappe.client.get_list",
args: {
doctype: this.child_doctype,
filters: filters,
fields: ['name', 'parent', ...this.child_columns],
parent: this.doctype,
order_by: 'parent'
}
});
}


+ 1
- 1
frappe/public/js/frappe/ui/dialog.js Zobrazit soubor

@@ -153,7 +153,7 @@ frappe.ui.Dialog = class Dialog extends frappe.ui.FieldGroup {

set_secondary_action(click) {
this.footer.removeClass('hide');
this.get_secondary_btn().removeClass('hide').on('click', click);
this.get_secondary_btn().removeClass('hide').off('click').on('click', click);
}

set_secondary_action_label(label) {


+ 1
- 4
frappe/public/js/frappe/views/reports/query_report.js Zobrazit soubor

@@ -832,6 +832,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
if (this.raw_data.add_total_row) {
data = data.slice();
data.splice(-1, 1);
this.$page.find('.layout-main-section').css('--report-total-height', '310px');
}

this.$report.show();
@@ -854,10 +855,6 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
}
};

if (this.raw_data.add_total_row) {
this.$page.find('.layout-main-section').css('--report-total-height', '310px');
}

if (this.report_settings.get_datatable_options) {
datatable_options = this.report_settings.get_datatable_options(datatable_options);
}


+ 5
- 2
frappe/templates/print_formats/standard_macros.html Zobrazit soubor

@@ -135,11 +135,13 @@ data-fieldname="{{ df.fieldname }}" data-fieldtype="{{ df.fieldtype }}"
{% elif df.fieldtype=="Check" and doc[df.fieldname] %}
<!-- <i class="{{ 'fa fa-check' }}"></i> -->
<svg viewBox="0 0 16 16"
fill="transparent" stroke="var(--icon-stroke)" stroke-width="2"
fill="transparent" stroke="#1F272E" stroke-width="2"
xmlns="http://www.w3.org/2000/svg" id="icon-tick"
style="width: 12px; height: 12px; margin-top: 5px;">
<path d="M2 9.66667L5.33333 13L14 3" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
{% elif df.fieldtype=="Check" and not doc[df.fieldname] %}
<!-- empty -->
{% elif df.fieldtype in ("Image", "Attach Image") and frappe.utils.is_image(doc[doc.meta.get_field(df.fieldname).options]) %}
<img src="{{ doc[doc.meta.get_field(df.fieldname).options] }}"
class="img-responsive"
@@ -178,7 +180,8 @@ data-fieldname="{{ df.fieldname }}" data-fieldtype="{{ df.fieldtype }}"
{% macro get_align_class(df, no_of_cols=2) %}
{% if no_of_cols >= 3 %}{{ "" }}
{%- elif df.align -%}{{ "text-" + df.align }}
{%- elif df.fieldtype in ("Int", "Float", "Currency", "Check", "Percent") -%}{{ "text-right" }}
{%- elif df.fieldtype in ("Int", "Float", "Currency", "Percent") -%}{{ "text-right" }}
{%- elif df.fieldtype in ("Check") -%}{{ "text-center" }}
{%- else -%}{{ "" }}
{%- endif -%}
{% endmacro %}


+ 1
- 1
package.json Zobrazit soubor

@@ -56,7 +56,7 @@
"qz-tray": "^2.0.8",
"redis": "^3.1.1",
"showdown": "^1.9.1",
"snyk": "^1.667.0",
"snyk": "^1.685.0",
"socket.io": "^2.4.0",
"superagent": "^3.8.2",
"touch": "^3.1.0",


+ 3
- 1
socketio.js Zobrazit soubor

@@ -265,9 +265,11 @@ function get_chat_room(socket, room) {
}

function get_site_name(socket) {
var hostname_from_host = get_hostname(socket.request.headers.host);

if (socket.request.headers['x-frappe-site-name']) {
return get_hostname(socket.request.headers['x-frappe-site-name']);
} else if (['localhost', '127.0.0.1'].indexOf(socket.request.headers.host) !== -1 &&
} else if (['localhost', '127.0.0.1'].indexOf(hostname_from_host) !== -1 &&
conf.default_site) {
// from currentsite.txt since host is localhost
return conf.default_site;


+ 48
- 2999
yarn.lock
Diff nebyl zobrazen, protože je příliš veliký
Zobrazit soubor


Načítá se…
Zrušit
Uložit