diff --git a/frappe/core/doctype/prepared_report/prepared_report.js b/frappe/core/doctype/prepared_report/prepared_report.js index aa55de9bbe..66265fbf74 100644 --- a/frappe/core/doctype/prepared_report/prepared_report.js +++ b/frappe/core/doctype/prepared_report/prepared_report.js @@ -44,6 +44,24 @@ frappe.ui.form.on("Prepared Report", { frappe.route_options = { prepared_report_name: frm.doc.name }; frappe.set_route("query-report", frm.doc.report_name); }); + let csv_attached = (frm.get_files() || []).some((f) => f.file_url.endsWith(".csv")); + if (!csv_attached) { + frm.add_custom_button(__("Download as CSV"), function () { + frappe.call({ + method: "frappe.core.doctype.prepared_report.prepared_report.enqueue_json_to_csv_conversion", + args: { + prepared_report_name: frm.doc.name, + }, + callback: function () { + frappe.msgprint( + __( + "Your CSV file is being generated and will appear in the Attachments section once ready. Additionally, you will get notified when the file is available for download." + ) + ); + }, + }); + }); + } } }, }); diff --git a/frappe/core/doctype/prepared_report/prepared_report.py b/frappe/core/doctype/prepared_report/prepared_report.py index 32e5f3ea40..2c8fdc2e05 100644 --- a/frappe/core/doctype/prepared_report/prepared_report.py +++ b/frappe/core/doctype/prepared_report/prepared_report.py @@ -85,7 +85,12 @@ class PreparedReport(Document): def get_prepared_data(self, with_file_name=False): if attachments := get_attachments(self.doctype, self.name): - attachment = attachments[0] + attachment = None + for f in attachments or []: + if f.file_url.endswith(".gz"): + attachment = f + break + attached_file = frappe.get_doc("File", attachment.name) if with_file_name: @@ -297,3 +302,67 @@ def has_permission(doc, user): return True return doc.report_name in user.get_all_reports().keys() + + +@frappe.whitelist() +def enqueue_json_to_csv_conversion(prepared_report_name): + """Call this to enqueue the conversion in background.""" + enqueue(method=convert_json_to_csv, queue="long", prepared_report_name=prepared_report_name) + + +def convert_json_to_csv(prepared_report_name): + """Background job: Fetch JSON file, convert to CSV, attach CSV to Prepared Report.""" + + import csv + from io import StringIO + + doc = frappe.get_doc("Prepared Report", prepared_report_name) + json_content, file_name = doc.get_prepared_data(with_file_name=True) + + if not json_content: + frappe.log_error(f"No JSON content found for {prepared_report_name}", "CSV Conversion") + return + + parsed = json.loads(json_content) + + columns = parsed.get("columns", []) + result = parsed.get("result", []) + + if not columns or not result: + frappe.log_error("Columns or result is empty", "CSV Conversion") + return + + fieldnames = [col.get("fieldname") for col in columns if col.get("fieldname")] + + output = StringIO() + writer = csv.DictWriter(output, fieldnames=fieldnames) + writer.writeheader() + for row in result: + writer.writerow({key: row.get(key, "") for key in fieldnames}) + + csv_content = output.getvalue().encode("utf-8") + + _file = frappe.get_doc( + { + "doctype": "File", + "file_name": f"csv_{file_name[:-8]}.csv", + "attached_to_doctype": "Prepared Report", + "attached_to_name": prepared_report_name, + "content": csv_content, + "is_private": 1, + } + ) + _file.save(ignore_permissions=True) + + frappe.get_doc( + { + "doctype": "Notification Log", + "subject": "Your CSV file is ready for download", + "email_content": f'Click here to download the file.', + "for_user": frappe.session.user, + "type": "Alert", + "document_type": "File", + "document_name": _file.name, + "link": _file.file_url, + } + ).insert(ignore_permissions=True) diff --git a/frappe/desk/doctype/notification_log/notification_log.json b/frappe/desk/doctype/notification_log/notification_log.json index 3228392a92..4411780a9e 100644 --- a/frappe/desk/doctype/notification_log/notification_log.json +++ b/frappe/desk/doctype/notification_log/notification_log.json @@ -96,7 +96,7 @@ }, { "fieldname": "link", - "fieldtype": "Data", + "fieldtype": "Small Text", "hidden": 1, "label": "Link" }, @@ -109,7 +109,7 @@ "hide_toolbar": 1, "in_create": 1, "links": [], - "modified": "2025-05-10 23:58:54.717673", + "modified": "2025-05-30 20:17:44.969738", "modified_by": "Administrator", "module": "Desk", "name": "Notification Log", diff --git a/frappe/desk/doctype/notification_log/notification_log.py b/frappe/desk/doctype/notification_log/notification_log.py index ec09e01fd6..dc54833b5a 100644 --- a/frappe/desk/doctype/notification_log/notification_log.py +++ b/frappe/desk/doctype/notification_log/notification_log.py @@ -24,9 +24,10 @@ class NotificationLog(Document): document_name: DF.Data | None document_type: DF.Link | None email_content: DF.TextEditor | None + email_header: DF.Data | None for_user: DF.Link | None from_user: DF.Link | None - link: DF.Data | None + link: DF.SmallText | None read: DF.Check subject: DF.Text | None type: DF.Literal["", "Mention", "Assignment", "Share", "Alert"]