diff --git a/base_report_to_printer/README.rst b/base_report_to_printer/README.rst
index 009291b..526f4d4 100644
--- a/base_report_to_printer/README.rst
+++ b/base_report_to_printer/README.rst
@@ -89,9 +89,8 @@ this will not be displayed by CUPS web interface or in Odoo. To see this
information, you need to change the configuration of your CUPS server
and set the JobPrivateValue directive to "none" (or some other list of
values which does not include "job-name") , and reload the server. See
-cupsd.conf(5)
-<`https://www.cups.org/doc/man-cupsd.conf.html\\> >`__
-for details.
+cupsd.conf(5) for
+details.
Usage
=====
@@ -112,6 +111,13 @@ Guidelines for use:
When no tray is configured for a report and a user, the default tray
setup on the CUPS server is used.
+Known issues / Roadmap
+======================
+
+- With threaded printing there's no download fallback when the issue
+ isn't detected by the CUPS Odoo backend. To able to do it, we would
+ need to notify the bus or use web_notify for it.
+
Changelog
=========
diff --git a/base_report_to_printer/__manifest__.py b/base_report_to_printer/__manifest__.py
index 2105161..8ba8600 100644
--- a/base_report_to_printer/__manifest__.py
+++ b/base_report_to_printer/__manifest__.py
@@ -18,6 +18,7 @@
"data": [
"data/printing_data.xml",
"security/security.xml",
+ "security/ir.model.access.csv",
"views/printing_printer.xml",
"views/printing_server.xml",
"views/printing_job.xml",
diff --git a/base_report_to_printer/i18n/sv.po b/base_report_to_printer/i18n/sv.po
index 6c84e31..3f0f419 100644
--- a/base_report_to_printer/i18n/sv.po
+++ b/base_report_to_printer/i18n/sv.po
@@ -921,7 +921,7 @@ msgstr "Guide"
#: code:addons/base_report_to_printer/wizards/print_attachment_report.py:0
#, python-format
msgid "{name} ({copies} copies)"
-msgstr "{namn} ({kopior} kopior)"
+msgstr "{name} ({copies} kopior)"
#~ msgid "Job"
#~ msgstr "Jobb"
diff --git a/base_report_to_printer/models/ir_actions_report.py b/base_report_to_printer/models/ir_actions_report.py
index bff6897..8af5143 100644
--- a/base_report_to_printer/models/ir_actions_report.py
+++ b/base_report_to_printer/models/ir_actions_report.py
@@ -3,9 +3,11 @@
# Copyright (C) 2011 Agile Business Group sagl ()
# Copyright (C) 2011 Domsense srl ()
# Copyright (C) 2013-2014 Camptocamp ()
+# Copyright 2024 Tecnativa - Sergio Teruel
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+import threading
-from odoo import _, api, exceptions, fields, models
+from odoo import _, api, exceptions, fields, models, registry
from odoo.tools.safe_eval import safe_eval, time
REPORT_TYPES = {"qweb-pdf": "pdf", "qweb-text": "text"}
@@ -53,6 +55,12 @@ class IrActionsReport(models.Model):
"action": result["action"],
"printer_name": result["printer"].name,
}
+ if result.get("printer_exception") and not self.env.context.get(
+ "skip_printer_exception"
+ ):
+ serializable_result["printer_exception"] = True
+ if self.env.context.get("force_print_to_client"):
+ serializable_result["action"] = "client"
return serializable_result
def _get_user_default_print_behaviour(self):
@@ -97,8 +105,49 @@ class IrActionsReport(models.Model):
# For some reason design takes report defaults over
# False action entries so we must allow for that here
result.update({k: v for k, v in print_action.behaviour().items() if v})
+ printer = result.get("printer")
+ if printer:
+ # When no printer is available we can fallback to the default behavior
+ # letting the user to manually print the reports.
+ try:
+ printer.server_id._open_connection(raise_on_error=True)
+ printer_exception = printer.status in [
+ "error",
+ "server-error",
+ "unavailable",
+ ]
+ except Exception:
+ printer_exception = True
+ if printer_exception and not self.env.context.get("skip_printer_exception"):
+ result["printer_exception"] = True
return result
+ def print_document_client_action(self, record_ids, data=None):
+ behaviour = self.behaviour()
+ printer = behaviour.pop("printer", None)
+ if printer.multi_thread:
+
+ @self.env.cr.postcommit.add
+ def _launch_print_thread():
+ threaded_calculation = threading.Thread(
+ target=self.print_document_threaded,
+ args=(self.id, record_ids, data),
+ )
+ threaded_calculation.start()
+
+ return True
+ else:
+ try:
+ return self.print_document(record_ids, data=data)
+ except Exception:
+ return
+
+ def print_document_threaded(self, report_id, record_ids, data):
+ with registry(self._cr.dbname).cursor() as cr:
+ self = self.with_env(self.env(cr=cr))
+ report = self.env["ir.actions.report"].browse(report_id)
+ report.print_document(record_ids, data)
+
def print_document(self, record_ids, data=None):
"""Print a document, do not return the document file"""
report_type = REPORT_TYPES.get(self.report_type)
@@ -127,6 +176,7 @@ class IrActionsReport(models.Model):
else:
title = self.report_name
behaviour["title"] = title
+ behaviour["res_ids"] = record_ids
# TODO should we use doc_format instead of report_type
return printer.print_document(
self, document, doc_format=self.report_type, **behaviour
@@ -140,7 +190,12 @@ class IrActionsReport(models.Model):
"""
if self.env.context.get("must_skip_send_to_printer"):
return False
- if behaviour["action"] == "server" and printer and document:
+ if (
+ behaviour["action"] == "server"
+ and printer
+ and document
+ and not behaviour.get("printer_exception")
+ ):
return True
return False
diff --git a/base_report_to_printer/models/printing_job.py b/base_report_to_printer/models/printing_job.py
index 6d9a347..d38fc2c 100644
--- a/base_report_to_printer/models/printing_job.py
+++ b/base_report_to_printer/models/printing_job.py
@@ -15,7 +15,7 @@ class PrintingJob(models.Model):
name = fields.Char(help="Job name.")
active = fields.Boolean(
- default=True, help="Unchecked if the job is purged from cups."
+ default=True, help="Unchecked if the job is purged from CUPS."
)
job_id_cups = fields.Integer(
string="Job ID", required=True, help="CUPS id for this job."
@@ -40,11 +40,15 @@ class PrintingJob(models.Model):
help="Percentage of progress for this job.",
)
time_at_creation = fields.Datetime(
- required=True, help="Date and time of creation for this job."
+ string="Creation Date",
+ required=True,
+ help="Date and time of creation of this job.",
+ )
+ time_at_processing = fields.Datetime(
+ string="Processing Date", help="Date and time of process for this job."
)
- time_at_processing = fields.Datetime(help="Date and time of process for this job.")
time_at_completed = fields.Datetime(
- help="Date and time of completion for this job."
+ string="Completion Date", help="Date and time of completion for this job."
)
job_state = fields.Selection(
selection=[
diff --git a/base_report_to_printer/models/printing_printer.py b/base_report_to_printer/models/printing_printer.py
index 8d2811e..a7e2587 100644
--- a/base_report_to_printer/models/printing_printer.py
+++ b/base_report_to_printer/models/printing_printer.py
@@ -12,7 +12,7 @@ import logging
import os
from tempfile import mkstemp
-from odoo import fields, models
+from odoo import api, fields, models
_logger = logging.getLogger(__name__)
@@ -67,11 +67,19 @@ class PrintingPrinter(models.Model):
tray_ids = fields.One2many(
comodel_name="printing.tray", inverse_name="printer_id", string="Paper Sources"
)
+ multi_thread = fields.Boolean(
+ compute="_compute_multi_thread", readonly=False, store=True
+ )
+
+ @api.depends("server_id.multi_thread")
+ def _compute_multi_thread(self):
+ for printer in self:
+ printer.multi_thread = printer.server_id.multi_thread
def _prepare_update_from_cups(self, cups_connection, cups_printer):
mapping = {3: "available", 4: "printing", 5: "error"}
cups_vals = {
- "name": cups_printer["printer-info"],
+ "name": self.name or cups_printer["printer-info"],
"model": cups_printer.get("printer-make-and-model", False),
"location": cups_printer.get("printer-location", False),
"uri": cups_printer.get("device-uri", False),
diff --git a/base_report_to_printer/models/printing_server.py b/base_report_to_printer/models/printing_server.py
index d5958f7..6d64088 100644
--- a/base_report_to_printer/models/printing_server.py
+++ b/base_report_to_printer/models/printing_server.py
@@ -41,6 +41,7 @@ class PrintingServer(models.Model):
string="Printers List",
help="List of printers available on this server.",
)
+ multi_thread = fields.Boolean()
def _open_connection(self, raise_on_error=False):
self.ensure_one()
@@ -121,6 +122,11 @@ class PrintingServer(models.Model):
printer_values["server_id"] = server.id
updated_printers.append(name)
+ # We want to keep any existing customized name over existing printer
+ # We want also to rely in the system name as a fallback to avoid
+ # empty names.
+ if not printer_values.get("name") and not printer.name:
+ printer_values["name"] = name
if not printer:
printer_values["system_name"] = name
printer.create(printer_values)
diff --git a/base_report_to_printer/readme/ROADMAP.md b/base_report_to_printer/readme/ROADMAP.md
new file mode 100644
index 0000000..f35d11c
--- /dev/null
+++ b/base_report_to_printer/readme/ROADMAP.md
@@ -0,0 +1,3 @@
+- With threaded printing there's no download fallback when the issue
+ isn't detected by the CUPS Odoo backend. To able to do it, we would
+ need to notify the bus or use web_notify for it.
diff --git a/base_report_to_printer/security/ir.model.access.csv b/base_report_to_printer/security/ir.model.access.csv
new file mode 100644
index 0000000..b97fc91
--- /dev/null
+++ b/base_report_to_printer/security/ir.model.access.csv
@@ -0,0 +1,2 @@
+id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink
+access_printing_printer_update_wizard,printers update,model_printing_printer_update_wizard,base.group_system,1,1,1,1
diff --git a/base_report_to_printer/security/security.xml b/base_report_to_printer/security/security.xml
index 8f68e12..e0c9e22 100644
--- a/base_report_to_printer/security/security.xml
+++ b/base_report_to_printer/security/security.xml
@@ -125,9 +125,10 @@
Update printer wizard
-
-
-
+
+
+
+
Print Attachment User
diff --git a/base_report_to_printer/static/description/index.html b/base_report_to_printer/static/description/index.html
index 6aec188..7732f22 100644
--- a/base_report_to_printer/static/description/index.html
+++ b/base_report_to_printer/static/description/index.html
@@ -8,10 +8,11 @@
/*
:Author: David Goodger (goodger@python.org)
-:Id: $Id: html4css1.css 8954 2022-01-20 10:10:25Z milde $
+:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
+Despite the name, some widely supported CSS2 features are used.
See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to
customize this style sheet.
@@ -274,7 +275,7 @@ pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ;
margin-right: 2em }
-pre.code .ln { color: grey; } /* line numbers */
+pre.code .ln { color: gray; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
@@ -300,7 +301,7 @@ span.option {
span.pre {
white-space: pre }
-span.problematic {
+span.problematic, pre.problematic {
color: red }
span.section-subtitle {
@@ -398,16 +399,17 @@ preprinted paper such as payment slip.
Installation
Configuration
Usage
-Changelog
-- 13.0.1.0.0 (2019-09-30)
-- 12.0.1.0.0 (2018-02-04)
+- Known issues / Roadmap
+- Changelog
-- Bug Tracker
-- Credits
@@ -438,9 +440,8 @@ this will not be displayed by CUPS web interface or in Odoo. To see this
information, you need to change the configuration of your CUPS server
and set the JobPrivateValue directive to “none” (or some other list of
values which does not include “job-name”) , and reload the server. See
-cupsd.conf(5)
-<https://www.cups.org/doc/man-cupsd.conf.html\>
-for details.
+cupsd.conf(5) <https://www.cups.org/doc/man-cupsd.conf.html> for
+details.
@@ -462,23 +463,31 @@ preferences.
When no tray is configured for a report and a user, the default tray
setup on the CUPS server is used.
+
+
+
+- With threaded printing there’s no download fallback when the issue
+isn’t detected by the CUPS Odoo backend. To able to do it, we would
+need to notify the bus or use web_notify for it.
+
+
-
+
Bugs are tracked on GitHub Issues.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
@@ -486,9 +495,9 @@ If you spotted it first, help us to smash it by providing a detailed and welcome
Do not contact contributors directly about support or help with technical issues.
-
+
-
+
- Agile Business Group & Domsense
- Pegueroles SCP
@@ -499,7 +508,7 @@ If you spotted it first, help us to smash it by providing a detailed and welcome
-
+
This module is maintained by the OCA.
-

+
+
+
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.
diff --git a/base_report_to_printer/static/src/js/qweb_action_manager.esm.js b/base_report_to_printer/static/src/js/qweb_action_manager.esm.js
index d9f9769..2d110dc 100644
--- a/base_report_to_printer/static/src/js/qweb_action_manager.esm.js
+++ b/base_report_to_printer/static/src/js/qweb_action_manager.esm.js
@@ -1,4 +1,5 @@
/** @odoo-module */
+import {Markup} from "web.utils";
import {_t} from "@web/core/l10n/translation";
import {registry} from "@web/core/registry";
@@ -9,19 +10,72 @@ async function cupsReportActionHandler(action, options, env) {
const print_action = await orm.call(
"ir.actions.report",
"print_action_for_report_name",
- [action.report_name]
+ [action.report_name],
+ {context: {force_print_to_client: action.context.force_print_to_client}}
);
- if (print_action && print_action.action === "server") {
- const result = await orm.call("ir.actions.report", "print_document", [
- action.id,
- action.context.active_ids,
- action.data,
- ]);
+ var printer_exception = print_action.printer_exception;
+ if (print_action && print_action.action === "server" && !printer_exception) {
+ // The Odoo CUPS backend is ok. We try to print into the printer
+ const result = await orm.call(
+ "ir.actions.report",
+ "print_document_client_action",
+ [action.id, action.context.active_ids, action.data]
+ );
if (result) {
- env.services.notification.add(_t("Successfully sent to printer!"));
- } else {
- env.services.notification.add(_t("Could not sent to printer!"));
+ env.services.notification.add(_t("Successfully sent to printer!"), {
+ type: "success",
+ });
+ return true;
+ // In case of exception during the job, we won't get any response. So we
+ // should flag the exception and notify the user
}
+ env.services.notification.add(_t("Could not sent to printer!"), {
+ type: "danger",
+ });
+ printer_exception = true;
+ }
+ if (print_action && print_action.action === "server" && printer_exception) {
+ // Just so the translation engine detects them as it doesn't do it inside
+ // template strings
+ const terms = {
+ the_report: env._t("The report"),
+ couldnt_be_printed: env._t(
+ "couldn't be printed. Click on the button below to download it"
+ ),
+ issue_on: env._t("Issue on"),
+ };
+ const notificationRemove = env.services.notification.add(
+ Markup(
+ `
${terms.the_report} ${action.name} ${terms.couldnt_be_printed}
`
+ ),
+ {
+ title: `${terms.issue_on} ${print_action.printer_name}`,
+ type: "warning",
+ sticky: true,
+ messageIsHtml: true,
+ buttons: [
+ {
+ name: env._t("Print"),
+ primary: true,
+ icon: "fa-print",
+ onClick: async () => {
+ const context = {
+ force_print_to_client: true,
+ must_skip_send_to_printer: true,
+ };
+ env.services.user.updateContext(context);
+ await env.services.action.doAction(
+ {type: "ir.actions.report", ...action},
+ {
+ additionalContext: context,
+ }
+ );
+ notificationRemove();
+ },
+ },
+ ],
+ }
+ );
return true;
}
}
diff --git a/base_report_to_printer/tests/test_ir_actions_report.py b/base_report_to_printer/tests/test_ir_actions_report.py
index 0ba5a87..4e1e77d 100644
--- a/base_report_to_printer/tests/test_ir_actions_report.py
+++ b/base_report_to_printer/tests/test_ir_actions_report.py
@@ -12,10 +12,12 @@ model = "odoo.addons.base.models.ir_actions_report.IrActionsReport"
class TestIrActionsReportXml(TransactionCase):
def setUp(self):
super().setUp()
- self.Model = self.env["ir.actions.report"]
+ self.Model = self.env["ir.actions.report"].with_context(
+ skip_printer_exception=True
+ )
self.vals = {}
- self.report = self.env["ir.actions.report"].search([], limit=1)
+ self.report = self.Model.search([], limit=1)
self.server = self.env["printing.server"].create({})
def new_action(self):
@@ -153,7 +155,7 @@ class TestIrActionsReportXml(TransactionCase):
self.env.user.printing_action = "client"
printing_action = self.new_printing_action()
printing_action.user_id = self.env.user
- printing_action.report_id = self.env["ir.actions.report"].search(
+ printing_action.report_id = self.Model.search(
[("id", "!=", report.id)], limit=1
)
self.assertEqual(
@@ -213,7 +215,7 @@ class TestIrActionsReportXml(TransactionCase):
"""
It should return the correct tray
"""
- report = self.env["ir.actions.report"].search([], limit=1)
+ report = self.Model.search([], limit=1)
action = self.env["printing.report.xml.action"].create(
{"user_id": self.env.user.id, "report_id": report.id, "action": "server"}
)
@@ -266,7 +268,7 @@ class TestIrActionsReportXml(TransactionCase):
self.assertEqual("Action tray", report.behaviour()["tray"])
def test_onchange_printer_tray_id_empty(self):
- action = self.env["ir.actions.report"].new({"printer_tray_id": False})
+ action = self.Model.new({"printer_tray_id": False})
action.onchange_printing_printer_id()
self.assertFalse(action.printer_tray_id)
@@ -289,7 +291,25 @@ class TestIrActionsReportXml(TransactionCase):
{"name": "Tray", "system_name": "TrayName", "printer_id": printer.id}
)
- action = self.env["ir.actions.report"].new({"printer_tray_id": tray.id})
+ action = self.Model.new({"printer_tray_id": tray.id})
self.assertEqual(action.printer_tray_id, tray)
action.onchange_printing_printer_id()
self.assertFalse(action.printer_tray_id)
+
+ def test_print_in_new_thread(self):
+ """It should return the action and printer from printing action in other
+ thread"""
+ report = self.Model.search([], limit=1)
+ self.env.user.printing_action = "server"
+ printing_action = self.new_printing_action()
+ printing_action.user_id = self.env.user
+ printing_action.printer_id = self.new_printer()
+ printing_action.printer_id.multi_thread = True
+ self.assertEqual(
+ report.behaviour(),
+ {
+ "action": printing_action.action,
+ "printer": printing_action.printer_id,
+ "tray": False,
+ },
+ )
diff --git a/base_report_to_printer/tests/test_printing_printer_tray.py b/base_report_to_printer/tests/test_printing_printer_tray.py
index 1588643..c0168ff 100644
--- a/base_report_to_printer/tests/test_printing_printer_tray.py
+++ b/base_report_to_printer/tests/test_printing_printer_tray.py
@@ -40,7 +40,7 @@ class TestPrintingPrinter(TransactionCase):
self.server = self.env["printing.server"].create({})
self.printer = self.env["printing.printer"].create(
{
- "name": "Printer",
+ "name": "",
"server_id": self.server.id,
"system_name": "Sys Name",
"default": True,
@@ -105,10 +105,11 @@ class TestPrintingPrinter(TransactionCase):
Check that the update_printers method calls _prepare_update_from_cups
"""
self.mock_cups_ppd(cups, file_name=False)
-
- self.assertEqual(self.printer.name, "Printer")
self.ServerModel.update_printers()
self.assertEqual(self.printer.name, "info")
+ self.printer.name = "My custom name"
+ self.ServerModel.update_printers()
+ self.assertEqual(self.printer.name, "My custom name")
@mock.patch("%s.cups" % server_model)
def test_prepare_update_from_cups_no_ppd(self, cups):
diff --git a/base_report_to_printer/tests/test_report.py b/base_report_to_printer/tests/test_report.py
index e93d0a0..e821962 100644
--- a/base_report_to_printer/tests/test_report.py
+++ b/base_report_to_printer/tests/test_report.py
@@ -11,7 +11,9 @@ from odoo.tests import common
class TestReport(common.HttpCase):
def setUp(self):
super().setUp()
- self.Model = self.env["ir.actions.report"]
+ self.Model = self.env["ir.actions.report"].with_context(
+ skip_printer_exception=True
+ )
self.server = self.env["printing.server"].create({})
self.report_vals = {
"name": "Test Report",
diff --git a/base_report_to_printer/views/printing_printer.xml b/base_report_to_printer/views/printing_printer.xml
index 4424c84..0c7f0f4 100644
--- a/base_report_to_printer/views/printing_printer.xml
+++ b/base_report_to_printer/views/printing_printer.xml
@@ -78,6 +78,7 @@
+
diff --git a/base_report_to_printer/views/printing_server.xml b/base_report_to_printer/views/printing_server.xml
index de9a04b..259ec1a 100644
--- a/base_report_to_printer/views/printing_server.xml
+++ b/base_report_to_printer/views/printing_server.xml
@@ -32,6 +32,7 @@
+