Merge pull request #7088 from karthikeyan5/raw-printing
feat(Raw Printing): Adding Support for qz-tray
This commit is contained in:
commit
7817b4e03b
9 changed files with 2063 additions and 1532 deletions
|
|
@ -140,6 +140,7 @@
|
|||
"expect": true,
|
||||
"context": true,
|
||||
"before": true,
|
||||
"beforeEach": true
|
||||
"beforeEach": true,
|
||||
"qz": true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -11,6 +11,9 @@ frappe.ui.form.PrintPreview = Class.extend({
|
|||
|
||||
// only system manager can edit
|
||||
this.wrapper.find(".btn-print-edit").toggle(frappe.user.has_role("System Manager"));
|
||||
if (frappe.model.get_doc(":Print Settings", "Print Settings").enable_raw_printing == "1") {
|
||||
this.wrapper.find(".btn-printer-setting").toggle(true);
|
||||
}
|
||||
},
|
||||
bind_events: function () {
|
||||
var me = this;
|
||||
|
|
@ -47,6 +50,10 @@ frappe.ui.form.PrintPreview = Class.extend({
|
|||
me.multilingual_preview()
|
||||
});
|
||||
|
||||
this.wrapper.find(".btn-printer-setting").click(function () {
|
||||
me.printer_setting_dialog();
|
||||
});
|
||||
|
||||
this.wrapper.find(".btn-print-print").click(function () {
|
||||
if (me.is_old_style()) {
|
||||
me.print_old_style();
|
||||
|
|
@ -125,10 +132,16 @@ frappe.ui.form.PrintPreview = Class.extend({
|
|||
multilingual_preview: function () {
|
||||
var me = this;
|
||||
if (this.is_old_style()) {
|
||||
me.wrapper.find(".btn-print-preview").toggle(true);
|
||||
me.wrapper.find(".btn-download-pdf").toggle(false);
|
||||
me.set_style();
|
||||
me.preview_old_style();
|
||||
} else if (this.is_raw_printing()) {
|
||||
me.wrapper.find(".btn-print-preview").toggle(false);
|
||||
me.wrapper.find(".btn-download-pdf").toggle(false);
|
||||
me.preview();
|
||||
} else {
|
||||
me.wrapper.find(".btn-print-preview").toggle(true);
|
||||
me.wrapper.find(".btn-download-pdf").toggle(true);
|
||||
me.preview();
|
||||
}
|
||||
|
|
@ -190,6 +203,33 @@ frappe.ui.form.PrintPreview = Class.extend({
|
|||
callback: function (data) {
|
||||
}
|
||||
});
|
||||
} else if (me.get_mapped_printer().length === 1) {
|
||||
// printer is already mapped in localstorage (applies for both raw and pdf )
|
||||
if (me.is_raw_printing()) {
|
||||
me.get_raw_commands(function (out) {
|
||||
frappe.ui.form.qz_connect().then(function () {
|
||||
let printer_map = me.get_mapped_printer()[0];
|
||||
let data = [out.raw_commands];
|
||||
let config = qz.configs.create(printer_map.printer);
|
||||
return qz.print(config, data);
|
||||
}).then(frappe.ui.form.qz_success).catch((err) => {
|
||||
frappe.ui.form.qz_fail(err);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
frappe.show_alert({
|
||||
message: __('PDF printing via "Raw Print" is not yet supported. Please remove the printer mapping in Printer Settings and try again.'),
|
||||
indicator: 'blue'
|
||||
}, 14);
|
||||
//Note: need to solve "Error: Cannot parse (FILE)<URL> as a PDF file" to enable qz pdf printing.
|
||||
}
|
||||
} else if (me.is_raw_printing()) {
|
||||
// printer not mapped in localstorage and the current print format is raw printing
|
||||
frappe.show_alert({
|
||||
message: __('Please set a printer mapping for this print format in the Printer Settings'),
|
||||
indicator: 'blue'
|
||||
}, 14);
|
||||
me.printer_setting_dialog();
|
||||
} else {
|
||||
me.new_page_preview(true);
|
||||
}
|
||||
|
|
@ -225,6 +265,41 @@ frappe.ui.form.PrintPreview = Class.extend({
|
|||
}
|
||||
});
|
||||
},
|
||||
get_raw_commands: function (callback) {
|
||||
// fetches rendered raw commands from the server for the current print format.
|
||||
frappe.call({
|
||||
method: "frappe.www.printview.get_rendered_raw_commands",
|
||||
args: {
|
||||
doc: this.frm.doc,
|
||||
print_format: this.selected_format(),
|
||||
_lang: this.lang_code
|
||||
},
|
||||
callback: function (r) {
|
||||
if (!r.exc) {
|
||||
callback(r.message);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
get_mapped_printer: function () {
|
||||
// returns a list of "print format: printer" mapping filtered by the current print format
|
||||
let print_format_printer_map = this.get_print_format_printer_map();
|
||||
if (print_format_printer_map[this.frm.doctype]) {
|
||||
return print_format_printer_map[this.frm.doctype].filter(
|
||||
(printer_map) => printer_map.print_format == this.selected_format());
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
get_print_format_printer_map: function () {
|
||||
// returns the whole object "print_format_printer_map" stored in the localStorage.
|
||||
try {
|
||||
let print_format_printer_map = JSON.parse(localStorage.print_format_printer_map);
|
||||
return print_format_printer_map;
|
||||
} catch (e) {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
preview_old_style: function () {
|
||||
var me = this;
|
||||
this.with_old_style({
|
||||
|
|
@ -270,6 +345,9 @@ frappe.ui.form.PrintPreview = Class.extend({
|
|||
is_old_style: function (format) {
|
||||
return this.get_print_format(format).print_format_type === "Client";
|
||||
},
|
||||
is_raw_printing: function (format) {
|
||||
return this.get_print_format(format).raw_printing === 1;
|
||||
},
|
||||
get_print_format: function (format) {
|
||||
if (!format) {
|
||||
format = this.selected_format();
|
||||
|
|
@ -286,6 +364,71 @@ frappe.ui.form.PrintPreview = Class.extend({
|
|||
},
|
||||
set_style: function (style) {
|
||||
frappe.dom.set_style(style || frappe.boot.print_css, "print-style");
|
||||
},
|
||||
printer_setting_dialog: function () {
|
||||
// dialog for the Printer Settings
|
||||
var me = this;
|
||||
this.print_format_printer_map = me.get_print_format_printer_map();
|
||||
this.data = [];
|
||||
this.data = this.print_format_printer_map[this.frm.doctype] || [];
|
||||
this.printer_list = [];
|
||||
frappe.ui.form.qz_get_printer_list().then((data) => {
|
||||
this.printer_list = data;
|
||||
const dialog = new frappe.ui.Dialog({
|
||||
title: __("Printer Settings"),
|
||||
fields: [{
|
||||
fieldtype: 'Section Break'
|
||||
},
|
||||
{
|
||||
fieldname: "printer_mapping",
|
||||
fieldtype: "Table",
|
||||
label: __('Printer Mapping'),
|
||||
in_place_edit: true,
|
||||
data: this.data,
|
||||
get_data: () => {
|
||||
return this.data;
|
||||
},
|
||||
fields: [{
|
||||
fieldtype: 'Select',
|
||||
fieldname: "print_format",
|
||||
default: 0,
|
||||
options: this.print_formats,
|
||||
read_only: 0,
|
||||
in_list_view: 1,
|
||||
label: __('Print Format')
|
||||
}, {
|
||||
fieldtype: 'Select',
|
||||
fieldname: "printer",
|
||||
default: 0,
|
||||
options: this.printer_list,
|
||||
read_only: 0,
|
||||
in_list_view: 1,
|
||||
label: __('Printer')
|
||||
}]
|
||||
},
|
||||
],
|
||||
primary_action: function () {
|
||||
let printer_mapping = this.get_values()["printer_mapping"];
|
||||
if (printer_mapping && printer_mapping.length) {
|
||||
let print_format_list = printer_mapping.map(a => a.print_format);
|
||||
let has_duplicate = print_format_list.some((item, idx) => print_format_list.indexOf(item) != idx);
|
||||
if (has_duplicate)
|
||||
frappe.throw(__("Cannot have multiple printers mapped to a single print format."));
|
||||
} else {
|
||||
printer_mapping = [];
|
||||
}
|
||||
this.print_format_printer_map = me.get_print_format_printer_map();
|
||||
this.print_format_printer_map[me.frm.doctype] = printer_mapping;
|
||||
localStorage.print_format_printer_map = JSON.stringify(this.print_format_printer_map);
|
||||
this.hide();
|
||||
},
|
||||
primary_action_label: __('Save')
|
||||
});
|
||||
dialog.show();
|
||||
if (!(this.printer_list && this.printer_list.length)) {
|
||||
frappe.throw(__("No Printer is Available."));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -326,3 +469,119 @@ frappe.ui.get_print_settings = function (pdf, callback, letter_head) {
|
|||
callback(data);
|
||||
}, __("Print Settings"));
|
||||
}
|
||||
|
||||
|
||||
// qz tray connection wrapper
|
||||
// - allows active and inactive connections to resolve regardless
|
||||
// - try to connect once before firing the mimetype launcher
|
||||
// - if connection fails, catch the reject, fire the mimetype launcher
|
||||
// - after mimetype launcher is fired, try to connect 3 more times
|
||||
// - display success/fail message to user
|
||||
frappe.ui.form.qz_connect = function () {
|
||||
return new Promise(function (resolve, reject) {
|
||||
frappe.ui.form.qz_init().then(() => {
|
||||
if (qz.websocket.isActive()) { // if already active, resolve immediately
|
||||
// frappe.show_alert({message: __('QZ Tray Connection Active!'), indicator: 'green'});
|
||||
resolve();
|
||||
} else {
|
||||
// try to connect once before firing the mimetype launcher
|
||||
frappe.show_alert({
|
||||
message: __('Attempting Connection to QZ Tray...'),
|
||||
indicator: 'blue'
|
||||
});
|
||||
qz.websocket.connect().then(() => {
|
||||
frappe.show_alert({
|
||||
message: __('Connected to QZ Tray!'),
|
||||
indicator: 'green'
|
||||
});
|
||||
resolve();
|
||||
}, function retry(err) {
|
||||
if (err.message === 'Unable to establish connection with QZ') {
|
||||
// if a connect was not successful, launch the mimetype, try 3 more times
|
||||
frappe.show_alert({
|
||||
message: __('Attempting to launch QZ Tray...'),
|
||||
indicator: 'blue'
|
||||
}, 14);
|
||||
window.location.assign("qz:launch");
|
||||
qz.websocket.connect({
|
||||
retries: 3,
|
||||
delay: 1
|
||||
}).then(() => {
|
||||
frappe.show_alert({
|
||||
message: __('Connected to QZ Tray!'),
|
||||
indicator: 'green'
|
||||
});
|
||||
resolve();
|
||||
},
|
||||
() => {
|
||||
frappe.throw(__('Error connecting to QZ Tray Application...<br><br> You need to have QZ Tray application installed and running, to use the Raw Print feature.<br><br><a target="_blank" href="https://qz.io/download/">Click here to Download and install QZ Tray</a>.<br> <a target="_blank" href="https://erpnext.com/docs/user/manual/en/setting-up/print/raw-printing">Click here to learn more about Raw Printing</a>.'));
|
||||
reject();
|
||||
});
|
||||
} else {
|
||||
frappe.show_alert({
|
||||
message: 'QZ Tray ' + err.toString(),
|
||||
indicator: 'red'
|
||||
}, 14);
|
||||
reject();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
frappe.ui.form.qz_init = function () {
|
||||
// Initializing qz tray library
|
||||
return new Promise((resolve) => {
|
||||
if (typeof qz === "object" && typeof qz.version === "string") {
|
||||
// resolve immediately if already Initialized
|
||||
resolve();
|
||||
} else {
|
||||
let qz_required_assets = [
|
||||
"/assets/frappe/node_modules/js-sha256/build/sha256.min.js",
|
||||
"/assets/frappe/node_modules/qz-tray/qz-tray.js"
|
||||
];
|
||||
frappe.require(qz_required_assets,() => {
|
||||
qz.api.setPromiseType(function promise(resolver) {
|
||||
return new Promise(resolver);
|
||||
});
|
||||
qz.api.setSha256Type(function (data) {
|
||||
// Codacy fix
|
||||
/*global sha256*/
|
||||
return sha256(data);
|
||||
});
|
||||
resolve();
|
||||
});
|
||||
// note 'frappe.require' does not have callback on fail. Hence, any failure cannot be communicated to the user.
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
frappe.ui.form.qz_get_printer_list = function () {
|
||||
// returns the list of printers that are available to the QZ Tray
|
||||
return frappe.ui.form.qz_connect().then(function () {
|
||||
return qz.printers.find();
|
||||
}).then((data) => {
|
||||
return data;
|
||||
}).catch((err) => {
|
||||
frappe.ui.form.qz_fail(err);
|
||||
});
|
||||
}
|
||||
|
||||
frappe.ui.form.qz_success = function () {
|
||||
// notify qz successful print
|
||||
frappe.show_alert({
|
||||
message: __('Print Sent to the printer!'),
|
||||
indicator: 'green'
|
||||
});
|
||||
}
|
||||
|
||||
frappe.ui.form.qz_fail = function (e) {
|
||||
// notify qz errors
|
||||
frappe.show_alert({
|
||||
message: __("QZ Tray Failed: ") + e.toString(),
|
||||
indicator: 'red'
|
||||
}, 20);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,9 @@
|
|||
<a class="btn-print-print btn-sm btn btn-default">
|
||||
<strong>{%= __("Print") %}</strong></a>
|
||||
<a class="btn-sm btn btn-default" href="#Form/Print Settings">
|
||||
{%= __("Settings...") %}</a>
|
||||
{%= __("Settings...") %}</a>
|
||||
<a class="btn-printer-setting btn-sm btn btn-default" style="display: none;">
|
||||
{%= __("Printer Settings...") %}</a>
|
||||
<a class="btn-print-edit btn-sm btn btn-default">
|
||||
{%= __("Customize...") %}</a>
|
||||
<a class="btn-print-preview btn-sm btn btn-default">
|
||||
|
|
|
|||
|
|
@ -196,11 +196,11 @@ $.extend(frappe.meta, {
|
|||
get_print_formats: function(doctype) {
|
||||
var print_format_list = ["Standard"];
|
||||
var default_print_format = locals.DocType[doctype].default_print_format;
|
||||
|
||||
let enable_raw_printing = frappe.model.get_doc(":Print Settings", "Print Settings").enable_raw_printing;
|
||||
var print_formats = frappe.get_list("Print Format", {doc_type: doctype})
|
||||
.sort(function(a, b) { return (a > b) ? 1 : -1; });
|
||||
$.each(print_formats, function(i, d) {
|
||||
if(!in_list(print_format_list, d.name) && in_list(['Server', 'Client'], d.print_format_type))
|
||||
if(!in_list(print_format_list, d.name) && in_list(['Server', 'Client'], d.print_format_type) && (cint(enable_raw_printing) || !d.raw_printing))
|
||||
print_format_list.push(d.name);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ def get_context(context):
|
|||
print_format = get_print_format_doc(None, meta = meta)
|
||||
|
||||
return {
|
||||
"body": get_html(doc, print_format = print_format,
|
||||
"body": get_rendered_template(doc, print_format = print_format,
|
||||
meta=meta, trigger_print = frappe.form_dict.trigger_print,
|
||||
no_letterhead=frappe.form_dict.no_letterhead),
|
||||
"css": get_print_style(frappe.form_dict.style, print_format),
|
||||
|
|
@ -58,7 +58,7 @@ def get_print_format_doc(print_format_name, meta):
|
|||
# if old name, return standard!
|
||||
return None
|
||||
|
||||
def get_html(doc, name=None, print_format=None, meta=None,
|
||||
def get_rendered_template(doc, name=None, print_format=None, meta=None,
|
||||
no_letterhead=None, trigger_print=False):
|
||||
|
||||
print_settings = frappe.db.get_singles_dict("Print Settings")
|
||||
|
|
@ -181,12 +181,40 @@ def get_html_and_style(doc, name=None, print_format=None, meta=None,
|
|||
doc = frappe.get_doc(json.loads(doc))
|
||||
|
||||
print_format = get_print_format_doc(print_format, meta=meta or frappe.get_meta(doc.doctype))
|
||||
|
||||
if print_format and print_format.raw_printing:
|
||||
return {
|
||||
"html": '<div class="text-muted text-center" style="font-size: 2em; margin-top: 80px;">'
|
||||
+ _("No Preview Available")
|
||||
+ '</div>'
|
||||
}
|
||||
|
||||
return {
|
||||
"html": get_html(doc, name=name, print_format=print_format, meta=meta,
|
||||
"html": get_rendered_template(doc, name=name, print_format=print_format, meta=meta,
|
||||
no_letterhead=no_letterhead, trigger_print=trigger_print),
|
||||
"style": get_print_style(style=style, print_format=print_format)
|
||||
}
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_rendered_raw_commands(doc, name=None, print_format=None, meta=None, lang=None):
|
||||
"""Returns Rendered Raw Commands of print format, used to send directly to printer"""
|
||||
|
||||
if isinstance(doc, string_types) and isinstance(name, string_types):
|
||||
doc = frappe.get_doc(doc, name)
|
||||
|
||||
if isinstance(doc, string_types):
|
||||
doc = frappe.get_doc(json.loads(doc))
|
||||
|
||||
print_format = get_print_format_doc(print_format, meta=meta or frappe.get_meta(doc.doctype))
|
||||
|
||||
if not print_format or (print_format and not print_format.raw_printing):
|
||||
frappe.throw(_("{0} is not a raw printing format.").format(print_format),
|
||||
frappe.TemplateNotFoundError)
|
||||
|
||||
return {
|
||||
"raw_commands": get_rendered_template(doc, name=name, print_format=print_format, meta=meta)
|
||||
}
|
||||
|
||||
def validate_print_permission(doc):
|
||||
if frappe.form_dict.get("key"):
|
||||
if frappe.form_dict.key == doc.get_signature():
|
||||
|
|
@ -218,8 +246,10 @@ def get_print_format(doctype, print_format):
|
|||
with open(path, "r") as pffile:
|
||||
return pffile.read()
|
||||
else:
|
||||
if print_format.html:
|
||||
if print_format.html and not print_format.raw_printing:
|
||||
return print_format.html
|
||||
elif print_format.raw_commands and print_format.raw_printing:
|
||||
return print_format.raw_commands
|
||||
else:
|
||||
frappe.throw(_("No template found at path: {0}").format(path),
|
||||
frappe.TemplateNotFoundError)
|
||||
|
|
|
|||
|
|
@ -28,10 +28,12 @@
|
|||
"frappe-gantt": "^0.1.0",
|
||||
"fuse.js": "^3.2.0",
|
||||
"highlight.js": "^9.12.0",
|
||||
"js-sha256": "^0.9.0",
|
||||
"jsbarcode": "^3.9.0",
|
||||
"moment": "^2.20.1",
|
||||
"moment-timezone": "^0.5.21",
|
||||
"quill": "2.0.0-dev.2",
|
||||
"qz-tray": "^2.0.8",
|
||||
"redis": "^2.8.0",
|
||||
"showdown": "^1.8.6",
|
||||
"socket.io": "^2.0.4",
|
||||
|
|
|
|||
10
yarn.lock
10
yarn.lock
|
|
@ -2457,6 +2457,11 @@ js-base64@^2.1.8, js-base64@^2.1.9:
|
|||
resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.5.1.tgz#1efa39ef2c5f7980bb1784ade4a8af2de3291121"
|
||||
integrity sha512-M7kLczedRMYX4L8Mdh4MzyAMM9O5osx+4FcOQuTvr3A9F2D9S5JXheN0ewNbrvK2UatkTRhL5ejGmGSjNMiZuw==
|
||||
|
||||
js-sha256@^0.9.0:
|
||||
version "0.9.0"
|
||||
resolved "https://registry.yarnpkg.com/js-sha256/-/js-sha256-0.9.0.tgz#0b89ac166583e91ef9123644bd3c5334ce9d0966"
|
||||
integrity sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA==
|
||||
|
||||
js-yaml@^3.12.0, js-yaml@^3.9.0:
|
||||
version "3.12.2"
|
||||
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.2.tgz#ef1d067c5a9d9cb65bd72f285b5d8105c77f14fc"
|
||||
|
|
@ -3857,6 +3862,11 @@ quill@2.0.0-dev.2:
|
|||
parchment quilljs/parchment#487850f7eb030a6c4e750ba809e58b09444e0bdb
|
||||
quill-delta "^3.6.2"
|
||||
|
||||
qz-tray@^2.0.8:
|
||||
version "2.0.8"
|
||||
resolved "https://registry.yarnpkg.com/qz-tray/-/qz-tray-2.0.8.tgz#5e15d102cf3a11a31ddb332891c7f8a6af8f6ad5"
|
||||
integrity sha512-lncGYzz7/sTORZuC1S3ukNlMPCMOmsHWNvJF4FjMCZ2+0UV3txi6kgPd754B7kDFKm0J587sIODgxIlFY7qU4w==
|
||||
|
||||
ramda@0.24.1:
|
||||
version "0.24.1"
|
||||
resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.24.1.tgz#c3b7755197f35b8dc3502228262c4c91ddb6b857"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue