From ff6cf8c3e3df93f621d7b6f1054a5c3e1c982b6d Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Thu, 4 Apr 2019 12:15:51 +0530 Subject: [PATCH 01/10] feat:Custom fields in reports --- frappe/core/doctype/report/report.json | 87 +++++++++++++++++++++++++- frappe/desk/query_report.py | 50 +++++++++++++-- 2 files changed, 131 insertions(+), 6 deletions(-) diff --git a/frappe/core/doctype/report/report.json b/frappe/core/doctype/report/report.json index 4c8b77de5f..bc62c4b4c2 100644 --- a/frappe/core/doctype/report/report.json +++ b/frappe/core/doctype/report/report.json @@ -21,6 +21,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "report_name", "fieldtype": "Data", "hidden": 0, @@ -52,6 +53,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "ref_doctype", "fieldtype": "Link", "hidden": 0, @@ -84,6 +86,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "is_standard", "fieldtype": "Select", "hidden": 0, @@ -116,6 +119,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "module", "fieldtype": "Link", "hidden": 0, @@ -148,6 +152,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "add_total_row", "fieldtype": "Check", "hidden": 0, @@ -179,6 +184,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "column_break_4", "fieldtype": "Column Break", "hidden": 0, @@ -209,6 +215,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "report_type", "fieldtype": "Select", "hidden": 0, @@ -241,6 +248,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "disabled", "fieldtype": "Check", "hidden": 0, @@ -272,6 +280,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "icon", "fieldtype": "Data", "hidden": 0, @@ -304,6 +313,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "color", "fieldtype": "Data", "hidden": 0, @@ -337,6 +347,7 @@ "collapsible": 0, "columns": 0, "depends_on": "eval: doc.is_standard == \"No\"", + "fetch_if_empty": 0, "fieldname": "letter_head", "fieldtype": "Link", "hidden": 0, @@ -370,6 +381,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "section_break_6", "fieldtype": "Section Break", "hidden": 0, @@ -401,6 +413,7 @@ "collapsible": 0, "columns": 0, "depends_on": "eval:doc.report_type==\"Query Report\"", + "fetch_if_empty": 0, "fieldname": "query", "fieldtype": "Code", "hidden": 0, @@ -434,6 +447,7 @@ "columns": 0, "depends_on": "", "description": "JavaScript Format: frappe.query_reports['REPORTNAME'] = {}", + "fetch_if_empty": 0, "fieldname": "javascript", "fieldtype": "Code", "hidden": 1, @@ -466,6 +480,7 @@ "collapsible": 0, "columns": 0, "depends_on": "eval:doc.report_type==\"Report Builder\"", + "fetch_if_empty": 0, "fieldname": "json", "fieldtype": "Code", "hidden": 0, @@ -497,6 +512,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "permission_rules", "fieldtype": "Section Break", "hidden": 0, @@ -530,6 +546,7 @@ "collapsible": 0, "columns": 0, "depends_on": "eval:doc.is_standard == 'Yes'", + "fetch_if_empty": 0, "fieldname": "roles", "fieldtype": "Table", "hidden": 0, @@ -563,6 +580,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "disable_prepared_report", "fieldtype": "Check", "hidden": 0, @@ -595,6 +613,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "prepared_report", "fieldtype": "Check", "hidden": 1, @@ -620,6 +639,72 @@ "set_only_once": 0, "translatable": 0, "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "customize_section", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Customize", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "add_custom_fields_in_report", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Add Custom Fields In Report", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 } ], "has_web_view": 0, @@ -633,7 +718,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2019-01-25 12:04:50.833264", + "modified": "2019-04-03 20:39:41.786686", "modified_by": "Administrator", "module": "Core", "name": "Report", diff --git a/frappe/desk/query_report.py b/frappe/desk/query_report.py index 7ec767c227..2e63f73544 100644 --- a/frappe/desk/query_report.py +++ b/frappe/desk/query_report.py @@ -83,10 +83,10 @@ def generate_report_result(report, filters=None, user=None): data_to_be_printed = res[4] if result: - result = get_filtered_data(report.ref_doctype, columns, result, user) + result, columns = get_filtered_data(report.ref_doctype, columns, result, user, report.add_custom_fields_in_report) - if cint(report.add_total_row) and result: - result = add_total_row(result, columns) + # if cint(report.add_total_row) and result: + # result = add_total_row(result, columns) return { "result": result, @@ -354,7 +354,7 @@ def add_total_row(result, columns, meta = None): return result -def get_filtered_data(ref_doctype, columns, data, user): +def get_filtered_data(ref_doctype, columns, data, user, add_custom_fields): result = [] linked_doctypes = get_linked_doctypes(columns, data) match_filters_per_doctype = get_user_match_filters(linked_doctypes, user=user) @@ -364,6 +364,46 @@ def get_filtered_data(ref_doctype, columns, data, user): role_permissions = get_role_permissions(frappe.get_meta(ref_doctype), user) if_owner = role_permissions.get("if_owner", {}).get("report") + doc_fields_map = {} + custom_field_value_map = {} + + if add_custom_fields: + + fields = frappe.db.sql(""" select dt, fieldname, fieldtype from `tabCustom Field` + where fieldtype not in ('Section Break', 'Column Break') and + dt in (%s)""" % ', '.join(['%s']* len(linked_doctypes)), tuple([doctype for doctype in linked_doctypes.keys()]), as_dict=1) + + for d in fields: + doc_fields_map.setdefault(d.dt, []) + doc_fields_map.get(d.dt).append(d.fieldname) + + columns.append({ + "label": frappe.unscrub(d.fieldname), + "fieldname": d.filedname, + "fieldtype": d.fieldtype, + "width": 100 + }) + + for doctype in linked_doctypes.keys(): + if doc_fields_map.get(doctype): + values = frappe.db.sql("select name, {fields} from `tab{doctype}` " + .format(fields = ", ".join(doc_fields_map.get(doctype)), doctype=doctype), as_dict=1) + + for value in values: + custom_field_value_map.setdefault(value.name, value) + + for row in data: + for index, column in enumerate(columns): + print(index,column) + if isinstance(row, dict) and column.get("fieldtype") == "Link": + fieldname = column.get("fieldname") + row.update({ "test_field": custom_field_value_map.get(row.get(fieldname),{}).get("test_field")}) + else: + print("$$$$$$$$$$") + custom_field_value_map.get(row[index],{}) + row.append(custom_field_value_map.get(row[index],{}).get("test_field")) + + if match_filters_per_doctype: for row in data: # Why linked_doctypes.get(ref_doctype)? because if column is empty, linked_doctypes[ref_doctype] is removed @@ -375,7 +415,7 @@ def get_filtered_data(ref_doctype, columns, data, user): else: result = list(data) - return result + return result, columns def has_match(row, linked_doctypes, doctype_match_filters, ref_doctype, if_owner, columns_dict, user): From f4061acf90b7512e69dcab39b61c552ea82f378e Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Sun, 7 Apr 2019 20:24:11 +0530 Subject: [PATCH 02/10] feat:Addition of custom fields in query reports --- frappe/desk/query_report.py | 95 +++++++++++-------- .../js/frappe/views/reports/query_report.js | 79 ++++++++++++++- 2 files changed, 133 insertions(+), 41 deletions(-) diff --git a/frappe/desk/query_report.py b/frappe/desk/query_report.py index 2e63f73544..bc6adff8ef 100644 --- a/frappe/desk/query_report.py +++ b/frappe/desk/query_report.py @@ -17,6 +17,7 @@ from six import string_types, iteritems from datetime import timedelta from frappe.utils.file_manager import get_file from frappe.utils import gzip_decompress +from frappe.model import no_value_fields def get_report_doc(report_name): doc = frappe.get_doc("Report", report_name) @@ -33,7 +34,7 @@ def get_report_doc(report_name): return doc -def generate_report_result(report, filters=None, user=None): +def generate_report_result(report, filters=None, user=None, custom_columns=None): status = None if not user: user = frappe.session.user @@ -83,10 +84,10 @@ def generate_report_result(report, filters=None, user=None): data_to_be_printed = res[4] if result: - result, columns = get_filtered_data(report.ref_doctype, columns, result, user, report.add_custom_fields_in_report) + result, columns = get_filtered_data(report.ref_doctype, columns, result, user, custom_columns) - # if cint(report.add_total_row) and result: - # result = add_total_row(result, columns) + if cint(report.add_total_row) and result: + result = add_total_row(result, columns) return { "result": result, @@ -160,7 +161,7 @@ def get_script(report_name): @frappe.whitelist() @frappe.read_only() -def run(report_name, filters=None, user=None): +def run(report_name, filters=None, user=None, custom_columns=None): report = get_report_doc(report_name) if not user: @@ -182,7 +183,7 @@ def run(report_name, filters=None, user=None): 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 @@ -353,8 +354,27 @@ def add_total_row(result, columns, meta = None): result.append(total_row) return result +@frappe.whitelist() +def get_custom_fields(doctypes): -def get_filtered_data(ref_doctype, columns, data, user, add_custom_fields): + field_map = [] + + doclist = json.loads(doctypes) + + for d in doclist: + fieldlist = [f.label for f in frappe.get_meta(d).fields \ + if f.label and f.fieldname and f.fieldname not in no_value_fields + and f.fieldname not in ["naming_series"] + and f.fieldtype not in ["Section Break", "Column Break", "Table"]] + + field_map.append({ + "doctype": d, + "fields": fieldlist + }) + + return field_map + +def get_filtered_data(ref_doctype, columns, data, user, custom_columns): result = [] linked_doctypes = get_linked_doctypes(columns, data) match_filters_per_doctype = get_user_match_filters(linked_doctypes, user=user) @@ -364,45 +384,42 @@ def get_filtered_data(ref_doctype, columns, data, user, add_custom_fields): role_permissions = get_role_permissions(frappe.get_meta(ref_doctype), user) if_owner = role_permissions.get("if_owner", {}).get("report") - doc_fields_map = {} - custom_field_value_map = {} + if custom_columns: + custom_field_value_map = {} + fields = json.loads(custom_columns) + custom_field_list = [] - if add_custom_fields: + for doctype, field_list in iteritems(fields): + values = frappe.db.sql("select name, {fields} from `tab{doctype}` " + .format(fields = ", ".join(field_list), doctype=doctype), as_dict=1) - fields = frappe.db.sql(""" select dt, fieldname, fieldtype from `tabCustom Field` - where fieldtype not in ('Section Break', 'Column Break') and - dt in (%s)""" % ', '.join(['%s']* len(linked_doctypes)), tuple([doctype for doctype in linked_doctypes.keys()]), as_dict=1) - - for d in fields: - doc_fields_map.setdefault(d.dt, []) - doc_fields_map.get(d.dt).append(d.fieldname) - - columns.append({ - "label": frappe.unscrub(d.fieldname), - "fieldname": d.filedname, - "fieldtype": d.fieldtype, - "width": 100 + custom_field_list += field_list + for field in field_list: + columns.append({ + "label": frappe.unscrub(field), + "fieldname": field, + "fieldtype": "Data", + "width": 100 }) - for doctype in linked_doctypes.keys(): - if doc_fields_map.get(doctype): - values = frappe.db.sql("select name, {fields} from `tab{doctype}` " - .format(fields = ", ".join(doc_fields_map.get(doctype)), doctype=doctype), as_dict=1) + for value in values: + custom_field_value_map.setdefault(value.name, value) - for value in values: - custom_field_value_map.setdefault(value.name, value) + columns_dict = get_columns_dict(columns) + columns_idx_map = [columns_dict.get(i) for i in range(len(columns))] for row in data: - for index, column in enumerate(columns): - print(index,column) - if isinstance(row, dict) and column.get("fieldtype") == "Link": - fieldname = column.get("fieldname") - row.update({ "test_field": custom_field_value_map.get(row.get(fieldname),{}).get("test_field")}) - else: - print("$$$$$$$$$$") - custom_field_value_map.get(row[index],{}) - row.append(custom_field_value_map.get(row[index],{}).get("test_field")) - + for index, column in enumerate(columns_idx_map): + for d in custom_field_list: + if column.get("fieldtype") == "Link" or column.get("fieldtype") == "Dynamic Link": + if isinstance(row, dict): + fieldname = column.get("fieldname") + row.update({ fieldname: custom_field_value_map.get(row.get(fieldname),{}).get(d)}) + else: + fieldname = column.get("fieldname") + value = custom_field_value_map.get(row[index],{}).get(d) + if value: + row.append(value) if match_filters_per_doctype: for row in data: diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index 596a6501ad..0beb56005b 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -269,7 +269,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { this.page.clear_fields(); } - refresh() { + refresh(values) { this.toggle_message(true); let filters = this.get_filter_values(true); let query = frappe.utils.get_query_string(frappe.get_route_str()); @@ -291,6 +291,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { args: { report_name: this.report_name, filters: filters, + custom_columns: values }, callback: resolve, always: () => this.page.btn_secondary.prop('disabled', false) @@ -418,7 +419,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { this.raw_data = data; this.columns = this.prepare_columns(data.columns); this.data = this.prepare_data(data.result); - + this.custom_fields = this.get_dialog_fields(); this.tree_report = this.data.some(d => 'indent' in d); } @@ -962,6 +963,22 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { action: () => frappe.set_route('List', 'Auto Email Report', {'report' : this.report_name}), standard: true }, + { + label: __('Add Custom Fields'), + action: () => { + const d = new frappe.ui.Dialog({ + title: __('Add Custom Fields'), + fields: this.custom_fields, + primary_action: (values) => { + this.refresh(values); + d.hide(); + } + }); + + d.show(); + }, + standard: true + }, { label: __('User Permissions'), action: () => frappe.set_route('List', 'User Permission', { @@ -979,6 +996,64 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { ]; } + get_linked_doctypes() { + + let doctypes = []; + let dynamic_links = []; + let dynamic_doctypes = new Set(); + + this.columns.forEach(df => { + if (df.fieldtype == "Link" && df.options) { + doctypes.push(df.options); + } + else if (df.fieldtype == "Dynamic Link" && df.options) { + dynamic_links.push(df.options); + } + }); + + this.data.forEach(row => { + dynamic_links.forEach(field => { + if (row[field]){ + dynamic_doctypes.add(row[field]); + } + }) + }) + + doctypes = doctypes.concat(Array.from(dynamic_doctypes)); + + return doctypes; + } + + get_dialog_fields() { + var dialog_fields = []; + const linked_doctypes = this.get_linked_doctypes(); + + frappe.call({ + method: "frappe.desk.query_report.get_custom_fields", + args: { + doctypes: linked_doctypes + }, + callback: function(r) { + r.message.forEach(df => { + dialog_fields.push({ + label: __(df.doctype), + fieldname: df.doctype, + fieldtype: 'MultiCheck', + columns: 2, + options: df.fields + .map(f => ({ + label: __(f), + value: f ? frappe.scrub(f) : null, + checked: 0 + })) + }); + }); + } + }); + + return dialog_fields; + } + setup_report_wrapper() { if (this.$report) return; From bc7e68a6f9d5314f0c71a24c6090175640a24b0a Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Sun, 7 Apr 2019 20:33:18 +0530 Subject: [PATCH 03/10] fix: Remove add fields checkbox from report doctype --- frappe/core/doctype/report/report.json | 68 +------------------------- 1 file changed, 1 insertion(+), 67 deletions(-) diff --git a/frappe/core/doctype/report/report.json b/frappe/core/doctype/report/report.json index bc62c4b4c2..a26bc3164f 100644 --- a/frappe/core/doctype/report/report.json +++ b/frappe/core/doctype/report/report.json @@ -639,72 +639,6 @@ "set_only_once": 0, "translatable": 0, "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "customize_section", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Customize", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "add_custom_fields_in_report", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Add Custom Fields In Report", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 } ], "has_web_view": 0, @@ -718,7 +652,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2019-04-03 20:39:41.786686", + "modified": "2019-04-07 20:32:30.943582", "modified_by": "Administrator", "module": "Core", "name": "Report", From e60d032224fbf76badccc17560beeccb72ea95db Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Sun, 7 Apr 2019 22:14:31 +0530 Subject: [PATCH 04/10] fix: Codacy fixes --- frappe/desk/query_report.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/desk/query_report.py b/frappe/desk/query_report.py index bc6adff8ef..dd65cce421 100644 --- a/frappe/desk/query_report.py +++ b/frappe/desk/query_report.py @@ -390,7 +390,7 @@ def get_filtered_data(ref_doctype, columns, data, user, custom_columns): custom_field_list = [] for doctype, field_list in iteritems(fields): - values = frappe.db.sql("select name, {fields} from `tab{doctype}` " + values = frappe.db.sql("select name, {fields} from `tab{doctype}` " #nosec .format(fields = ", ".join(field_list), doctype=doctype), as_dict=1) custom_field_list += field_list From 3a5166c423ef430454ef26de07f45208823504f0 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Wed, 10 Apr 2019 12:49:59 +0530 Subject: [PATCH 05/10] feat: Allow user to add custom columns in report --- frappe/desk/query_report.py | 68 ++------- .../js/frappe/views/reports/query_report.js | 142 ++++++++++++------ 2 files changed, 104 insertions(+), 106 deletions(-) diff --git a/frappe/desk/query_report.py b/frappe/desk/query_report.py index dd65cce421..b7d12a83de 100644 --- a/frappe/desk/query_report.py +++ b/frappe/desk/query_report.py @@ -34,7 +34,7 @@ def get_report_doc(report_name): return doc -def generate_report_result(report, filters=None, user=None, custom_columns=None): +def generate_report_result(report, filters=None, user=None): status = None if not user: user = frappe.session.user @@ -84,7 +84,7 @@ def generate_report_result(report, filters=None, user=None, custom_columns=None) data_to_be_printed = res[4] if result: - result, columns = get_filtered_data(report.ref_doctype, columns, result, user, custom_columns) + result, columns = get_filtered_data(report.ref_doctype, columns, result, user) if cint(report.add_total_row) and result: result = add_total_row(result, columns) @@ -161,7 +161,7 @@ def get_script(report_name): @frappe.whitelist() @frappe.read_only() -def run(report_name, filters=None, user=None, custom_columns=None): +def run(report_name, filters=None, user=None): report = get_report_doc(report_name) if not user: @@ -183,7 +183,7 @@ def run(report_name, filters=None, user=None, custom_columns=None): dn = "" result = get_prepared_report_result(report, filters, dn, user) else: - result = generate_report_result(report, filters, user, custom_columns) + result = generate_report_result(report, filters, user) result["add_total_row"] = report.add_total_row @@ -355,26 +355,15 @@ def add_total_row(result, columns, meta = None): return result @frappe.whitelist() -def get_custom_fields(doctypes): +def get_data_for_custom_field(doctype, field): - field_map = [] + value_map = frappe._dict(frappe.get_all(doctype, + fields=["name", field], + as_list=1)) - doclist = json.loads(doctypes) + return value_map - for d in doclist: - fieldlist = [f.label for f in frappe.get_meta(d).fields \ - if f.label and f.fieldname and f.fieldname not in no_value_fields - and f.fieldname not in ["naming_series"] - and f.fieldtype not in ["Section Break", "Column Break", "Table"]] - - field_map.append({ - "doctype": d, - "fields": fieldlist - }) - - return field_map - -def get_filtered_data(ref_doctype, columns, data, user, custom_columns): +def get_filtered_data(ref_doctype, columns, data, user): result = [] linked_doctypes = get_linked_doctypes(columns, data) match_filters_per_doctype = get_user_match_filters(linked_doctypes, user=user) @@ -384,43 +373,6 @@ def get_filtered_data(ref_doctype, columns, data, user, custom_columns): role_permissions = get_role_permissions(frappe.get_meta(ref_doctype), user) if_owner = role_permissions.get("if_owner", {}).get("report") - if custom_columns: - custom_field_value_map = {} - fields = json.loads(custom_columns) - custom_field_list = [] - - for doctype, field_list in iteritems(fields): - values = frappe.db.sql("select name, {fields} from `tab{doctype}` " #nosec - .format(fields = ", ".join(field_list), doctype=doctype), as_dict=1) - - custom_field_list += field_list - for field in field_list: - columns.append({ - "label": frappe.unscrub(field), - "fieldname": field, - "fieldtype": "Data", - "width": 100 - }) - - for value in values: - custom_field_value_map.setdefault(value.name, value) - - columns_dict = get_columns_dict(columns) - columns_idx_map = [columns_dict.get(i) for i in range(len(columns))] - - for row in data: - for index, column in enumerate(columns_idx_map): - for d in custom_field_list: - if column.get("fieldtype") == "Link" or column.get("fieldtype") == "Dynamic Link": - if isinstance(row, dict): - fieldname = column.get("fieldname") - row.update({ fieldname: custom_field_value_map.get(row.get(fieldname),{}).get(d)}) - else: - fieldname = column.get("fieldname") - value = custom_field_value_map.get(row[index],{}).get(d) - if value: - row.append(value) - if match_filters_per_doctype: for row in data: # Why linked_doctypes.get(ref_doctype)? because if column is empty, linked_doctypes[ref_doctype] is removed diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index 0beb56005b..726478b776 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -269,7 +269,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { this.page.clear_fields(); } - refresh(values) { + refresh() { this.toggle_message(true); let filters = this.get_filter_values(true); let query = frappe.utils.get_query_string(frappe.get_route_str()); @@ -291,7 +291,6 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { args: { report_name: this.report_name, filters: filters, - custom_columns: values }, callback: resolve, always: () => this.page.btn_secondary.prop('disabled', false) @@ -322,7 +321,6 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { this.add_prepared_report_buttons(data.doc); } this.toggle_message(false); - if (data.result && data.result.length) { this.prepare_report_data(data); @@ -419,12 +417,13 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { this.raw_data = data; this.columns = this.prepare_columns(data.columns); this.data = this.prepare_data(data.result); - this.custom_fields = this.get_dialog_fields(); + this.linked_doctypes = this.get_linked_doctypes(); this.tree_report = this.data.some(d => 'indent' in d); } render_datatable() { let data = this.data; + if (this.raw_data.add_total_row) { data = data.slice(); data.splice(-1, 1); @@ -964,16 +963,63 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { standard: true }, { - label: __('Add Custom Fields'), + label: __('Add Column'), action: () => { - const d = new frappe.ui.Dialog({ - title: __('Add Custom Fields'), - fields: this.custom_fields, + let d = new frappe.ui.Dialog({ + title: __('Add Column'), + fields: [ + { + fieldtype: 'Select', + fieldname: 'doctype', + label: 'DocType', + options: this.linked_doctypes.map(df => ({ label: df.doctype, value: df.doctype })), + change: () => { + let doctype = d.get_value('doctype'); + frappe.model.with_doctype(doctype, () => { + let fields = frappe.meta.get_docfields(doctype) + .map(df => ({ label: df.label, value: df.fieldname })); + d.set_df_property('field', 'options', fields); + + }) + } + }, + { + fieldtype: 'Select', + label: 'Field', + fieldname: 'field', + options: [] + }, + { + fieldtype: 'Select', + label: 'Insert After', + fieldname: 'insert_after', + options: this.columns.map(df => df.label) + } + ], primary_action: (values) => { - this.refresh(values); - d.hide(); + const custom_columns = []; + let df = frappe.meta.get_docfield(values.doctype, values.field); + custom_columns.push({ + fieldname: df.fieldname, + fieldtype: df.fieldtype, + label: df.label, + width: 100 + }); + frappe.call({ + method: 'frappe.desk.query_report.get_data_for_custom_field', + args: { + field: values.field, + doctype: values.doctype + }, + 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); + d.hide(); + } + }); } - }); + }) d.show(); }, @@ -996,64 +1042,64 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { ]; } - get_linked_doctypes() { + add_custom_column(custom_column, custom_data, link_field, column_field, insert_after) { + 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 => { + row[column_field] = custom_data[row[link_field]] + }) + + this.render_datatable(); + } + + get_linked_doctypes() { let doctypes = []; let dynamic_links = []; let dynamic_doctypes = new Set(); + this.doctype_field_map = {} this.columns.forEach(df => { - if (df.fieldtype == "Link" && df.options) { - doctypes.push(df.options); + if (df.fieldtype == "Link" && df.options && df.options != "Currency") { + doctypes.push({ + doctype: df.options, + fieldname: df.fieldname + }); } else if (df.fieldtype == "Dynamic Link" && df.options) { - dynamic_links.push(df.options); + dynamic_links.push({ + link_name: df.options, + fieldname: df.fieldname + }); } }); this.data.forEach(row => { dynamic_links.forEach(field => { - if (row[field]){ - dynamic_doctypes.add(row[field]); + if (row[field.link_name]){ + dynamic_doctypes.add(row[field.link_name] + ":" + field.fieldname); } }) }) - doctypes = doctypes.concat(Array.from(dynamic_doctypes)); + doctypes = doctypes.concat(Array.from(dynamic_doctypes).map(d => { + const doc_field_pair = d.split(":"); + return { + doctype: doc_field_pair[0], + fieldname: doc_field_pair[1] + } + })); + + doctypes.forEach(doc => { + this.doctype_field_map[doc.doctype] = doc.fieldname; + }) return doctypes; } - get_dialog_fields() { - var dialog_fields = []; - const linked_doctypes = this.get_linked_doctypes(); - - frappe.call({ - method: "frappe.desk.query_report.get_custom_fields", - args: { - doctypes: linked_doctypes - }, - callback: function(r) { - r.message.forEach(df => { - dialog_fields.push({ - label: __(df.doctype), - fieldname: df.doctype, - fieldtype: 'MultiCheck', - columns: 2, - options: df.fields - .map(f => ({ - label: __(f), - value: f ? frappe.scrub(f) : null, - checked: 0 - })) - }); - }); - } - }); - - return dialog_fields; - } - setup_report_wrapper() { if (this.$report) return; From 35165a7e69b759ad2cc09bd2a77d98976afd34ee Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Wed, 10 Apr 2019 12:51:39 +0530 Subject: [PATCH 06/10] fix: Find footer buttons only in case of tree report --- frappe/public/js/frappe/views/reports/query_report.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index 726478b776..272842d567 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -1138,9 +1138,9 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { ${__('Collapse All')} `); this.page.footer.before(this.$tree_footer); + this.$tree_footer.find('[data-action=collapse_all_rows]').show(); + this.$tree_footer.find('[data-action=expand_all_rows]').hide(); } - this.$tree_footer.find('[data-action=collapse_all_rows]').show(); - this.$tree_footer.find('[data-action=expand_all_rows]').hide(); } expand_all_rows() { From 327caada3224e0d736318c2848ed52428d98e654 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Wed, 10 Apr 2019 13:02:25 +0530 Subject: [PATCH 07/10] fix: Removed unused imports and data passing --- frappe/desk/query_report.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/frappe/desk/query_report.py b/frappe/desk/query_report.py index b7d12a83de..5790c42878 100644 --- a/frappe/desk/query_report.py +++ b/frappe/desk/query_report.py @@ -17,7 +17,6 @@ from six import string_types, iteritems from datetime import timedelta from frappe.utils.file_manager import get_file from frappe.utils import gzip_decompress -from frappe.model import no_value_fields def get_report_doc(report_name): doc = frappe.get_doc("Report", report_name) @@ -84,7 +83,7 @@ def generate_report_result(report, filters=None, user=None): data_to_be_printed = res[4] if result: - result, columns = get_filtered_data(report.ref_doctype, columns, result, user) + result = get_filtered_data(report.ref_doctype, columns, result, user) if cint(report.add_total_row) and result: result = add_total_row(result, columns) @@ -384,7 +383,7 @@ def get_filtered_data(ref_doctype, columns, data, user): else: result = list(data) - return result, columns + return result def has_match(row, linked_doctypes, doctype_match_filters, ref_doctype, if_owner, columns_dict, user): From a9b080ca6aed361293e9358de8f940538ea2ec69 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Wed, 10 Apr 2019 14:05:07 +0530 Subject: [PATCH 08/10] fix: Styling fixes and missing semicolon --- frappe/public/js/frappe/views/reports/query_report.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index 272842d567..2b6d0461e5 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -1050,7 +1050,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { this.columns.splice(insert_after_index + 1, 0, column[0]); this.data.forEach(row => { - row[column_field] = custom_data[row[link_field]] + row[column_field] = custom_data[row[link_field]]; }) this.render_datatable(); @@ -1060,7 +1060,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { let doctypes = []; let dynamic_links = []; let dynamic_doctypes = new Set(); - this.doctype_field_map = {} + this.doctype_field_map = {}; this.columns.forEach(df => { if (df.fieldtype == "Link" && df.options && df.options != "Currency") { @@ -1090,7 +1090,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { return { doctype: doc_field_pair[0], fieldname: doc_field_pair[1] - } + }; })); doctypes.forEach(doc => { From fc2b66dac0f47858f3639b6c511f12eedece572b Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Wed, 10 Apr 2019 14:49:34 +0530 Subject: [PATCH 09/10] fix: Rename label from doctype to from document type --- frappe/public/js/frappe/views/reports/query_report.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index 2b6d0461e5..24231de50b 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -971,7 +971,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { { fieldtype: 'Select', fieldname: 'doctype', - label: 'DocType', + label: 'From Document Type', options: this.linked_doctypes.map(df => ({ label: df.doctype, value: df.doctype })), change: () => { let doctype = d.get_value('doctype'); From 3cd899dfab55285464baeb521c5b541865b82f13 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Wed, 10 Apr 2019 15:34:18 +0530 Subject: [PATCH 10/10] fix: Translation fixes --- .../js/frappe/views/reports/query_report.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index 24231de50b..de4bd03e75 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -971,7 +971,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { { fieldtype: 'Select', fieldname: 'doctype', - label: 'From Document Type', + label: __('From Document Type'), options: this.linked_doctypes.map(df => ({ label: df.doctype, value: df.doctype })), change: () => { let doctype = d.get_value('doctype'); @@ -980,18 +980,18 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { .map(df => ({ label: df.label, value: df.fieldname })); d.set_df_property('field', 'options', fields); - }) + }); } }, { fieldtype: 'Select', - label: 'Field', + label: __('Field'), fieldname: 'field', options: [] }, { fieldtype: 'Select', - label: 'Insert After', + label: __('Insert After'), fieldname: 'insert_after', options: this.columns.map(df => df.label) } @@ -1051,7 +1051,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { this.data.forEach(row => { row[column_field] = custom_data[row[link_field]]; - }) + }); this.render_datatable(); } @@ -1082,8 +1082,8 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { if (row[field.link_name]){ dynamic_doctypes.add(row[field.link_name] + ":" + field.fieldname); } - }) - }) + }); + }); doctypes = doctypes.concat(Array.from(dynamic_doctypes).map(d => { const doc_field_pair = d.split(":"); @@ -1095,7 +1095,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { doctypes.forEach(doc => { this.doctype_field_map[doc.doctype] = doc.fieldname; - }) + }); return doctypes; }