Merge pull request #9777 from prssanna/report-export-columns

fix(Report): Export custom columns in Excel
This commit is contained in:
mergify[bot] 2020-04-02 06:16:48 +00:00 committed by GitHub
commit c1533cfb95
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 61 additions and 37 deletions

View file

@ -6,7 +6,7 @@ import frappe
import json, datetime
from frappe import _, scrub
import frappe.desk.query_report
from frappe.utils import cint
from frappe.utils import cint, cstr
from frappe.model.document import Document
from frappe.modules.export_file import export_to_files
from frappe.modules import make_boilerplate
@ -92,6 +92,18 @@ class Report(Document):
make_boilerplate("controller.py", self, {"name": self.name})
make_boilerplate("controller.js", self, {"name": self.name})
def execute_query_report(self, filters):
if not self.query:
frappe.throw(_("Must specify a Query to run"), title=_('Report Document Error'))
if not self.query.lower().startswith("select"):
frappe.throw(_("Query must be a SELECT"), title=_('Report Document Error'))
result = [list(t) for t in frappe.db.sql(self.query, filters)]
columns = [cstr(c[0]) for c in frappe.db.get_description()]
return [columns, result]
def execute_script_report(self, filters):
# save the timestamp to automatically set to prepared
threshold = 30

View file

@ -67,3 +67,19 @@ def find_all(list_of_dict, match_function):
if match_function(entry):
found.append(entry)
return found
def ljust_list(_list, length, fill_word=None):
"""
Similar to ljust but for list.
Usage:
$ ljust_list([1, 2, 3], 5)
> [1, 2, 3, None, None]
"""
# make a copy to avoid mutation of passed list
_list = list(_list)
fill_length = length - len(_list)
if fill_length > 0:
_list.extend([fill_word] * fill_length)
return _list

View file

@ -8,7 +8,7 @@ import os, json
from frappe import _
from frappe.modules import scrub, get_module_path
from frappe.utils import flt, cint, get_html_format, cstr, get_url_to_form
from frappe.utils import flt, cint, get_html_format, get_url_to_form
from frappe.model.utils import render_include
from frappe.translate import send_translations
import frappe.desk.reportview
@ -16,6 +16,7 @@ from frappe.permissions import get_role_permissions
from six import string_types, iteritems
from datetime import timedelta
from frappe.utils import gzip_decompress
from frappe.core.utils import ljust_list
def get_report_doc(report_name):
doc = frappe.get_doc("Report", report_name)
@ -42,44 +43,32 @@ def get_report_doc(report_name):
return doc
def generate_report_result(report, filters=None, user=None):
status = None
if not user:
user = frappe.session.user
if not filters:
filters = []
def generate_report_result(report, filters=None, user=None, custom_columns=None):
user = user or frappe.session.user
filters = filters or []
if filters and isinstance(filters, string_types):
filters = json.loads(filters)
columns, result, message, chart, report_summary, skip_total_row = [], [], None, None, None, 0
res = []
if report.report_type == "Query Report":
if not report.query:
status = "error"
frappe.msgprint(_("Must specify a Query to run"), raise_exception=True)
if not report.query.lower().startswith("select"):
status = "error"
frappe.msgprint(_("Query must be a SELECT"), raise_exception=True)
result = [list(t) for t in frappe.db.sql(report.query, filters)]
columns = [cstr(c[0]) for c in frappe.db.get_description()]
res = report.execute_query_report(filters)
elif report.report_type == 'Script Report':
res = report.execute_script_report(filters)
columns, result = res[0], res[1]
if len(res) > 2:
message = res[2]
if len(res) > 3:
chart = res[3]
if len(res) > 4:
report_summary = res[4]
if len(res) > 5:
skip_total_row = cint(res[5])
columns, result, message, chart, report_summary, skip_total_row = \
ljust_list(res, 6)
if report.custom_columns:
columns = json.loads(report.custom_columns)
result = add_data_to_custom_columns(columns, result)
if custom_columns:
result = add_data_to_custom_columns(custom_columns, result)
for custom_column in custom_columns:
columns.insert(custom_column['insert_after_index'] + 1, custom_column)
if result:
result = get_filtered_data(report.ref_doctype, columns, result, user)
@ -93,8 +82,8 @@ def generate_report_result(report, filters=None, user=None):
"message": message,
"chart": chart,
"report_summary": report_summary,
"skip_total_row": skip_total_row,
"status": status,
"skip_total_row": skip_total_row or 0,
"status": None,
"execution_time": frappe.cache().hget('report_execution_time', report.name) or 0
}
@ -161,7 +150,7 @@ def get_script(report_name):
@frappe.whitelist()
@frappe.read_only()
def run(report_name, filters=None, user=None, ignore_prepared_report=False):
def run(report_name, filters=None, user=None, ignore_prepared_report=False, custom_columns=None):
report = get_report_doc(report_name)
if not user:
@ -183,7 +172,7 @@ def run(report_name, filters=None, user=None, ignore_prepared_report=False):
dn = ""
result = get_prepared_report_result(report, filters, dn, user)
else:
result = generate_report_result(report, filters, user)
result = generate_report_result(report, filters, user, custom_columns)
result["add_total_row"] = report.add_total_row and not result.get('skip_total_row', False)
@ -294,6 +283,8 @@ def export_query():
if isinstance(data.get("file_format_type"), string_types):
file_format_type = data["file_format_type"]
custom_columns = frappe.parse_json(data["custom_columns"])
include_indentation = data["include_indentation"]
if isinstance(data.get("visible_idx"), string_types):
visible_idx = json.loads(data.get("visible_idx"))
@ -301,7 +292,7 @@ def export_query():
visible_idx = None
if file_format_type == "Excel":
data = run(report_name, filters)
data = run(report_name, filters, custom_columns=custom_columns)
data = frappe._dict(data)
if not data.columns:
frappe.respond_as_web_page(_("No data to export"),

View file

@ -616,6 +616,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
prepare_report_data(data) {
this.raw_data = data;
this.columns = this.prepare_columns(data.columns);
this.custom_columns = [];
this.data = this.prepare_data(data.result);
this.linked_doctypes = this.get_linked_doctypes();
this.tree_report = this.data.some(d => 'indent' in d);
@ -1110,6 +1111,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
const args = {
cmd: 'frappe.desk.query_report.export_query',
report_name: this.report_name,
custom_columns: this.custom_columns.length? this.custom_columns: [],
file_format_type: file_format,
filters: filters,
visible_idx,
@ -1275,16 +1277,20 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
primary_action: (values) => {
const custom_columns = [];
let df = frappe.meta.get_docfield(values.doctype, values.field);
const insert_after_index = this.columns
.findIndex(column => column.label === values.insert_after);
custom_columns.push({
fieldname: df.fieldname,
fieldtype: df.fieldtype,
label: df.label,
insert_after_index: insert_after_index,
link_field: this.doctype_field_map[values.doctype],
doctype: values.doctype,
options: df.fieldtype === "Link" ? df.options : undefined,
width: 100
});
this.custom_columns = this.custom_columns.concat(custom_columns);
frappe.call({
method: 'frappe.desk.query_report.get_data_for_custom_field',
args: {
@ -1294,7 +1300,8 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
callback: (r) => {
const custom_data = r.message;
const link_field = this.doctype_field_map[values.doctype];
this.add_custom_column(custom_columns, custom_data, link_field, values.field, values.insert_after);
this.add_custom_column(custom_columns, custom_data, link_field, values.field, insert_after_index);
d.hide();
}
});
@ -1369,11 +1376,9 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
}
}
add_custom_column(custom_column, custom_data, link_field, column_field, insert_after) {
add_custom_column(custom_column, custom_data, link_field, column_field, insert_after_index) {
const column = this.prepare_columns(custom_column);
const insert_after_index = this.columns
.findIndex(column => column.label === insert_after);
this.columns.splice(insert_after_index + 1, 0, column[0]);
this.data.forEach(row => {