Merge pull request #25358 from akhilnarang/bulk-print-background
feat: move bulk print operation to the background
This commit is contained in:
commit
6c11daee56
2 changed files with 161 additions and 47 deletions
|
|
@ -10,6 +10,7 @@ export default class BulkOperations {
|
|||
const is_submittable = frappe.model.is_submittable(this.doctype);
|
||||
const allow_print_for_cancelled = cint(print_settings.allow_print_for_cancelled);
|
||||
const letterheads = this.get_letterhead_options();
|
||||
const MAX_PRINT_LIMIT = 500;
|
||||
|
||||
const valid_docs = docs
|
||||
.filter((doc) => {
|
||||
|
|
@ -35,8 +36,10 @@ export default class BulkOperations {
|
|||
return;
|
||||
}
|
||||
|
||||
if (valid_docs.length > 50) {
|
||||
frappe.msgprint(__("You can only print upto 50 documents at a time"));
|
||||
if (valid_docs.length > MAX_PRINT_LIMIT) {
|
||||
frappe.msgprint(
|
||||
__("You can only print upto {0} documents at a time", [MAX_PRINT_LIMIT])
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -102,28 +105,34 @@ export default class BulkOperations {
|
|||
pdf_options = JSON.stringify({ "page-size": args.page_size });
|
||||
}
|
||||
|
||||
const w = window.open(
|
||||
"/api/method/frappe.utils.print_format.download_multi_pdf?" +
|
||||
"doctype=" +
|
||||
encodeURIComponent(this.doctype) +
|
||||
"&name=" +
|
||||
encodeURIComponent(json_string) +
|
||||
"&format=" +
|
||||
encodeURIComponent(print_format) +
|
||||
"&no_letterhead=" +
|
||||
(with_letterhead ? "0" : "1") +
|
||||
"&letterhead=" +
|
||||
encodeURIComponent(letterhead) +
|
||||
"&options=" +
|
||||
encodeURIComponent(pdf_options)
|
||||
);
|
||||
|
||||
if (!w) {
|
||||
frappe.msgprint(__("Please enable pop-ups"));
|
||||
return;
|
||||
}
|
||||
frappe
|
||||
.call("frappe.utils.print_format.download_multi_pdf_async", {
|
||||
doctype: this.doctype,
|
||||
name: json_string,
|
||||
format: print_format,
|
||||
no_letterhead: with_letterhead ? "0" : "1",
|
||||
letterhead: letterhead,
|
||||
options: pdf_options,
|
||||
})
|
||||
.then((response) => {
|
||||
let task_id = response.message.task_id;
|
||||
frappe.realtime.task_subscribe(task_id);
|
||||
frappe.realtime.on(`task_complete:${task_id}`, (data) => {
|
||||
frappe.msgprint({
|
||||
title: __("Bulk PDF Export"),
|
||||
message: __("Your PDF is ready for download"),
|
||||
primary_action: {
|
||||
label: __("Download PDF"),
|
||||
client_action: "window.open",
|
||||
args: data.file_url,
|
||||
},
|
||||
});
|
||||
frappe.realtime.task_unsubscribe(task_id);
|
||||
frappe.realtime.off(`task_complete:${task_id}`);
|
||||
});
|
||||
dialog.hide();
|
||||
});
|
||||
});
|
||||
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
import http
|
||||
import json
|
||||
import os
|
||||
import uuid
|
||||
from io import BytesIO
|
||||
|
||||
from pypdf import PdfWriter
|
||||
|
|
@ -19,14 +22,68 @@ from frappe.www.printview import validate_print_permission
|
|||
|
||||
|
||||
@frappe.whitelist()
|
||||
def download_multi_pdf(doctype, name, format=None, no_letterhead=False, letterhead=None, options=None):
|
||||
def download_multi_pdf(
|
||||
doctype: str | dict[str, list[str]],
|
||||
name: str | list[str],
|
||||
format: str | None = None,
|
||||
no_letterhead: bool = False,
|
||||
letterhead: str | None = None,
|
||||
options: str | None = None,
|
||||
):
|
||||
"""
|
||||
Calls _download_multi_pdf with the given parameters and returns the response
|
||||
"""
|
||||
return _download_multi_pdf(doctype, name, format, no_letterhead, options)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def download_multi_pdf_async(
|
||||
doctype: str | dict[str, list[str]],
|
||||
name: str | list[str],
|
||||
format: str | None = None,
|
||||
no_letterhead: bool = False,
|
||||
letterhead: str | None = None,
|
||||
options: str | None = None,
|
||||
):
|
||||
"""
|
||||
Calls _download_multi_pdf with the given parameters in a background job, returns task ID
|
||||
"""
|
||||
task_id = str(uuid.uuid4())
|
||||
if isinstance(doctype, dict):
|
||||
doc_count = sum([len(doctype[dt]) for dt in doctype])
|
||||
else:
|
||||
doc_count = len(json.loads(name))
|
||||
|
||||
frappe.enqueue(
|
||||
_download_multi_pdf,
|
||||
doctype=doctype,
|
||||
name=name,
|
||||
task_id=task_id,
|
||||
format=format,
|
||||
no_letterhead=no_letterhead,
|
||||
letterhead=letterhead,
|
||||
options=options,
|
||||
queue="long" if doc_count > 20 else "short",
|
||||
)
|
||||
frappe.local.response["http_status_code"] = http.HTTPStatus.CREATED
|
||||
return {"task_id": task_id}
|
||||
|
||||
|
||||
def _download_multi_pdf(
|
||||
doctype: str | dict[str, list[str]],
|
||||
name: str | list[str],
|
||||
format: str | None = None,
|
||||
no_letterhead: bool = False,
|
||||
letterhead: str | None = None,
|
||||
options: str | None = None,
|
||||
task_id: str | None = None,
|
||||
):
|
||||
"""Return a PDF compiled by concatenating multiple documents.
|
||||
|
||||
The documents can be from a single DocType or multiple DocTypes.
|
||||
|
||||
Note: The design may seem a little weird, but it exists exists to
|
||||
ensure backward compatibility. The correct way to use this function is to
|
||||
pass a dict to doctype as described below
|
||||
Note: The design may seem a little weird, but it exists to ensure backward compatibility.
|
||||
The correct way to use this function is to pass a dict to doctype as described below
|
||||
|
||||
NEW FUNCTIONALITY
|
||||
=================
|
||||
|
|
@ -51,10 +108,9 @@ def download_multi_pdf(doctype, name, format=None, no_letterhead=False, letterhe
|
|||
Print Format to be used
|
||||
|
||||
Returns:
|
||||
PDF: A PDF generated by the concatenation of the mentioned input docs
|
||||
Publishes a link to the PDF to the given task ID
|
||||
"""
|
||||
|
||||
import json
|
||||
filename = ""
|
||||
|
||||
pdf_writer = PdfWriter()
|
||||
|
||||
|
|
@ -63,24 +119,47 @@ def download_multi_pdf(doctype, name, format=None, no_letterhead=False, letterhe
|
|||
|
||||
if not isinstance(doctype, dict):
|
||||
result = json.loads(name)
|
||||
total_docs = len(result)
|
||||
filename = f"{doctype}_"
|
||||
|
||||
# Concatenating pdf files
|
||||
for ss in result:
|
||||
pdf_writer = frappe.get_print(
|
||||
doctype,
|
||||
ss,
|
||||
format,
|
||||
as_pdf=True,
|
||||
output=pdf_writer,
|
||||
no_letterhead=no_letterhead,
|
||||
letterhead=letterhead,
|
||||
pdf_options=options,
|
||||
for idx, ss in enumerate(result):
|
||||
try:
|
||||
pdf_writer = frappe.get_print(
|
||||
doctype,
|
||||
ss,
|
||||
format,
|
||||
as_pdf=True,
|
||||
output=pdf_writer,
|
||||
no_letterhead=no_letterhead,
|
||||
letterhead=letterhead,
|
||||
pdf_options=options,
|
||||
)
|
||||
except Exception:
|
||||
if task_id:
|
||||
frappe.publish_realtime(task_id=task_id, message={"message": "Failed"})
|
||||
|
||||
# Publish progress
|
||||
if task_id:
|
||||
frappe.publish_progress(
|
||||
percent=(idx + 1) / total_docs * 100,
|
||||
title=_("PDF Generation in Progress"),
|
||||
description=_(
|
||||
f"{idx + 1}/{total_docs} complete | Please leave this tab open until completion."
|
||||
),
|
||||
task_id=task_id,
|
||||
)
|
||||
|
||||
if task_id is None:
|
||||
frappe.local.response.filename = "{doctype}.pdf".format(
|
||||
doctype=doctype.replace(" ", "-").replace("/", "-")
|
||||
)
|
||||
frappe.local.response.filename = "{doctype}.pdf".format(
|
||||
doctype=doctype.replace(" ", "-").replace("/", "-")
|
||||
)
|
||||
|
||||
else:
|
||||
total_docs = sum([len(doctype[dt]) for dt in doctype])
|
||||
count = 1
|
||||
for doctype_name in doctype:
|
||||
filename += f"{doctype_name}_"
|
||||
for doc_name in doctype[doctype_name]:
|
||||
try:
|
||||
pdf_writer = frappe.get_print(
|
||||
|
|
@ -94,19 +173,45 @@ def download_multi_pdf(doctype, name, format=None, no_letterhead=False, letterhe
|
|||
pdf_options=options,
|
||||
)
|
||||
except Exception:
|
||||
if task_id:
|
||||
frappe.publish_realtime(task_id=task_id, message="Failed")
|
||||
frappe.log_error(
|
||||
title="Error in Multi PDF download",
|
||||
message=f"Permission Error on doc {doc_name} of doctype {doctype_name}",
|
||||
reference_doctype=doctype_name,
|
||||
reference_name=doc_name,
|
||||
)
|
||||
frappe.local.response.filename = f"{name}.pdf"
|
||||
|
||||
count += 1
|
||||
|
||||
if task_id:
|
||||
frappe.publish_progress(
|
||||
percent=count / total_docs * 100,
|
||||
title=_("PDF Generation in Progress"),
|
||||
description=_(
|
||||
f"{count}/{total_docs} complete | Please leave this tab open until completion."
|
||||
),
|
||||
task_id=task_id,
|
||||
)
|
||||
if task_id is None:
|
||||
frappe.local.response.filename = f"{name}.pdf"
|
||||
|
||||
with BytesIO() as merged_pdf:
|
||||
pdf_writer.write(merged_pdf)
|
||||
frappe.local.response.filecontent = merged_pdf.getvalue()
|
||||
|
||||
frappe.local.response.type = "pdf"
|
||||
if task_id:
|
||||
_file = frappe.get_doc(
|
||||
{
|
||||
"doctype": "File",
|
||||
"file_name": f"{filename}{task_id}.pdf",
|
||||
"content": merged_pdf.getvalue(),
|
||||
"is_private": 1,
|
||||
}
|
||||
)
|
||||
_file.save()
|
||||
frappe.publish_realtime(f"task_complete:{task_id}", message={"file_url": _file.unique_url})
|
||||
else:
|
||||
frappe.local.response.filecontent = merged_pdf.getvalue()
|
||||
frappe.local.response.type = "pdf"
|
||||
|
||||
|
||||
@deprecated
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue