feat(desk): Add Group By in Report View
This commit is contained in:
parent
aa4fdb728e
commit
987f210dfd
5 changed files with 210 additions and 54 deletions
|
|
@ -357,6 +357,7 @@
|
|||
"public/js/frappe/views/reports/report_factory.js",
|
||||
"public/js/frappe/views/reports/report_view.js",
|
||||
"public/js/frappe/views/reports/reportview_footer.html",
|
||||
"public/js/frappe/views/reports/groupby.html",
|
||||
"public/js/frappe/views/reports/query_report.js",
|
||||
"public/js/frappe/views/reports/grid_report.js",
|
||||
"public/js/frappe/views/reports/print_grid.html",
|
||||
|
|
|
|||
|
|
@ -87,13 +87,18 @@ $.extend(frappe.model, {
|
|||
return !frappe.model.std_fields_list.includes(fieldname);
|
||||
},
|
||||
|
||||
get_std_field: function(fieldname) {
|
||||
get_std_field: function(fieldname, ignore=false) {
|
||||
var docfield = $.map([].concat(frappe.model.std_fields).concat(frappe.model.std_fields_table),
|
||||
function(d) {
|
||||
if(d.fieldname==fieldname) return d;
|
||||
});
|
||||
if(!docfield.length) {
|
||||
frappe.msgprint(__("Unknown Column: {0}", [fieldname]));
|
||||
if (!docfield.length) {
|
||||
//Standard fields are ignored in case of adding columns as a result of groupby
|
||||
if (ignore) {
|
||||
return {fieldname: fieldname};
|
||||
} else {
|
||||
frappe.msgprint(__("Unknown Column: {0}", [fieldname]));
|
||||
}
|
||||
}
|
||||
return docfield[0];
|
||||
},
|
||||
|
|
|
|||
37
frappe/public/js/frappe/views/reports/groupby.html
Normal file
37
frappe/public/js/frappe/views/reports/groupby.html
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
<div class="groupby-box" style="display: none">
|
||||
<div class="list_groupby row">
|
||||
<div class="col-sm-4 form-group ui-front">
|
||||
<select class="groupby form-control">
|
||||
<option value=""disabled selected>Select Group By...</option>
|
||||
{% for condition in groupby_conditions %}
|
||||
<option value="{{condition.fieldname}}">{{ condition.label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-sm-2 form-group">
|
||||
<select class="aggregate-function form-control">
|
||||
{% for condition in aggregate_function_conditions %}
|
||||
<option value="{{condition.name}}">{{ condition.label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-sm-6 col-xs-12">
|
||||
<div class="groupby-field pull-left">
|
||||
<select class="aggregate-on form-control" style="display: none">
|
||||
<option value="" disabled selected>Select Field...</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="groupby-actions pull-left">
|
||||
<a class="set-groupby-and-run btn btn-sm btn-primary pull-left">
|
||||
<i class=" fa fa-check visible-xs"></i>
|
||||
<span class="hidden-xs">{%= __("Apply") %}</span>
|
||||
</a>
|
||||
<a class="small grey remove-groupby pull-left">
|
||||
<i class="octicon octicon-trashcan visible-xs"></i>
|
||||
<span class="hidden-xs">{%= __("Remove") %}</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -60,7 +60,6 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
|
|||
}
|
||||
|
||||
setup_sort_selector() {
|
||||
console.log("Setting Sort Selector");
|
||||
this.sort_selector = new frappe.ui.SortSelector({
|
||||
parent: this.filter_area.$filter_list_wrapper,
|
||||
doctype: this.doctype,
|
||||
|
|
@ -68,63 +67,120 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
|
|||
onchange: this.on_sort_change.bind(this)
|
||||
});
|
||||
|
||||
let group_by_button = $(`<button style="margin-right: 10px;" class="btn btn-default btn-xs add-filter text-muted"> ${__("Add Group By")}</button>`)
|
||||
group_by_button.click(() => this.show_group_by_modal());
|
||||
this.page.wrapper.find(".active-tag-filters").append(group_by_button);
|
||||
//Setup groupby for reports
|
||||
this.setup_groupby_area();
|
||||
}
|
||||
|
||||
show_group_by_modal() {
|
||||
this.group_by = "customer";
|
||||
this.aggregate_on = "grand_total";
|
||||
this.aggregate_function = 'sum';
|
||||
make_groupby_button() {
|
||||
let group_by_button = $(`<div class="tag-groupby-area">
|
||||
<div class="active-tag-groupby">
|
||||
<button class="btn btn-default btn-xs add-groupby text-muted">
|
||||
${__("Add Group")}
|
||||
</button>
|
||||
</div>
|
||||
</div>`);
|
||||
this.page.wrapper.find(".sort-selector").before(group_by_button);
|
||||
group_by_button.click(() => this.groupby_edit_area.show());
|
||||
}
|
||||
|
||||
const d = new frappe.ui.Dialog({
|
||||
title: __('Set Group By'),
|
||||
fields: [
|
||||
{
|
||||
fieldtype: 'Select',
|
||||
fieldname: 'group_by',
|
||||
label: 'Column for Group by',
|
||||
options: this.get_group_by_fields(),
|
||||
reqd: 1
|
||||
},
|
||||
{
|
||||
fieldtype: 'Select',
|
||||
fieldname: 'aggregate_function',
|
||||
label: 'Aggregate Function',
|
||||
options: ["Count", "Sum", "Average"],
|
||||
reqd: 1
|
||||
},
|
||||
{
|
||||
fieldtype: 'Select',
|
||||
fieldname: 'aggregate_on',
|
||||
label: 'Column for Aggregate',
|
||||
options: this.meta.fields.map(f => f.label),
|
||||
reqd: 1
|
||||
setup_groupby_area() {
|
||||
this.make_groupby_button();
|
||||
let current_cols=[];
|
||||
let sql_aggregate_function = [{name:'count', label: 'Count'}, {name:'sum', label: 'Sum'}, {name:'avg', label:'Average'}];
|
||||
this.groupby_edit_area = $(frappe.render_template("groupby", {
|
||||
groupby_conditions: this.get_group_by_fields(),
|
||||
aggregate_function_conditions: sql_aggregate_function,
|
||||
}));
|
||||
$(".aggregate-function").val("count");
|
||||
this.page.wrapper.find(".frappe-list").append(
|
||||
this.groupby_edit_area);
|
||||
|
||||
//Set aggregate on options as numeric fields if function is sum or average
|
||||
$('.aggregate-function').on('change', () => {
|
||||
this.meta.fields.forEach((field) => {
|
||||
let fn = $('.aggregate-function option:selected').val();
|
||||
if(fn === 'sum' || fn === 'avg') {
|
||||
if(frappe.model.is_numeric_field(field.fieldtype)) {
|
||||
$('.aggregate-on')
|
||||
.append($('<option>', { value : field.fieldname })
|
||||
.text(field.label));
|
||||
}
|
||||
$('.aggregate-on').show();
|
||||
} else {
|
||||
$('.aggregate-on').hide();
|
||||
}
|
||||
]
|
||||
});
|
||||
window.xxx = d;
|
||||
d.set_primary_action("Add", () => {
|
||||
this.group_by = this.meta.fields.find( f => f.label == d.get_values().group_by).fieldname;
|
||||
this.aggregate_on = this.meta.fields.find( f => f.label == d.get_values().aggregate_on).fieldname;
|
||||
});
|
||||
})
|
||||
|
||||
let sql_aggregate_function = {
|
||||
Count: "count",
|
||||
Sum: "sum",
|
||||
Average: "avg",
|
||||
}
|
||||
this.aggregate_function = sql_aggregate_function[d.get_values().aggregate_function];
|
||||
d.hide();
|
||||
$('.set-groupby-and-run').on('click', () => {
|
||||
current_cols = this.apply_groupby();
|
||||
});
|
||||
|
||||
$('.remove-groupby').on('click', () => {
|
||||
this.remove_groupby(current_cols);
|
||||
});
|
||||
d.show();
|
||||
}
|
||||
|
||||
apply_groupby() {
|
||||
this.group_by = this.page.wrapper.find('.groupby option:selected').val();
|
||||
this.aggregate_function = this.page.wrapper.find('.aggregate-function option:selected').val();
|
||||
this.aggregate_on = this.page.wrapper.find('.aggregate-on option:selected').val();
|
||||
|
||||
//All necessary fields must be set before applying groupby
|
||||
if(!this.group_by) {
|
||||
this.page.wrapper.find('.groupby').focus();
|
||||
return;
|
||||
} else if(!this.aggregate_function) {
|
||||
this.page.wrapper.find('.aggregate-function').focus();
|
||||
return;
|
||||
} else if(!this.aggregate_on && this.aggregate_function!=='count') {
|
||||
this.page.wrapper.find('.aggregate-on').focus();
|
||||
return;
|
||||
}
|
||||
|
||||
if(this.aggregate_function === 'count') {
|
||||
let groupby_query = 'count(' + this.field_type + '.'+ this.group_by+') as ' + this.group_by + '_Count';
|
||||
this.fields.push([groupby_query, this.doctype]);
|
||||
}
|
||||
$('.set-groupby-and-run').hide();
|
||||
let current_cols = this.columns;
|
||||
let remove_columns = this.columns.filter(column => ![this.aggregate_on, this.group_by].includes(column.field));
|
||||
remove_columns.forEach((col) => {
|
||||
this.remove_column_from_datatable(col);
|
||||
});
|
||||
|
||||
this.columns[0].width = 400;
|
||||
if(this.columns[1]) this.columns[1].width = 300;
|
||||
this.refresh();
|
||||
return current_cols;
|
||||
}
|
||||
|
||||
remove_groupby(columns) {
|
||||
this.groupby_edit_area.hide();
|
||||
$('.set-groupby-and-run').show();
|
||||
this.group_by = null;
|
||||
this.aggregate_function = null;
|
||||
this.aggregate_on = null;
|
||||
$(".groupby").val("");
|
||||
$(".aggregate-function").val("count");
|
||||
$(".aggregate-on").val("").hide();
|
||||
columns.forEach((col, i) => {
|
||||
this.add_column_to_datatable(col.field, this.doctype, i);
|
||||
});
|
||||
this.fields.pop();
|
||||
this.setup_columns();
|
||||
}
|
||||
|
||||
|
||||
get_args() {
|
||||
const args = super.get_args();
|
||||
if (this.aggregate_function && this.aggregate_on) {
|
||||
args.fields.push(`${this.aggregate_function}(${this.aggregate_on}) as ${this.aggregate_on}`);
|
||||
this.field_type = args.fields[0].substring(0,args.fields[0].indexOf('.'));
|
||||
if (this.aggregate_function && this.group_by && this.aggregate_on) {
|
||||
if(this.aggregate_function !== 'count') {
|
||||
args.fields.push(`${this.aggregate_function}(${this.aggregate_on}) as ${this.aggregate_on}`);
|
||||
}
|
||||
}
|
||||
|
||||
return Object.assign(args, {
|
||||
with_comment_count: false,
|
||||
start: 0,
|
||||
|
|
@ -134,7 +190,7 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
|
|||
}
|
||||
|
||||
get_group_by_fields() {
|
||||
return this.meta.fields.filter((f)=> ["Select", "Link"].includes(f.fieldtype)).map(f => f.label);
|
||||
return this.meta.fields.filter((f)=> ["Select", "Link"].includes(f.fieldtype));
|
||||
}
|
||||
|
||||
before_refresh() {
|
||||
|
|
@ -733,7 +789,7 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
|
|||
this.build_fields();
|
||||
this.setup_columns();
|
||||
|
||||
this.datatable.destroy();
|
||||
if (this.datatable) this.datatable.destroy();
|
||||
this.datatable = null;
|
||||
this.refresh();
|
||||
}
|
||||
|
|
@ -758,7 +814,7 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
|
|||
const index = this.fields.findIndex(f => column.field === f[0]);
|
||||
if (index === -1) return;
|
||||
const field = this.fields[index];
|
||||
if (field[0] === 'name') {
|
||||
if (field[0] === 'name' && this.group_by === null) {
|
||||
this.refresh();
|
||||
frappe.throw(__('Cannot remove ID field'));
|
||||
}
|
||||
|
|
@ -869,13 +925,28 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
|
|||
}
|
||||
|
||||
build_column(c) {
|
||||
|
||||
let [fieldname, doctype] = c;
|
||||
let docfield = frappe.meta.docfield_map[doctype || this.doctype][fieldname];
|
||||
|
||||
// brackets are not allowed in fieldnames, if there is a bracket, its a function
|
||||
if (fieldname.includes('(')) {
|
||||
if (fieldname.includes(' AS ')) {
|
||||
fieldname = fieldname.split(' AS ').slice(-1)[0];
|
||||
} else if (fieldname.includes(' as ')) {
|
||||
fieldname = fieldname.split(' as ').slice(-1)[0];
|
||||
}
|
||||
}
|
||||
if (!docfield) {
|
||||
docfield = frappe.model.get_std_field(fieldname);
|
||||
docfield = frappe.model.get_std_field(fieldname, true);
|
||||
|
||||
if (docfield) {
|
||||
if(!docfield.label) {
|
||||
docfield.label = toTitle(fieldname);
|
||||
if(docfield.label.includes('_')) {
|
||||
docfield.label = docfield.label.replace('_',' ');
|
||||
}
|
||||
}
|
||||
docfield.parent = this.doctype;
|
||||
if (fieldname == "name") {
|
||||
docfield.options = this.doctype;
|
||||
|
|
|
|||
|
|
@ -67,3 +67,45 @@
|
|||
margin-bottom: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.groupby-box {
|
||||
border-bottom: 1px solid #d1d8dd;
|
||||
padding: 10px 15px 3px;
|
||||
|
||||
.remove-groupby {
|
||||
margin-top: 6px;
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.groupby-field {
|
||||
padding-right: 15px;
|
||||
width: calc(100% - 36px);
|
||||
|
||||
.frappe-control {
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// for sm and above
|
||||
@media (min-width: @screen-xs) {
|
||||
.groupby-box .row > div[class*="col-sm-"] {
|
||||
padding-right: 0px;
|
||||
}
|
||||
|
||||
.groupby-field {
|
||||
width: 65% !important;
|
||||
|
||||
.frappe-control {
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tag-groupby-area {
|
||||
padding: 10px 150px 10px 10px;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
display: flex;
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue