diff --git a/printing_auto_base/README.rst b/printing_auto_base/README.rst new file mode 100644 index 0000000..2148a63 --- /dev/null +++ b/printing_auto_base/README.rst @@ -0,0 +1,80 @@ +================== +printing_auto_base +================== + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! 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/printing_auto_base + :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-printing_auto_base + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/webui/builds.html?repo=OCA/report-print-send&target_branch=14.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +Base module to support automatic printing of a report or attachments. + +Check other repo like stock-logistics-reporting module printing_auto_stock_picking +for printing documents related to a stock transfer. + +**Table of contents** + +.. contents:: + :local: + +Bug Tracker +=========== + +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 smashing it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* BCIM +* MT Software + +Contributors +~~~~~~~~~~~~ + +* Jacques-Etienne Baudoux (BCIM) +* Michael Tietz (MT Software) +* Camptocamp +* Christopher Hansen + +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 `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/printing_auto_base/__init__.py b/printing_auto_base/__init__.py new file mode 100644 index 0000000..0650744 --- /dev/null +++ b/printing_auto_base/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/printing_auto_base/__manifest__.py b/printing_auto_base/__manifest__.py new file mode 100644 index 0000000..6888c3b --- /dev/null +++ b/printing_auto_base/__manifest__.py @@ -0,0 +1,18 @@ +# Copyright 2022 Jacques-Etienne Baudoux (BCIM) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "Printing Auto Base", + "author": "BCIM, MT Software, Odoo Community Association (OCA)", + "maintainers": ["jbaudoux"], + "category": "Warehouse Management", + "data": [ + "views/printing_auto.xml", + ], + "depends": [ + "base_report_to_printer", + ], + "license": "AGPL-3", + "version": "14.0.1.0.0", + "website": "https://github.com/OCA/report-print-send", +} diff --git a/printing_auto_base/models/__init__.py b/printing_auto_base/models/__init__.py new file mode 100644 index 0000000..0b24867 --- /dev/null +++ b/printing_auto_base/models/__init__.py @@ -0,0 +1,2 @@ +from . import printing_auto +from . import printing_auto_mixin diff --git a/printing_auto_base/models/printing_auto.py b/printing_auto_base/models/printing_auto.py new file mode 100644 index 0000000..2216fc0 --- /dev/null +++ b/printing_auto_base/models/printing_auto.py @@ -0,0 +1,146 @@ +# Copyright 2022 Jacques-Etienne Baudoux (BCIM) +# Copyright 2022 Michael Tietz (MT Software) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import base64 + +from odoo import _, api, fields, models +from odoo.exceptions import UserError, ValidationError +from odoo.osv import expression +from odoo.tools.safe_eval import safe_eval + + +class PrintingAuto(models.Model): + """Configure which document to print automatically. This model must be + linked with a many2many relation from the another model from which you want + to print a document""" + + _name = "printing.auto" + _description = "Printing Auto" + + name = fields.Char(string="Name", required=True) + + data_source = fields.Selection( + [ + ("report", "Report"), + ("attachment", "Attachment"), + ], + string="Data source", + default="report", + required=True, + help=( + "Choose to print the result of an odoo report or a pre-existing " + "attachment (useful for labels received from carriers that are " + "recorded on the picking as an attachment)" + ), + ) + report_id = fields.Many2one("ir.actions.report") + attachment_domain = fields.Char("Attachment domain", default="[]") + + condition = fields.Char( + "Condition", + default="[]", + help="Give a domain that must be valid for printing this", + ) + record_change = fields.Char( + "Record change", + help="Select on which document the report must be executed. Use a path " + "using a dotted notation starting from any record field. For " + "example, if your record is a stock.picking, you can access the " + "next picking with 'move_lines.move_dest_ids.picking_id'", + ) + + printer_id = fields.Many2one("printing.printer", "Printer") + printer_tray_id = fields.Many2one("printing.tray", "Tray") + nbr_of_copies = fields.Integer("Number of Copies", default=1) + + @api.constrains("data_source", "report_id", "attachment_domain") + def _check_data_source(self): + for rec in self: + if rec.data_source == "report" and not rec.report_id: + raise UserError(_("Report is not set")) + if rec.data_source == "attachment" and ( + not rec.attachment_domain or rec.attachment_domain == "[]" + ): + raise UserError(_("Attachment domain is not set")) + + def _get_behaviour(self): + if self.printer_id: + result = {"printer": self.printer_id} + if self.printer_tray_id: + result["tray"] = self.printer_tray_id.system_name + return result + if self.data_source == "report": + return self.report_id.behaviour() + return self.env["ir.actions.report"]._get_user_default_print_behaviour() + + def _get_record(self, record): + if self.record_change: + try: + return safe_eval(f"obj.{self.record_change}", {"obj": record}) + except Exception as e: + raise ValidationError( + _("The Record change could not be applied because: %s") % str(e) + ) from e + return record + + def _check_condition(self, record): + domain = safe_eval(self.condition, {"env": self.env}) + return record.filtered_domain(domain) + + def _get_content(self, record): + generate_data_func = getattr( + self, f"_generate_data_from_{self.data_source}", None + ) + content = [] + if generate_data_func: + records = self._get_record(record) + for record in records: + content.append(generate_data_func(record)[0]) + return content + + def _prepare_attachment_domain(self, record): + domain = safe_eval(self.attachment_domain) + record_domain = [ + ("res_id", "=", record.id), + ("res_model", "=", record._name), + ] + return expression.AND([domain, record_domain]) + + def _generate_data_from_attachment(self, record): + domain = self._prepare_attachment_domain(record) + attachments = self.env["ir.attachment"].search(domain) + if not attachments: + raise ValidationError(_("No attachment was found.")) + return [base64.b64decode(a.datas) for a in attachments] + + def _generate_data_from_report(self, record): + self.ensure_one() + data, _ = self.report_id.with_context(must_skip_send_to_printer=True)._render( + record.id + ) + return [data] + + def do_print(self, record): + self.ensure_one() + record.ensure_one() + + behaviour = self._get_behaviour() + printer = behaviour["printer"] + + if self.nbr_of_copies <= 0: + return (printer, 0) + if not self._check_condition(record): + return (printer, 0) + + if not printer: + raise UserError( + _("No printer configured to print this {}.").format(self.name) + ) + + count = 0 + for content in self._get_content(record): + for _n in range(self.nbr_of_copies): + printer.print_document(report=None, content=content, **behaviour) + count += 1 + return (printer, count) diff --git a/printing_auto_base/models/printing_auto_mixin.py b/printing_auto_base/models/printing_auto_mixin.py new file mode 100644 index 0000000..036415b --- /dev/null +++ b/printing_auto_base/models/printing_auto_mixin.py @@ -0,0 +1,61 @@ +# Copyright 2022 Jacques-Etienne Baudoux (BCIM) +# Copyright 2022 Michael Tietz (MT Software) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import logging + +from odoo import _, fields, models + +_logger = logging.getLogger(__name__) + + +class PrintingAutoMixin(models.AbstractModel): + _name = "printing.auto.mixin" + _description = "Printing Auto Mixin" + + auto_printing_ids = fields.Many2many( + "printing.auto", string="Auto Printing Configuration" + ) + printing_auto_error = fields.Text("Printing error") + + def _on_printing_auto_start(self): + self.write({"printing_auto_error": False}) + + def _printing_auto_done_post(self, auto, printer, count): + self.ensure_one() + self.message_post( + body=_("{name}: {count} document(s) sent to printer {printer}").format( + name=auto.name, count=count, printer=printer.name + ) + ) + + def _on_printing_auto_done(self, auto, printer, count): + self._printing_auto_done_post(auto, printer, count) + + def _on_printing_auto_error(self, e): + self.write({"printing_auto_error": str(e)}) + + def _get_printing_auto(self): + return self.auto_printing_ids + + def _handle_print_auto(self, printing_auto): + self.ensure_one() + printing_auto.ensure_one() + try: + with self.env.cr.savepoint(): + printer, count = printing_auto.do_print(self) + if count: + self._on_printing_auto_done(printing_auto, printer, count) + except Exception as e: + _logger.exception( + "An error occurred while printing '%s' for record %s.", + printing_auto, + self, + ) + self._on_printing_auto_error(e) + + def handle_print_auto(self): + """Print some report or attachment directly to the corresponding printer.""" + self._on_printing_auto_start() + for record in self: + for printing_auto in record._get_printing_auto(): + record._handle_print_auto(printing_auto) diff --git a/printing_auto_base/readme/CONTRIBUTORS.rst b/printing_auto_base/readme/CONTRIBUTORS.rst new file mode 100644 index 0000000..6a130e8 --- /dev/null +++ b/printing_auto_base/readme/CONTRIBUTORS.rst @@ -0,0 +1,4 @@ +* Jacques-Etienne Baudoux (BCIM) +* Michael Tietz (MT Software) +* Camptocamp +* Christopher Hansen diff --git a/printing_auto_base/readme/DESCRIPTION.rst b/printing_auto_base/readme/DESCRIPTION.rst new file mode 100644 index 0000000..82ead2a --- /dev/null +++ b/printing_auto_base/readme/DESCRIPTION.rst @@ -0,0 +1,4 @@ +Base module to support automatic printing of a report or attachments. + +Check other repo like stock-logistics-reporting module printing_auto_stock_picking +for printing documents related to a stock transfer. diff --git a/printing_auto_base/static/description/index.html b/printing_auto_base/static/description/index.html new file mode 100644 index 0000000..0180299 --- /dev/null +++ b/printing_auto_base/static/description/index.html @@ -0,0 +1,423 @@ + + + + + + +printing_auto_base + + + +
+

printing_auto_base

+ + +

Beta License: AGPL-3 OCA/report-print-send Translate me on Weblate Try me on Runboat

+

Base module to support automatic priting of a report or attachments.

+

Table of contents

+ +
+

Bug Tracker

+

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 smashing it by providing a detailed and welcomed +feedback.

+

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

+
+
+

Credits

+
+

Authors

+
    +
  • BCIM
  • +
  • MT Software
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

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 project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/printing_auto_base/tests/__init__.py b/printing_auto_base/tests/__init__.py new file mode 100644 index 0000000..998b914 --- /dev/null +++ b/printing_auto_base/tests/__init__.py @@ -0,0 +1 @@ +from . import test_printing_auto_base diff --git a/printing_auto_base/tests/common.py b/printing_auto_base/tests/common.py new file mode 100644 index 0000000..1cd0d11 --- /dev/null +++ b/printing_auto_base/tests/common.py @@ -0,0 +1,92 @@ +# Copyright 2022 Jacques-Etienne Baudoux (BCIM) +# Copyright 2022 Michael Tietz (MT Software) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + + +from odoo.tests import common + + +def print_document(cls, *args, **kwargs): + return + + +class TestPrintingAutoCommon(common.SavepointCase): + @classmethod + def _create_printer(cls, name): + return cls.env["printing.printer"].create( + { + "name": name, + "system_name": name, + "server_id": cls.server.id, + } + ) + + @classmethod + def _create_tray(cls, name, printer): + return cls.env["printing.tray"].create( + {"name": name, "system_name": name, "printer_id": printer.id} + ) + + @classmethod + def setUpReportAndRecord(cls): + cls.report = cls.env.ref("base.report_ir_model_overview") + cls.record = cls.env.ref("base.model_res_partner") + + @classmethod + def setUpClass(cls): + super().setUpClass() + + cls.server = cls.env["printing.server"].create({}) + for i in range(1, 4): + printer_name = f"printer_{i}" + tray_name = f"tray_{i}" + printer = cls._create_printer(printer_name) + tray = cls._create_tray(tray_name, printer) + setattr(cls, printer_name, printer) + setattr(cls, tray_name, tray) + + cls.setUpReportAndRecord() + cls.data = cls.report._render(cls.record.id)[0] + + @classmethod + def _create_printing_auto(cls, vals): + return cls.env["printing.auto"].create(vals) + + @classmethod + def _create_attachment(cls, record, data, name_suffix): + return cls.env["ir.attachment"].create( + { + "res_model": record._name, + "res_id": record.id, + "name": f"printing_auto_test_attachment_{name_suffix}.txt", + "raw": data, + } + ) + + @classmethod + def _prepare_printing_auto_report_vals(cls): + return { + "data_source": "report", + "report_id": cls.report.id, + "name": "Printing auto report", + } + + @classmethod + def _create_printing_auto_report(cls, vals=None): + _vals = cls._prepare_printing_auto_report_vals() + _vals.update(vals or {}) + return cls._create_printing_auto(_vals) + + @classmethod + def _prepare_printing_auto_attachment_vals(cls): + return { + "data_source": "attachment", + "attachment_domain": "[('name', 'like', 'printing_auto_test_attachment')]", + "name": "Printing auto attachment", + } + + @classmethod + def _create_printing_auto_attachment(cls, vals=None): + _vals = cls._prepare_printing_auto_attachment_vals() + _vals.update(vals or {}) + return cls._create_printing_auto(_vals) diff --git a/printing_auto_base/tests/test_printing_auto_base.py b/printing_auto_base/tests/test_printing_auto_base.py new file mode 100644 index 0000000..1e1607b --- /dev/null +++ b/printing_auto_base/tests/test_printing_auto_base.py @@ -0,0 +1,92 @@ +# Copyright 2022 Jacques-Etienne Baudoux (BCIM) +# Copyright 2022 Michael Tietz (MT Software) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from unittest import mock + +from odoo.exceptions import UserError, ValidationError + +from .common import PrintingPrinter, TestPrintingAutoCommon, print_document + + +@mock.patch.object(PrintingPrinter, "print_document", print_document) +class TestPrintingAutoBase(TestPrintingAutoCommon): + def test_check_data_source(self): + with self.assertRaises(UserError): + self._create_printing_auto_report({"report_id": False}) + + with self.assertRaises(UserError): + self._create_printing_auto_attachment({"attachment_domain": "[]"}) + + with self.assertRaises(UserError): + printing_auto = self._create_printing_auto_attachment() + printing_auto.attachment_domain = False + + def test_behaviour(self): + expected = {"printer": self.printer_1} + printing_auto = self._create_printing_auto_report( + {"printer_id": self.printer_1.id} + ) + self.assertEqual(expected, printing_auto._get_behaviour()) + + printing_auto.printer_tray_id = self.tray_1 + expected["tray"] = self.tray_1.system_name + self.assertEqual(expected, printing_auto._get_behaviour()) + + expected = printing_auto.report_id.behaviour() + printing_auto.printer_id = False + printing_auto.printer_tray_id = False + self.assertEqual(expected, printing_auto._get_behaviour()) + + expected = self.env["ir.actions.report"]._get_user_default_print_behaviour() + printing_auto = self._create_printing_auto_attachment() + self.assertEqual(expected, printing_auto._get_behaviour()) + + def test_record_change(self): + parent = self.env["res.partner"].create({"name": "Parent"}) + partner = parent.create({"name": "Child", "parent_id": parent.id}) + printing_auto = self._create_printing_auto_report( + {"record_change": "parent_id"} + ) + self.assertEqual(parent, printing_auto._get_record(partner)) + + def test_check_condition(self): + partner = self.env["res.partner"].create({"name": "Partner"}) + printing_auto = self._create_printing_auto_report( + {"condition": f"[('name', '=', '{partner.name}')]"} + ) + self.assertEqual(partner, printing_auto._check_condition(partner)) + printing_auto.condition = "[('name', '=', '1')]" + self.assertFalse(printing_auto._check_condition(partner)) + + def test_get_content(self): + printing_auto_report = self._create_printing_auto_report() + self.assertEqual([self.data], printing_auto_report._get_content(self.record)) + + printing_auto_attachment = self._create_printing_auto_attachment() + attachment = self._create_attachment(self.record, self.data, "1") + self.assertEqual( + [attachment.raw], printing_auto_attachment._get_content(self.record) + ) + attachment.unlink() + + with self.assertRaises(ValidationError): + printing_auto_attachment._get_content(self.record) + + def test_do_print(self): + printing_auto = self._create_printing_auto_attachment( + {"attachment_domain": "[('name', 'like', 'printing_auto_test')]"} + ) + self._create_attachment(self.record, self.data, "1") + with self.assertRaises(UserError): + printing_auto.do_print(self.record) + + printing_auto.printer_id = self.printer_1 + for nbr_of_copies in [0, 2, 1]: + expected = (self.printer_1, nbr_of_copies) + printing_auto.nbr_of_copies = nbr_of_copies + self.assertEqual(expected, printing_auto.do_print(self.record)) + + printing_auto.condition = "[('name', '=', 'test_printing_auto')]" + expected = (self.printer_1, 0) + self.assertEqual(expected, printing_auto.do_print(self.record)) diff --git a/printing_auto_base/views/printing_auto.xml b/printing_auto_base/views/printing_auto.xml new file mode 100644 index 0000000..a346bd5 --- /dev/null +++ b/printing_auto_base/views/printing_auto.xml @@ -0,0 +1,50 @@ + + + printing.auto.view.form + printing.auto + +
+ + + + + + + + + + + + + +
+
+
+ + + printing.auto.view.tree + printing.auto + + + + + + + + + + + + + + +
diff --git a/setup/printing_auto_base/odoo/addons/printing_auto_base b/setup/printing_auto_base/odoo/addons/printing_auto_base new file mode 120000 index 0000000..b17e178 --- /dev/null +++ b/setup/printing_auto_base/odoo/addons/printing_auto_base @@ -0,0 +1 @@ +../../../../printing_auto_base \ No newline at end of file diff --git a/setup/printing_auto_base/setup.py b/setup/printing_auto_base/setup.py new file mode 100644 index 0000000..28c57bb --- /dev/null +++ b/setup/printing_auto_base/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)