diff --git a/printing_auto_base/README.rst b/printing_auto_base/README.rst new file mode 100644 index 00000000000..6460cc85b26 --- /dev/null +++ b/printing_auto_base/README.rst @@ -0,0 +1,96 @@ +.. image:: https://odoo-community.org/readme-banner-image + :target: https://odoo-community.org/get-involved?utm_source=readme + :alt: Odoo Community Association + +================== +Printing Auto Base +================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:01c1d324f32a529bb0df28fc4d0b2336f2766ed314386725fdd3bd1a7e41b1ea + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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/license-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/19.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-19-0/report-print-send-19-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/builds?repo=OCA/report-print-send&target_branch=19.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 +printingauto_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 to smash 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. + +.. |maintainer-jbaudoux| image:: https://github.com/jbaudoux.png?size=40px + :target: https://github.com/jbaudoux + :alt: jbaudoux + +Current `maintainer `__: + +|maintainer-jbaudoux| + +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 00000000000..0650744f6bc --- /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 00000000000..e08dfb84c16 --- /dev/null +++ b/printing_auto_base/__manifest__.py @@ -0,0 +1,19 @@ +# 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": [ + "security/ir.model.access.csv", + "views/printing_auto.xml", + ], + "depends": [ + "base_report_to_printer_cups", + ], + "license": "AGPL-3", + "version": "19.0.1.0.0", + "website": "https://github.com/OCA/report-print-send", +} diff --git a/printing_auto_base/i18n/it.po b/printing_auto_base/i18n/it.po new file mode 100644 index 00000000000..2ab15d1d943 --- /dev/null +++ b/printing_auto_base/i18n/it.po @@ -0,0 +1,211 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * printing_auto_base +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2025-10-02 21:43+0000\n" +"Last-Translator: mymage \n" +"Language-Team: none\n" +"Language: it\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 5.10.4\n" + +#. module: printing_auto_base +#: model:ir.model.fields,field_description:printing_auto_base.field_printing_auto__action_on_error +msgid "Action on error" +msgstr "Azione o errore" + +#. module: printing_auto_base +#: model:ir.model.fields.selection,name:printing_auto_base.selection__printing_auto__data_source__attachment +msgid "Attachment" +msgstr "Allegato" + +#. module: printing_auto_base +#: model:ir.model.fields,field_description:printing_auto_base.field_printing_auto__attachment_domain +msgid "Attachment domain" +msgstr "Dominio allegato" + +#. module: printing_auto_base +#. odoo-python +#: code:addons/printing_auto_base/models/printing_auto.py:0 +msgid "Attachment domain is not set" +msgstr "Il dominio allegato non è definito" + +#. module: printing_auto_base +#: model:ir.model.fields,field_description:printing_auto_base.field_printing_auto_mixin__auto_printing_ids +msgid "Auto Printing Configuration" +msgstr "Configurazione stampa automatica" + +#. module: printing_auto_base +#: model:ir.model.fields,help:printing_auto_base.field_printing_auto__data_source +msgid "" +"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)" +msgstr "" +"Scegliere se stampare il risultato di un resoconto Odoo o di un allegato " +"preesistente (utile per le etichette ricevute dai corrieri che vengono " +"registrate nel prelievo come allegato)" + +#. module: printing_auto_base +#: model:ir.model.fields,field_description:printing_auto_base.field_printing_auto__condition +msgid "Condition" +msgstr "Condizione" + +#. module: printing_auto_base +#: model:ir.model.fields,field_description:printing_auto_base.field_printing_auto__create_uid +msgid "Created by" +msgstr "Creato da" + +#. module: printing_auto_base +#: model:ir.model.fields,field_description:printing_auto_base.field_printing_auto__create_date +msgid "Created on" +msgstr "Creato il" + +#. module: printing_auto_base +#: model:ir.model.fields,field_description:printing_auto_base.field_printing_auto__data_source +msgid "Data source" +msgstr "Origine dati" + +#. module: printing_auto_base +#: model:ir.model.fields,field_description:printing_auto_base.field_printing_auto__display_name +msgid "Display Name" +msgstr "Nome visualizzato" + +#. module: printing_auto_base +#: model:ir.model.fields,help:printing_auto_base.field_printing_auto__condition +msgid "Give a domain that must be valid for printing this" +msgstr "Fornire un dominio che deve essere valido per stampare questo" + +#. module: printing_auto_base +#: model:ir.model.fields,field_description:printing_auto_base.field_printing_auto__id +msgid "ID" +msgstr "ID" + +#. module: printing_auto_base +#: model:ir.model.fields,field_description:printing_auto_base.field_printing_auto__write_uid +msgid "Last Updated by" +msgstr "Ultimo aggiornamento di" + +#. module: printing_auto_base +#: model:ir.model.fields,field_description:printing_auto_base.field_printing_auto__write_date +msgid "Last Updated on" +msgstr "Ultimo aggiornamento il" + +#. module: printing_auto_base +#: model:ir.model.fields,field_description:printing_auto_base.field_printing_auto__name +msgid "Name" +msgstr "Nome" + +#. module: printing_auto_base +#. odoo-python +#: code:addons/printing_auto_base/models/printing_auto.py:0 +msgid "No attachment was found." +msgstr "Nessun allegato trovato." + +#. module: printing_auto_base +#. odoo-python +#: code:addons/printing_auto_base/models/printing_auto.py:0 +msgid "No printer configured to print this {}." +msgstr "Nessuna stampante configurata per stampare questo {}." + +#. module: printing_auto_base +#: model:ir.model.fields,field_description:printing_auto_base.field_printing_auto__nbr_of_copies +msgid "Number of Copies" +msgstr "Numero di copie" + +#. module: printing_auto_base +#: model:ir.model.fields,field_description:printing_auto_base.field_printing_auto__printer_id +msgid "Printer" +msgstr "Stampante" + +#. module: printing_auto_base +#: model:ir.model,name:printing_auto_base.model_printing_auto +msgid "Printing Auto" +msgstr "Stampa automatica" + +#. module: printing_auto_base +#: model:ir.model,name:printing_auto_base.model_printing_auto_mixin +msgid "Printing Auto Mixin" +msgstr "Mixin stampante automatica" + +#. module: printing_auto_base +#: model:ir.model.fields,field_description:printing_auto_base.field_printing_auto_mixin__printing_auto_error +msgid "Printing error" +msgstr "Errore di stampa" + +#. module: printing_auto_base +#: model:ir.model.fields.selection,name:printing_auto_base.selection__printing_auto__action_on_error__raise +msgid "Raise an Exception" +msgstr "Genera un'eccezione" + +#. module: printing_auto_base +#: model:ir.model.fields.selection,name:printing_auto_base.selection__printing_auto__action_on_error__log +msgid "Record an error" +msgstr "Registra un errore" + +#. module: printing_auto_base +#: model:ir.model.fields,field_description:printing_auto_base.field_printing_auto__record_change +msgid "Record change" +msgstr "Modifica record" + +#. module: printing_auto_base +#: model:ir.model.fields,field_description:printing_auto_base.field_printing_auto__model +msgid "Related Document Model" +msgstr "Modello documento correlato" + +#. module: printing_auto_base +#: model:ir.model.fields,field_description:printing_auto_base.field_printing_auto__report_id +#: model:ir.model.fields.selection,name:printing_auto_base.selection__printing_auto__data_source__report +msgid "Report" +msgstr "Resoconto" + +#. module: printing_auto_base +#. odoo-python +#: code:addons/printing_auto_base/models/printing_auto.py:0 +msgid "Report is not set" +msgstr "Il resoconto on è impostato" + +#. module: printing_auto_base +#: model:ir.model.fields,help:printing_auto_base.field_printing_auto__record_change +msgid "" +"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'" +msgstr "" +"Selezionare il documento su cui deve essere eseguito il resoconto. Utilizza " +"un percorso con notazione puntata a partire da qualsiasi campo del record. " +"Ad esempio, se il record è \"stock.picking\", si può accedere al prelievo " +"successivo con \"move_lines.move_dest_ids.picking_id\"" + +#. module: printing_auto_base +#: model:ir.model.fields,field_description:printing_auto_base.field_printing_auto__sequence +msgid "Sequence" +msgstr "Sequenza" + +#. module: printing_auto_base +#. odoo-python +#: code:addons/printing_auto_base/models/printing_auto.py:0 +msgid "The Record change could not be applied because: %s" +msgstr "La modifica del record non si può applicare perché: %s" + +#. module: printing_auto_base +#: model:ir.model.fields,field_description:printing_auto_base.field_printing_auto__printer_tray_id +msgid "Tray" +msgstr "Vassoio" + +#. module: printing_auto_base +#. odoo-python +#: code:addons/printing_auto_base/models/printing_auto_mixin.py:0 +msgid "{name}: {count} document(s) sent to printer {printer}" +msgstr "{name}: {count} document(s) invia alla stampante {printer}" + +#~ msgid "Last Modified on" +#~ msgstr "Ultima modifica il" diff --git a/printing_auto_base/i18n/printing_auto_base.pot b/printing_auto_base/i18n/printing_auto_base.pot new file mode 100644 index 00000000000..a851cd75ce8 --- /dev/null +++ b/printing_auto_base/i18n/printing_auto_base.pot @@ -0,0 +1,198 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * printing_auto_base +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 18.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: printing_auto_base +#: model:ir.model.fields,field_description:printing_auto_base.field_printing_auto__action_on_error +msgid "Action on error" +msgstr "" + +#. module: printing_auto_base +#: model:ir.model.fields.selection,name:printing_auto_base.selection__printing_auto__data_source__attachment +msgid "Attachment" +msgstr "" + +#. module: printing_auto_base +#: model:ir.model.fields,field_description:printing_auto_base.field_printing_auto__attachment_domain +msgid "Attachment domain" +msgstr "" + +#. module: printing_auto_base +#. odoo-python +#: code:addons/printing_auto_base/models/printing_auto.py:0 +msgid "Attachment domain is not set" +msgstr "" + +#. module: printing_auto_base +#: model:ir.model.fields,field_description:printing_auto_base.field_printing_auto_mixin__auto_printing_ids +msgid "Auto Printing Configuration" +msgstr "" + +#. module: printing_auto_base +#: model:ir.model.fields,help:printing_auto_base.field_printing_auto__data_source +msgid "" +"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)" +msgstr "" + +#. module: printing_auto_base +#: model:ir.model.fields,field_description:printing_auto_base.field_printing_auto__condition +msgid "Condition" +msgstr "" + +#. module: printing_auto_base +#: model:ir.model.fields,field_description:printing_auto_base.field_printing_auto__create_uid +msgid "Created by" +msgstr "" + +#. module: printing_auto_base +#: model:ir.model.fields,field_description:printing_auto_base.field_printing_auto__create_date +msgid "Created on" +msgstr "" + +#. module: printing_auto_base +#: model:ir.model.fields,field_description:printing_auto_base.field_printing_auto__data_source +msgid "Data source" +msgstr "" + +#. module: printing_auto_base +#: model:ir.model.fields,field_description:printing_auto_base.field_printing_auto__display_name +msgid "Display Name" +msgstr "" + +#. module: printing_auto_base +#: model:ir.model.fields,help:printing_auto_base.field_printing_auto__condition +msgid "Give a domain that must be valid for printing this" +msgstr "" + +#. module: printing_auto_base +#: model:ir.model.fields,field_description:printing_auto_base.field_printing_auto__id +msgid "ID" +msgstr "" + +#. module: printing_auto_base +#: model:ir.model.fields,field_description:printing_auto_base.field_printing_auto__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: printing_auto_base +#: model:ir.model.fields,field_description:printing_auto_base.field_printing_auto__write_date +msgid "Last Updated on" +msgstr "" + +#. module: printing_auto_base +#: model:ir.model.fields,field_description:printing_auto_base.field_printing_auto__name +msgid "Name" +msgstr "" + +#. module: printing_auto_base +#. odoo-python +#: code:addons/printing_auto_base/models/printing_auto.py:0 +msgid "No attachment was found." +msgstr "" + +#. module: printing_auto_base +#. odoo-python +#: code:addons/printing_auto_base/models/printing_auto.py:0 +msgid "No printer configured to print this {}." +msgstr "" + +#. module: printing_auto_base +#: model:ir.model.fields,field_description:printing_auto_base.field_printing_auto__nbr_of_copies +msgid "Number of Copies" +msgstr "" + +#. module: printing_auto_base +#: model:ir.model.fields,field_description:printing_auto_base.field_printing_auto__printer_id +msgid "Printer" +msgstr "" + +#. module: printing_auto_base +#: model:ir.model,name:printing_auto_base.model_printing_auto +msgid "Printing Auto" +msgstr "" + +#. module: printing_auto_base +#: model:ir.model,name:printing_auto_base.model_printing_auto_mixin +msgid "Printing Auto Mixin" +msgstr "" + +#. module: printing_auto_base +#: model:ir.model.fields,field_description:printing_auto_base.field_printing_auto_mixin__printing_auto_error +msgid "Printing error" +msgstr "" + +#. module: printing_auto_base +#: model:ir.model.fields.selection,name:printing_auto_base.selection__printing_auto__action_on_error__raise +msgid "Raise an Exception" +msgstr "" + +#. module: printing_auto_base +#: model:ir.model.fields.selection,name:printing_auto_base.selection__printing_auto__action_on_error__log +msgid "Record an error" +msgstr "" + +#. module: printing_auto_base +#: model:ir.model.fields,field_description:printing_auto_base.field_printing_auto__record_change +msgid "Record change" +msgstr "" + +#. module: printing_auto_base +#: model:ir.model.fields,field_description:printing_auto_base.field_printing_auto__model +msgid "Related Document Model" +msgstr "" + +#. module: printing_auto_base +#: model:ir.model.fields,field_description:printing_auto_base.field_printing_auto__report_id +#: model:ir.model.fields.selection,name:printing_auto_base.selection__printing_auto__data_source__report +msgid "Report" +msgstr "" + +#. module: printing_auto_base +#. odoo-python +#: code:addons/printing_auto_base/models/printing_auto.py:0 +msgid "Report is not set" +msgstr "" + +#. module: printing_auto_base +#: model:ir.model.fields,help:printing_auto_base.field_printing_auto__record_change +msgid "" +"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'" +msgstr "" + +#. module: printing_auto_base +#: model:ir.model.fields,field_description:printing_auto_base.field_printing_auto__sequence +msgid "Sequence" +msgstr "" + +#. module: printing_auto_base +#. odoo-python +#: code:addons/printing_auto_base/models/printing_auto.py:0 +msgid "The Record change could not be applied because: %s" +msgstr "" + +#. module: printing_auto_base +#: model:ir.model.fields,field_description:printing_auto_base.field_printing_auto__printer_tray_id +msgid "Tray" +msgstr "" + +#. module: printing_auto_base +#. odoo-python +#: code:addons/printing_auto_base/models/printing_auto_mixin.py:0 +msgid "{name}: {count} document(s) sent to printer {printer}" +msgstr "" diff --git a/printing_auto_base/models/__init__.py b/printing_auto_base/models/__init__.py new file mode 100644 index 00000000000..0b24867e8a9 --- /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 00000000000..624c3e80d7c --- /dev/null +++ b/printing_auto_base/models/printing_auto.py @@ -0,0 +1,155 @@ +# 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.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" + _order = "sequence ASC, id ASC" + + name = fields.Char(required=True) + model = fields.Char(string="Related Document Model", required=True) + sequence = fields.Integer(default=10) + + 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( + 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) + action_on_error = fields.Selection( + [("log", "Record an error"), ("raise", "Raise an Exception")], + "Action on error", + default="log", + required=True, + ) + + @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 ValidationError(self.env._("Report is not set")) + if rec.data_source == "attachment" and ( + not rec.attachment_domain or rec.attachment_domain == "[]" + ): + raise ValidationError(self.env._("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 UserError( + self.env._( + "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, records): + generate_data_func = getattr( + self, f"_generate_data_from_{self.data_source}", None + ) + content = [] + if generate_data_func: + records = self._get_record(records) + for record in records: + content += generate_data_func(record) + 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 fields.Domain.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 UserError(self.env._("No attachment was found.")) + return [base64.b64decode(a.datas) for a in attachments] + + def _generate_data_from_report(self, record): + self.ensure_one() + report_ref = self.report_id.get_external_id()[self.report_id.id] + report_model = self.report_id[:0].with_context(must_skip_send_to_printer=True) + [data, __] = report_model._render(report_ref, record.id) + return [data] + + def do_print(self, records): + self.ensure_one() + + behaviour = self._get_behaviour() + printer = behaviour["printer"] + + if self.nbr_of_copies <= 0: + return (printer, 0) + records = self._check_condition(records) + if not records: + return (printer, 0) + + if not printer: + raise UserError( + self.env._("No printer configured to print this %s.", self.name) + ) + + count = 0 + for content in self._get_content(records): + 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 00000000000..6b731f78620 --- /dev/null +++ b/printing_auto_base/models/printing_auto_mixin.py @@ -0,0 +1,76 @@ +# 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", readonly=True, copy=False) + + 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=self.env._( + "%(name)s: %(count)d document(s) sent to printer %(printer)s", + 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 _do_print_auto(self, printing_auto): + printing_auto.ensure_one() + printer, count = printing_auto.do_print(self) + if count: + self._on_printing_auto_done(printing_auto, printer, count) + + def _handle_print_auto(self, printing_auto): + printing_auto.ensure_one() + if printing_auto.action_on_error == "raise": + self._do_print_auto(printing_auto) + return + try: + with self.env.cr.savepoint(): + self._do_print_auto(printing_auto) + 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() + to_print = {} + for record in self: + for printing_auto in record._get_printing_auto(): + if printing_auto not in to_print.keys(): + to_print[printing_auto] = record + else: + to_print[printing_auto] |= record + for printing_auto, records in to_print.items(): + records._handle_print_auto(printing_auto) diff --git a/printing_auto_base/pyproject.toml b/printing_auto_base/pyproject.toml new file mode 100644 index 00000000000..4231d0cccb3 --- /dev/null +++ b/printing_auto_base/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/printing_auto_base/readme/CONTRIBUTORS.md b/printing_auto_base/readme/CONTRIBUTORS.md new file mode 100644 index 00000000000..e98f3cd2b2e --- /dev/null +++ b/printing_auto_base/readme/CONTRIBUTORS.md @@ -0,0 +1,4 @@ +- Jacques-Etienne Baudoux (BCIM) \<\> +- Michael Tietz (MT Software) \<\> +- Camptocamp +- Christopher Hansen \<\> diff --git a/printing_auto_base/readme/DESCRIPTION.md b/printing_auto_base/readme/DESCRIPTION.md new file mode 100644 index 00000000000..456cfcb1bb3 --- /dev/null +++ b/printing_auto_base/readme/DESCRIPTION.md @@ -0,0 +1,5 @@ +Base module to support automatic printing of a report or attachments. + +Check other repo like stock-logistics-reporting module +printingauto_stock_picking for printing documents related to a stock +transfer. diff --git a/printing_auto_base/security/ir.model.access.csv b/printing_auto_base/security/ir.model.access.csv new file mode 100644 index 00000000000..7eced9aa41d --- /dev/null +++ b/printing_auto_base/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_auto_group_user,access_printing_auto group_user,model_printing_auto,base.group_user,1,0,0,0 diff --git a/printing_auto_base/static/description/icon.png b/printing_auto_base/static/description/icon.png new file mode 100644 index 00000000000..3a0328b516c Binary files /dev/null and b/printing_auto_base/static/description/icon.png differ diff --git a/printing_auto_base/static/description/index.html b/printing_auto_base/static/description/index.html new file mode 100644 index 00000000000..68504da8395 --- /dev/null +++ b/printing_auto_base/static/description/index.html @@ -0,0 +1,438 @@ + + + + + +README.rst + + + +
+ + + +Odoo Community Association + +
+

Printing Auto Base

+ +

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

+

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

+

Check other repo like stock-logistics-reporting module +printingauto_stock_picking for printing documents related to a stock +transfer.

+

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 to smash 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.

+

Current maintainer:

+

jbaudoux

+

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 00000000000..998b91457ce --- /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 00000000000..8bf04a3750a --- /dev/null +++ b/printing_auto_base/tests/common.py @@ -0,0 +1,103 @@ +# 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.tests import common + +PRINT_DOCUMENT = ( + "odoo.addons.base_report_to_printer.models.printing_printer." + "PrintingPrinter.print_document" +) + + +def patch_print_document(): + return mock.patch(PRINT_DOCUMENT, mock.MagicMock()) + + +class TestPrintingAutoCommon(common.TransactionCase): + @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_ref = "base.report_ir_model_overview" + cls.record = cls.env.ref("base.model_res_partner") + + @classmethod + def _render_report(cls): + return cls.env["ir.actions.report"]._render(cls.report_ref, cls.record.id)[0] + + @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._render_report() + + @classmethod + def _create_printing_auto(cls, vals): + vals.setdefault("model", "printing.auto") + 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.env.ref(cls.report_ref).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/model_test.py b/printing_auto_base/tests/model_test.py new file mode 100644 index 00000000000..d13b361f947 --- /dev/null +++ b/printing_auto_base/tests/model_test.py @@ -0,0 +1,20 @@ +# Copyright 2023 Camptocamp +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class PrintingAutoTesterChild(models.Model): + _name = "printingauto.tester.child" + _description = _name + + name = fields.Char() + + +class PrintingAutoTester(models.Model): + _name = "printingauto.tester" + _description = _name + _inherit = "printing.auto.mixin" + + name = fields.Char() + child_ids = fields.Many2many("printingauto.tester.child") 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 00000000000..b180350e785 --- /dev/null +++ b/printing_auto_base/tests/test_printing_auto_base.py @@ -0,0 +1,176 @@ +# 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 +from odoo.orm.model_classes import add_to_registry + +from .common import TestPrintingAutoCommon, patch_print_document + + +@patch_print_document() +class TestPrintingAutoBase(TestPrintingAutoCommon): + @classmethod + def setUpClass(cls): + super().setUpClass() + from .model_test import PrintingAutoTester, PrintingAutoTesterChild + + add_to_registry(cls.registry, PrintingAutoTester) + add_to_registry(cls.registry, PrintingAutoTesterChild) + cls.registry._setup_models__( + cls.env.cr, ["printingauto.tester", "printingauto.tester.child"] + ) + cls.registry.init_models( + cls.env.cr, + ["printingauto.tester", "printingauto.tester.child"], + {"models_to_check": True}, + ) + + @classmethod + def tearDownClass(cls): + cls.addClassCleanup(cls.registry.__delitem__, "printingauto.tester") + cls.addClassCleanup(cls.registry.__delitem__, "printingauto.tester.child") + return super().tearDownClass() + + 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(UserError): + 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]: + self.printer_1.print_document.reset_mock() + expected = (self.printer_1, nbr_of_copies) + printing_auto.nbr_of_copies = nbr_of_copies + self.assertEqual(expected, printing_auto.do_print(self.record)) + # Check mock usage + kwargs = {"report": None, "content": self.data, "printer": self.printer_1} + expected_calls = [("__call__", (), kwargs)] * nbr_of_copies + self.assertEqual(self.printer_1.print_document.mock_calls, expected_calls) + + printing_auto.condition = "[('name', '=', 'test_printing_auto')]" + expected = (self.printer_1, 0) + self.printer_1.print_document.reset_mock() + self.assertEqual(expected, printing_auto.do_print(self.record)) + self.printer_1.print_document.assert_not_called() + + def test_do_not_print_multiple_time_the_same_record(self): + """Check the same record is not printed multiple times. + + When the 'record_change' field is being used on the printing auto configuration + and 'handle_print_auto' is called from a recrodset. + The same record could be send for printing multiple times. + + """ + printing_auto = self._create_printing_auto_report( + vals={"record_change": "child_ids", "printer_id": self.printer_1.id} + ) + child1 = self.env["printingauto.tester.child"].create({"name": "Child One"}) + child2 = self.env["printingauto.tester.child"].create({"name": "Child Two"}) + parent1 = self.env["printingauto.tester"].create( + { + "name": "Customer One", + "child_ids": [(4, child1.id, 0)], + "auto_printing_ids": [(4, printing_auto.id, 0)], + } + ) + parent2 = self.env["printingauto.tester"].create( + { + "name": "Customer Two", + "child_ids": [(4, child1.id, 0)], + "auto_printing_ids": [(4, printing_auto.id, 0)], + } + ) + parents = parent1 | parent2 + generate_data_from = ( + "odoo.addons.printing_auto_base.models.printing_auto." + "PrintingAuto._generate_data_from_report" + ) + with mock.patch(generate_data_from) as generate_data_from: + # Both parents have the same child only print the child report once + parents.handle_print_auto() + self.assertEqual(generate_data_from.call_count, 1) + generate_data_from.assert_called_with(child1) + generate_data_from.reset_mock() + # Both parents have different childs, print both child reports + parent2.child_ids = [(6, 0, child2.ids)] + parents.handle_print_auto() + self.assertEqual(generate_data_from.call_count, 2) + generate_data_from.assert_has_calls( + [mock.call(child1), mock.call(child2)], any_order=True + ) + generate_data_from.reset_mock() + # THe parents have one child in common and one parent has a 2nd child + parent2.child_ids = [(4, child1.id, 0)] + parents.handle_print_auto() + self.assertEqual(generate_data_from.call_count, 2) + generate_data_from.assert_has_calls( + [mock.call(child1), mock.call(child2)], any_order=True + ) diff --git a/printing_auto_base/views/printing_auto.xml b/printing_auto_base/views/printing_auto.xml new file mode 100644 index 00000000000..fc625c1a5ed --- /dev/null +++ b/printing_auto_base/views/printing_auto.xml @@ -0,0 +1,53 @@ + + + printing.auto.view.form + printing.auto + +
+ + + + + + + + + + + + + + +
+
+
+ + + printing.auto.view.tree + printing.auto + + + + + + + + + + + + + + + + +