From 8eb77aa62a0220b9c6d4a6d1f2f64df0501fbd43 Mon Sep 17 00:00:00 2001 From: Denis Roussel Date: Wed, 16 Jul 2025 13:36:32 +0200 Subject: [PATCH 1/2] [IMP] base_report_to_printer: Add ability to send a print quantity If we use the direct printing function, be able to transmit the quantity of copies we want. --- .../models/ir_actions_report.py | 37 ++++++++++---- .../tests/test_printing_printer.py | 14 ++++++ base_report_to_printer/tests/test_report.py | 48 +++++++++++++++++++ 3 files changed, 91 insertions(+), 8 deletions(-) diff --git a/base_report_to_printer/models/ir_actions_report.py b/base_report_to_printer/models/ir_actions_report.py index 8af514383c5..2c37a8f3cdc 100644 --- a/base_report_to_printer/models/ir_actions_report.py +++ b/base_report_to_printer/models/ir_actions_report.py @@ -85,12 +85,33 @@ def _get_report_default_print_behaviour(self): result["tray"] = self.printer_tray_id.system_name return result - def behaviour(self): + def _set_extra_print_options(self, extras): + """ + extra: A dictionary with extra print options + """ + extras_options = {} + for extra, value in extras.items(): + try: + extras_options.update( + getattr(self, "_set_print_extra_%s" % extra)(value) + ) + except AttributeError: + extras_options[extra] = str(value) + return extras_options + + def _set_print_extra_quantity(self, value): + """ + We add the 'copies' attribute + """ + return {"copies": str(value)} if value else {} + + def behaviour(self, **extras): self.ensure_one() printing_act_obj = self.env["printing.report.xml.action"] result = self._get_user_default_print_behaviour() result.update(self._get_report_default_print_behaviour()) + result.update(self._set_extra_print_options(extras=extras)) # Retrieve report-user specific values print_action = printing_act_obj.search( @@ -122,8 +143,8 @@ def behaviour(self): result["printer_exception"] = True return result - def print_document_client_action(self, record_ids, data=None): - behaviour = self.behaviour() + def print_document_client_action(self, record_ids, data=None, **extras): + behaviour = self.behaviour(**extras) printer = behaviour.pop("printer", None) if printer.multi_thread: @@ -138,17 +159,17 @@ def _launch_print_thread(): return True else: try: - return self.print_document(record_ids, data=data) + return self.print_document(record_ids, data=data, **extras) except Exception: return - def print_document_threaded(self, report_id, record_ids, data): + def print_document_threaded(self, report_id, record_ids, data, **extras): 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) + report.print_document(record_ids, data, **extras) - def print_document(self, record_ids, data=None): + def print_document(self, record_ids, data=None, **extras): """Print a document, do not return the document file""" report_type = REPORT_TYPES.get(self.report_type) if not report_type: @@ -160,7 +181,7 @@ def print_document(self, record_ids, data=None): document, doc_format = getattr( self.with_context(must_skip_send_to_printer=True), method_name )(self.report_name, record_ids, data=data) - behaviour = self.behaviour() + behaviour = self.behaviour(**extras) printer = behaviour.pop("printer", None) if not printer: diff --git a/base_report_to_printer/tests/test_printing_printer.py b/base_report_to_printer/tests/test_printing_printer.py index 6cadf02cc75..e0d710b0a3c 100644 --- a/base_report_to_printer/tests/test_printing_printer.py +++ b/base_report_to_printer/tests/test_printing_printer.py @@ -180,3 +180,17 @@ def test_print_test_page(self, cups): printer = self.new_record() printer.print_test_page() cups.Connection().printTestPage.assert_called_once_with(printer.system_name) + + @mock.patch("%s.cups" % server_model) + def test_print_report_quantity(self, cups): + """It should print a report through CUPS""" + fd, file_name = tempfile.mkstemp() + with mock.patch("%s.mkstemp" % model) as mkstemp: + mkstemp.return_value = fd, file_name + printer = self.new_record() + printer.print_document( + self.report, b"content to print", doc_format="pdf", **{"copies": 3} + ) + cups.Connection().printFile.assert_called_once_with( + printer.system_name, file_name, file_name, options={"copies": "3"} + ) diff --git a/base_report_to_printer/tests/test_report.py b/base_report_to_printer/tests/test_report.py index d101c1863eb..4c633c8c81a 100644 --- a/base_report_to_printer/tests/test_report.py +++ b/base_report_to_printer/tests/test_report.py @@ -7,6 +7,8 @@ from odoo import exceptions from odoo.tests import common +REPORT_TYPES = {"qweb-pdf": "pdf", "qweb-text": "text"} + class TestReport(common.HttpCase): def setUp(self): @@ -203,3 +205,49 @@ def test_print_document_string(self): ) as print_file: self.new_printer().print_document("", "test") print_file.assert_called_once() + + def test_print_document_printable_quantity(self): + """It should print the report, regardless of the defined behaviour""" + self.report.property_printing_action_id.action_type = "server" + self.report.printing_printer_id = self.new_printer() + report_type = REPORT_TYPES.get(self.report.report_type) + behaviour = self.report.behaviour(**{"quantity": 3}) + behaviour.pop("printer") + method_name = "_render_qweb_%s" % (report_type) + document, doc_format = getattr( + self.report.with_context(must_skip_send_to_printer=True), method_name + )(self.report.report_name, self.partners.ids, data=None) + behaviour["title"] = self.report.report_name + behaviour["res_ids"] = self.partners.ids + with mock.patch( + "odoo.addons.base_report_to_printer.models." + "printing_printer.PrintingPrinter." + "print_document" + ) as print_document: + self.report.print_document(self.partners.ids, **{"quantity": 3}) + print_document.assert_called_once_with( + self.report, document, doc_format=self.report.report_type, **behaviour + ) + + def test_print_document_printable_unknown(self): + """It should print the report, regardless of the defined behaviour""" + self.report.property_printing_action_id.action_type = "server" + self.report.printing_printer_id = self.new_printer() + report_type = REPORT_TYPES.get(self.report.report_type) + behaviour = self.report.behaviour(**{"unknown": "test"}) + behaviour.pop("printer") + method_name = "_render_qweb_%s" % (report_type) + document, doc_format = getattr( + self.report.with_context(must_skip_send_to_printer=True), method_name + )(self.report.report_name, self.partners.ids, data=None) + behaviour["title"] = self.report.report_name + behaviour["res_ids"] = self.partners.ids + with mock.patch( + "odoo.addons.base_report_to_printer.models." + "printing_printer.PrintingPrinter." + "print_document" + ) as print_document: + self.report.print_document(self.partners.ids, **{"unknown": "test"}) + print_document.assert_called_once_with( + self.report, document, doc_format=self.report.report_type, **behaviour + ) From 4a307aef5923daa7fbb2b477f306bc68dbe0bb8b Mon Sep 17 00:00:00 2001 From: Denis Roussel Date: Thu, 11 Sep 2025 13:46:01 +0200 Subject: [PATCH 2/2] [IMP] base_report_to_printer: Add a printer extra parameter For a manual printing, users may want to choose manually the printer on which to print. --- .../models/ir_actions_report.py | 11 +++++++ base_report_to_printer/tests/test_report.py | 29 ++++++++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/base_report_to_printer/models/ir_actions_report.py b/base_report_to_printer/models/ir_actions_report.py index 2c37a8f3cdc..fcc242bf947 100644 --- a/base_report_to_printer/models/ir_actions_report.py +++ b/base_report_to_printer/models/ir_actions_report.py @@ -105,6 +105,17 @@ def _set_print_extra_quantity(self, value): """ return {"copies": str(value)} if value else {} + def _set_print_extra_printer(self, value): + """ + We force the printer that will be used + The printer should be a recordset + """ + if isinstance(value, int): + printer = self.env["printing.printer"].browse(value) + else: + printer = value + return {"printer": printer} if printer else {} + def behaviour(self, **extras): self.ensure_one() printing_act_obj = self.env["printing.report.xml.action"] diff --git a/base_report_to_printer/tests/test_report.py b/base_report_to_printer/tests/test_report.py index 4c633c8c81a..2056dc7a4ac 100644 --- a/base_report_to_printer/tests/test_report.py +++ b/base_report_to_printer/tests/test_report.py @@ -1,7 +1,6 @@ # Copyright 2016 LasLabs Inc. # Copyright 2017 Tecnativa - Jairo Llopis # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). - from unittest import mock from odoo import exceptions @@ -9,6 +8,8 @@ REPORT_TYPES = {"qweb-pdf": "pdf", "qweb-text": "text"} +server_model = "odoo.addons.base_report_to_printer.models.printing_server" + class TestReport(common.HttpCase): def setUp(self): @@ -251,3 +252,29 @@ def test_print_document_printable_unknown(self): print_document.assert_called_once_with( self.report, document, doc_format=self.report.report_type, **behaviour ) + + @mock.patch("%s.cups" % server_model) + def test_print_document_printer_extra(self, cups): + """ + It should print the report to the desired printer + """ + self.report.property_printing_action_id.action_type = "server" + self.report.printing_printer_id = self.new_printer() + forced_printer = self.new_printer() + forced_printer.system_name = "Forced Printer" + report_type = REPORT_TYPES.get(self.report.report_type) + behaviour = self.report.behaviour(**{"printer": forced_printer}) + behaviour.pop("printer") + method_name = "_render_qweb_%s" % (report_type) + document, doc_format = getattr( + self.report.with_context(must_skip_send_to_printer=True), method_name + )(self.report.report_name, self.partners.ids, data=None) + behaviour["title"] = self.report.report_name + behaviour["res_ids"] = self.partners.ids + extras = { + "printer": forced_printer, + } + self.report.print_document(self.partners.ids, **extras) + self.assertEqual( + "Forced Printer", cups.Connection().printFile.call_args.args[0] + )