Merge pull request #7088 from karthikeyan5/raw-printing

feat(Raw Printing): Adding Support for qz-tray
This commit is contained in:
Rushabh Mehta 2019-04-19 11:43:47 +05:30 committed by GitHub
commit 7817b4e03b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 2063 additions and 1532 deletions

View file

@ -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

View file

@ -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);
}

View file

@ -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">

View file

@ -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);
});

View file

@ -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)

View file

@ -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",

View file

@ -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"