diff --git a/frappe/core/doctype/report/report.js b/frappe/core/doctype/report/report.js index 6b48ca137a..818c5951e6 100644 --- a/frappe/core/doctype/report/report.js +++ b/frappe/core/doctype/report/report.js @@ -17,6 +17,9 @@ frappe.ui.form.on('Report', { case "Script Report": frappe.set_route("query-report", doc.name); break; + case "Custom Report": + frappe.set_route("query-report", doc.name); + break; } }, "fa fa-table"); diff --git a/frappe/core/doctype/report/report.json b/frappe/core/doctype/report/report.json index a26bc3164f..8c30b8e328 100644 --- a/frappe/core/doctype/report/report.json +++ b/frappe/core/doctype/report/report.json @@ -79,6 +79,39 @@ "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": "reference_report", + "fieldtype": "Data", + "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": "Reference 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 + }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -228,7 +261,7 @@ "label": "Report Type", "length": 0, "no_copy": 0, - "options": "Report Builder\nQuery Report\nScript Report", + "options": "Report Builder\nQuery Report\nScript Report\nCustom Report", "permlevel": 0, "print_hide": 0, "print_hide_if_no_value": 0, @@ -479,7 +512,7 @@ "bold": 0, "collapsible": 0, "columns": 0, - "depends_on": "eval:doc.report_type==\"Report Builder\"", + "depends_on": "eval:doc.report_type==\"Report Builder\" || \"Custom Report\"", "fetch_if_empty": 0, "fieldname": "json", "fieldtype": "Code", @@ -642,17 +675,15 @@ } ], "has_web_view": 0, - "hide_heading": 0, "hide_toolbar": 0, "icon": "", "idx": 1, - "image_view": 0, "in_create": 0, "is_submittable": 0, "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2019-04-07 20:32:30.943582", + "modified": "2019-04-12 15:53:14.194591", "modified_by": "Administrator", "module": "Core", "name": "Report", @@ -737,7 +768,6 @@ ], "quick_entry": 0, "read_only": 0, - "read_only_onload": 0, "show_name_in_global_search": 1, "sort_field": "modified", "sort_order": "DESC", diff --git a/frappe/desk/query_report.py b/frappe/desk/query_report.py index 0eed37a86a..a8f8e9b334 100644 --- a/frappe/desk/query_report.py +++ b/frappe/desk/query_report.py @@ -18,6 +18,15 @@ from datetime import timedelta from frappe.utils import gzip_decompress def get_report_doc(report_name): + + custom_report = custom_columns = frappe.db.get_value("Report", + {'report_name':report_name, 'is_standard': 'No', 'report_type': 'Custom Report'}, + ['reference_report', 'json'] + ) + + if custom_report: + report_name, custom_columns = custom_report[0], custom_report[1] + doc = frappe.get_doc("Report", report_name) if not doc.is_permitted(): frappe.throw(_("You don't have access to Report: {0}").format(report_name), frappe.PermissionError) @@ -29,10 +38,10 @@ def get_report_doc(report_name): if doc.disabled: frappe.throw(_("Report {0} is disabled").format(report_name)) - return doc + return doc, custom_columns -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 @@ -81,6 +90,11 @@ def generate_report_result(report, filters=None, user=None): if len(res) > 4: data_to_be_printed = res[4] + + if custom_columns: + columns = json.loads(custom_columns) + result = add_data_to_custom_columns(columns, result) + if result: result = get_filtered_data(report.ref_doctype, columns, result, user) @@ -102,7 +116,7 @@ def background_enqueue_run(report_name, filters=None, user=None): """run reports in background""" if not user: user = frappe.session.user - report = get_report_doc(report_name) + report, _ = get_report_doc(report_name) track_instance = \ frappe.get_doc({ "doctype": "Prepared Report", @@ -125,8 +139,7 @@ def background_enqueue_run(report_name, filters=None, user=None): @frappe.whitelist() def get_script(report_name): - report = get_report_doc(report_name) - + report, _ = get_report_doc(report_name) module = report.module or frappe.db.get_value("DocType", report.ref_doctype, "module") module_path = get_module_path(module) report_folder = os.path.join(module_path, "report", scrub(report.name)) @@ -161,7 +174,7 @@ def get_script(report_name): @frappe.read_only() def run(report_name, filters=None, user=None): - report = get_report_doc(report_name) + report, custom_columns = get_report_doc(report_name) if not user: user = frappe.session.user if not frappe.has_permission(report.ref_doctype, "report"): @@ -181,12 +194,37 @@ 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 return result +def add_data_to_custom_columns(columns, result): + + custom_fields_data = get_data_for_custom_report(columns) + + data = [] + for row in result: + row_obj = {} + if isinstance(row, list): + for idx, column in enumerate(columns): + if column.get('link_field'): + row_obj[column['fieldname']] = None + row.insert(idx,None) + else: + row_obj[column['fieldname']] = row[idx] + data.append(row_obj) + else: + data.append(row) + + for row in data: + for column in columns: + if column.get('link_field'): + row[column['fieldname']] = \ + custom_fields_data.get((column['doctype'], column['fieldname']), {}).get(row[column['link_field']]) + + return data def get_prepared_report_result(report, filters, dn="", user=None): latest_report_data = {} @@ -362,6 +400,44 @@ def get_data_for_custom_field(doctype, field): return value_map +def get_data_for_custom_report(columns): + doc_field_value_map = {} + + for column in columns: + if column.get('link_field'): + fieldname = column.get('fieldname') + doctype = column.get('doctype') + doc_field_value_map[(doctype, fieldname)] = get_data_for_custom_field(doctype, fieldname) + + return doc_field_value_map + +@frappe.whitelist() +def save_report(reference_report, report_name, columns): + + report_doc, _ = get_report_doc(reference_report) + + docname = frappe.db.exists("Report", report_name) + if docname: + report = frappe.get_doc("Report", {'report_name':docname, 'is_standard': 'No', 'report_type': 'Custom Report'}) + report.update({"json": columns}) + report.save() + frappe.msgprint("Report updated successfully") + + return docname + else: + new_report = frappe.get_doc({ + 'doctype': 'Report', + 'report_name': report_name, + 'json': columns, + 'ref_doctype': report_doc.ref_doctype, + 'is_standard': 'No', + 'report_type': 'Custom Report', + 'reference_report': reference_report + }).insert(ignore_permissions = True) + frappe.msgprint("{0} saved successfully".format(new_report.name)) + return new_report.name + + def get_filtered_data(ref_doctype, columns, data, user): result = [] linked_doctypes = get_linked_doctypes(columns, data) diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index 31e4c0664a..83b0924341 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -110,9 +110,21 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { this.page_name = frappe.get_route_str(); this.report_name = this.route[1]; this.page_title = __(this.report_name); + this.show_save = false; this.menu_items = this.get_menu_items(); this.datatable = null; this.prepared_report_action = "New"; + this.custom_report = null; + + frappe.db.get_value("Report", + {"report_name": this.report_name}, + 'reference_report', (r) => { + if (r.reference_report){ + this.custom_report = this.report_name; + this.report_name = r.reference_report; + } + } + ) frappe.run_serially([ () => this.get_report_doc(), @@ -289,7 +301,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { method: 'frappe.desk.query_report.run', type: 'GET', args: { - report_name: this.report_name, + report_name: this.custom_report || this.report_name, filters: filters, }, callback: resolve, @@ -1003,8 +1015,11 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { fieldname: df.fieldname, fieldtype: df.fieldtype, label: df.label, + link_field: this.doctype_field_map[values.doctype], + doctype: values.doctype, width: 100 }); + frappe.call({ method: 'frappe.desk.query_report.get_data_for_custom_field', args: { @@ -1018,6 +1033,8 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { d.hide(); } }); + this.show_save = true; + this.set_menu_items() } }) @@ -1025,6 +1042,40 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { }, standard: true }, + { + label: __('Save'), + action: () => { + let d = new frappe.ui.Dialog({ + title: __('Save Reports'), + fields: [ + { + fieldtype: 'Data', + fieldname: 'report_name', + label: __("Report Name"), + default: this.report_doc.is_standard == 'No' ? this.custom_report : "", + } + ], + primary_action: (values) => { + frappe.call({ + method: "frappe.desk.query_report.save_report", + args: { + reference_report: this.report_name, + report_name: values.report_name, + columns: this.columns + }, + callback: function(r) { + this.show_save = false; + d.hide(); + frappe.set_route('query-report', r.message); + } + }); + } + }); + d.show() + }, + condition: () => this.show_save, + standard: true + }, { label: __('User Permissions'), action: () => frappe.set_route('List', 'User Permission', {