Skip to content

Commit

Permalink
[ADD] base_report_to_qz: new module to print with QZ Tray
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisandrewmann committed May 27, 2024
1 parent 91ae430 commit a6cf4ed
Show file tree
Hide file tree
Showing 22 changed files with 3,989 additions and 0 deletions.
151 changes: 151 additions & 0 deletions base_report_to_qz_tray/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
=================
Report to QZ Tray
=================

.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Freport--print--send-lightgray.png?logo=github
:target: https://github.com/OCA/report-print-send/tree/14.0/base_report_to_qz_tray
:alt: OCA/report-print-send
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/report-print-send-14-0/report-print-send-14-0-base_report_to_qz_tray
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png
:target: https://runbot.odoo-community.org/runbot/144/14.0
:alt: Try me on Runbot

|badge1| |badge2| |badge3| |badge4| |badge5|

This module allows users to send reports to a printer attached to the client.

It adds an optional behaviour on reports to send it directly to a printer.

* `Send to Client` is the default behaviour providing you a downloadable PDF
* `Send to Printer` prints the report on selected printer

Report behaviour is defined by settings.

You will find this option on default user config, on default report
config and on specific config per user per report.

This allows you to dedicate a specific paper source for example for
preprinted paper such as payment slip.

Settings can be configured:

* globally
* per user
* per report
* per user and report

**Table of contents**

.. contents::
:local:

Installation
============

To install this module, you need to:

#. Install QZ Tray on client - https://qz.io/download/

#. Install pyOpenSSL

PyOpenSSL is required to sign messages sent to the clients QZ Tray (and prevent warnings).

.. code-block:: bash
sudo pip3 install pyOpenSSL
#. Generate a certificate

Take care about "Common Name (e.g. server FQDN or YOUR name) []:" THIS ENTRY IS IMPORTANT,
this should be your Odoo domain name, can be filled in wildcard format an example of this would be \*.my-odoo-domain.com

.. code-block:: bash
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 3650-nodes
#. Set key and certificate on Odoo

Go to /Settings/Technical/Parameters/System Parameters and set:
- a parameter called qz.certificate with the content of the certificate.
- a parameter called qz.key with the content of the key.


#. Install certificate on client QZ Tray.

Open QZ Tray menu /Advanced/Site Manager and drag and drop cert.pem for your Odoo domain.

Configuration
=============

To configure this module, you need to:

#. Enable the "Printing / Print User" option under access
rights to give users the ability to view the print menu.


The jobs will be sent to the printer installed locally with a name matching the QZ Tray Printer.

Usage
=====

Guidelines for use:

* To print a report on a specific printer or tray, you can change
these in *Settings > Printing > Reports* to define default behaviour.
* To print a report on a specific printer and/or tray for a user, you can
change these in *Settings > Printing > Reports* in
*Specific actions per user*
* Users may also select a default action, printer or tray in their preferences.

Bug Tracker
===========

Bugs are tracked on `GitHub Issues <https://github.com/OCA/report-print-send/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed
`feedback <https://github.com/OCA/report-print-send/issues/new?body=module:%20base_report_to_qz_tray%0Aversion:%2014.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.

Do not contact contributors directly about support or help with technical issues.

Credits
=======

Authors
~~~~~~~

* PESOL

Contributors
~~~~~~~~~~~~

* Angel Moya <[email protected]>

Maintainers
~~~~~~~~~~~

This module is maintained by the OCA.

.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org

OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.

This module is part of the `OCA/report-print-send <https://github.com/OCA/report-print-send/tree/14.0/base_report_to_qz_tray>`_ project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
5 changes: 5 additions & 0 deletions base_report_to_qz_tray/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Copyright (C) 2022 PESOL (<http://www.camptocamp.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from . import models
from . import controllers
26 changes: 26 additions & 0 deletions base_report_to_qz_tray/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Copyright (C) 2022 PESOL (<http://www.camptocamp.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

{
"name": "Report to QZ Tray",
"version": "16.0.1.0.0",
"category": "Generic Modules/Base",
"author": "PESOL, Odoo Community Association (OCA)",
"website": "https://github.com/OCA/report-print-send",
"license": "AGPL-3",
"depends": ["web", "mail"],
"data": [
"security/security.xml",
"views/ir_actions_report.xml",
"views/res_users.xml",
],
"installable": True,
"application": False,
"external_dependencies": {"python": ["pyOpenSSL"]},
"assets": {
"web.assets_backend": [
"base_report_to_qz_tray/static/src/lib/qz-tray.js",
"base_report_to_qz_tray/static/src/js/qweb_action_manager.esm.js",
]
},
}
4 changes: 4 additions & 0 deletions base_report_to_qz_tray/controllers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Copyright (C) 2022 PESOL (<http://www.camptocamp.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from . import main
29 changes: 29 additions & 0 deletions base_report_to_qz_tray/controllers/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Copyright (C) 2022 PESOL (<http://www.camptocamp.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

import base64

from OpenSSL import crypto

from odoo import http
from odoo.http import request


class SignMessage(http.Controller):
@http.route("/qz-certificate/", auth="public")
def qz_certificate(self, **kwargs):
config_param_sudo = request.env["ir.config_parameter"].sudo()
cert = config_param_sudo.get_param("qz.certificate", default=False)
return request.make_response(cert, [("Content-Type", "text/plain")])

Check warning on line 17 in base_report_to_qz_tray/controllers/main.py

View check run for this annotation

Codecov / codecov/patch

base_report_to_qz_tray/controllers/main.py#L15-L17

Added lines #L15 - L17 were not covered by tests

@http.route("/qz-sign-message/", auth="public")
def qz_sign_message(self, **kwargs):
config_param_sudo = request.env["ir.config_parameter"].sudo()
key = config_param_sudo.get_param("qz.key", default=False)
password = None
pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, key, password)
sign = crypto.sign(

Check warning on line 25 in base_report_to_qz_tray/controllers/main.py

View check run for this annotation

Codecov / codecov/patch

base_report_to_qz_tray/controllers/main.py#L21-L25

Added lines #L21 - L25 were not covered by tests
pkey, bytes(kwargs.get("request", ""), encoding="utf-8"), "sha512"
)
data_base64 = base64.b64encode(sign)
return request.make_response(data_base64, [("Content-Type", "text/plain")])

Check warning on line 29 in base_report_to_qz_tray/controllers/main.py

View check run for this annotation

Codecov / codecov/patch

base_report_to_qz_tray/controllers/main.py#L28-L29

Added lines #L28 - L29 were not covered by tests
7 changes: 7 additions & 0 deletions base_report_to_qz_tray/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Copyright (C) 2022 PESOL (<http://www.camptocamp.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from . import ir_actions_report
from . import printing_report_qz_tray
from . import res_users
from . import mail_template
97 changes: 97 additions & 0 deletions base_report_to_qz_tray/models/ir_actions_report.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# Copyright (C) 2022 PESOL (<http://www.camptocamp.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

import base64

from odoo import api, fields, models


class IrActionsReport(models.Model):
_inherit = "ir.actions.report"

printing_report_qz_tray_ids = fields.One2many(
comodel_name="printing.report.qz.tray",
inverse_name="report_id",
string="Default Actions",
help="This field allows configuring action and printer on a per " "user basis",
)

@api.model
def qz_tray_for_report_name(self, report_name):
"""Returns if the action is a direct print or pdf
Called from js
"""
report = self._get_report_from_name(report_name)

Check warning on line 25 in base_report_to_qz_tray/models/ir_actions_report.py

View check run for this annotation

Codecov / codecov/patch

base_report_to_qz_tray/models/ir_actions_report.py#L25

Added line #L25 was not covered by tests

if not report or self.env.context.get("must_skip_send_to_printer"):
return {"action": "download", "printer_name": self.env.user.qz_tray_printer}

Check warning on line 28 in base_report_to_qz_tray/models/ir_actions_report.py

View check run for this annotation

Codecov / codecov/patch

base_report_to_qz_tray/models/ir_actions_report.py#L28

Added line #L28 was not covered by tests

# get values for specific report and user
report_action_values = report.get_default_report_action(user_id=self.env.uid)

Check warning on line 31 in base_report_to_qz_tray/models/ir_actions_report.py

View check run for this annotation

Codecov / codecov/patch

base_report_to_qz_tray/models/ir_actions_report.py#L31

Added line #L31 was not covered by tests
if report_action_values:
return report_action_values

Check warning on line 33 in base_report_to_qz_tray/models/ir_actions_report.py

View check run for this annotation

Codecov / codecov/patch

base_report_to_qz_tray/models/ir_actions_report.py#L33

Added line #L33 was not covered by tests

# get values for specific report, for all users
report_action_values = report.get_default_report_action()

Check warning on line 36 in base_report_to_qz_tray/models/ir_actions_report.py

View check run for this annotation

Codecov / codecov/patch

base_report_to_qz_tray/models/ir_actions_report.py#L36

Added line #L36 was not covered by tests
if report_action_values:
return report_action_values

Check warning on line 38 in base_report_to_qz_tray/models/ir_actions_report.py

View check run for this annotation

Codecov / codecov/patch

base_report_to_qz_tray/models/ir_actions_report.py#L38

Added line #L38 was not covered by tests

# get values for user or download
return {

Check warning on line 41 in base_report_to_qz_tray/models/ir_actions_report.py

View check run for this annotation

Codecov / codecov/patch

base_report_to_qz_tray/models/ir_actions_report.py#L41

Added line #L41 was not covered by tests
"id": report.id,
"action": self.env.user.report_default_action or "download",
"printer_name": self.env.user.qz_tray_printer or False,
}

def get_default_report_action(self, user_id=False):
self.ensure_one()
printing_act_obj = self.env["printing.report.qz.tray"]
printing_report_qz_tray = printing_act_obj.search(

Check warning on line 50 in base_report_to_qz_tray/models/ir_actions_report.py

View check run for this annotation

Codecov / codecov/patch

base_report_to_qz_tray/models/ir_actions_report.py#L48-L50

Added lines #L48 - L50 were not covered by tests
# [
# ("report_id", "=", self.id),
# ("user_id", "=", user_id),
# ],
[
("report_id", "=", self.id),
"|",
("user_id", "=", user_id),
("user_id", "=", False),
],
limit=1,
)
if not printing_report_qz_tray:
return False
return {

Check warning on line 65 in base_report_to_qz_tray/models/ir_actions_report.py

View check run for this annotation

Codecov / codecov/patch

base_report_to_qz_tray/models/ir_actions_report.py#L64-L65

Added lines #L64 - L65 were not covered by tests
"id": self.id,
"action": printing_report_qz_tray.action,
"printer_name": printing_report_qz_tray.qz_tray_printer,
}

def get_qz_tray_data(self, res_ids, report_type="pdf", report_name="", data=None):
if report_type == "pdf":
# Pre V16
# result = self._render_qweb_pdf(res_ids, data)
# Post V16
result = self.env["ir.actions.report"]._render_qweb_pdf(

Check warning on line 76 in base_report_to_qz_tray/models/ir_actions_report.py

View check run for this annotation

Codecov / codecov/patch

base_report_to_qz_tray/models/ir_actions_report.py#L76

Added line #L76 was not covered by tests
report_name, res_ids, data
)
data = [

Check warning on line 79 in base_report_to_qz_tray/models/ir_actions_report.py

View check run for this annotation

Codecov / codecov/patch

base_report_to_qz_tray/models/ir_actions_report.py#L79

Added line #L79 was not covered by tests
{
"type": "pixel",
"format": "pdf",
"flavor": "base64",
"data": base64.b64encode(result[0]),
}
]
elif report_type == "text":
# Pre V16
# result = self._render_qweb_text(res_ids, data)
# Post V16
result = self.env["ir.actions.report"]._render_qweb_text(

Check warning on line 91 in base_report_to_qz_tray/models/ir_actions_report.py

View check run for this annotation

Codecov / codecov/patch

base_report_to_qz_tray/models/ir_actions_report.py#L91

Added line #L91 was not covered by tests
report_name, res_ids, data
)
data = [result[0].replace(b"\n", b"").decode("unicode_escape")]

Check warning on line 94 in base_report_to_qz_tray/models/ir_actions_report.py

View check run for this annotation

Codecov / codecov/patch

base_report_to_qz_tray/models/ir_actions_report.py#L94

Added line #L94 was not covered by tests
else:
data = []
return data

Check warning on line 97 in base_report_to_qz_tray/models/ir_actions_report.py

View check run for this annotation

Codecov / codecov/patch

base_report_to_qz_tray/models/ir_actions_report.py#L96-L97

Added lines #L96 - L97 were not covered by tests
22 changes: 22 additions & 0 deletions base_report_to_qz_tray/models/mail_template.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Copyright (C) 2022 PESOL (<http://www.camptocamp.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from odoo import models


class MailTemplate(models.Model):
_inherit = "mail.template"

def _generate_template(self, res_ids, fields=None):
# Pre-V17
return super(

Check warning on line 12 in base_report_to_qz_tray/models/mail_template.py

View check run for this annotation

Codecov / codecov/patch

base_report_to_qz_tray/models/mail_template.py#L12

Added line #L12 was not covered by tests
MailTemplate, self.with_context(must_skip_send_to_printer=True)
).generate_email(res_ids, fields=fields)
# V17 onwards
# return super(
# MailTemplate, self.with_context(must_skip_send_to_printer=True)
# )._generate_template(
# res_ids,
# render_fields=render_fields,
# find_or_create_partners=find_or_create_partners,
# )
21 changes: 21 additions & 0 deletions base_report_to_qz_tray/models/printing_report_qz_tray.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Copyright (C) 2022 PESOL (<http://www.camptocamp.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from odoo import fields, models


class PrintingReportQzTray(models.Model):
_name = "printing.report.qz.tray"
_description = "Printing Report Qz Tray"

report_id = fields.Many2one(
comodel_name="ir.actions.report",
string="Report",
required=True,
)
user_id = fields.Many2one(
comodel_name="res.users", required=False, ondelete="cascade"
)
action = fields.Selection([("print", "Print"), ("download", "Download")])
# TODO: Use js to show local printers and select one
qz_tray_printer = fields.Char(string="QZ Tray Printer")
23 changes: 23 additions & 0 deletions base_report_to_qz_tray/models/res_users.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Copyright (C) 2022 PESOL (<http://www.camptocamp.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from odoo import fields, models


class ResUsers(models.Model):
_inherit = "res.users"

report_default_action = fields.Selection(
[("print", "Print"), ("download", "Download")],
default="download",
string="Report Action",
)
# TODO: Use js to show local printers and select one
qz_tray_printer = fields.Char(string="QZ Tray Printer")

printing_report_qz_tray_ids = fields.One2many(
comodel_name="printing.report.qz.tray",
inverse_name="user_id",
string="Default Report Actions",
help="This field allows configuring action and printer on a per " "user basis",
)
Loading

0 comments on commit a6cf4ed

Please sign in to comment.