diff --git a/setup/stock_picking_auto_print/odoo/addons/stock_picking_auto_print b/setup/stock_picking_auto_print/odoo/addons/stock_picking_auto_print new file mode 120000 index 00000000000..4b9e771f46e --- /dev/null +++ b/setup/stock_picking_auto_print/odoo/addons/stock_picking_auto_print @@ -0,0 +1 @@ +../../../../stock_picking_auto_print \ No newline at end of file diff --git a/setup/stock_picking_auto_print/setup.py b/setup/stock_picking_auto_print/setup.py new file mode 100644 index 00000000000..28c57bb6403 --- /dev/null +++ b/setup/stock_picking_auto_print/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/stock_picking_auto_print/README.rst b/stock_picking_auto_print/README.rst new file mode 100644 index 00000000000..fefb5f83b7c --- /dev/null +++ b/stock_picking_auto_print/README.rst @@ -0,0 +1,80 @@ +======================== +stock_picking_auto_print +======================== + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! 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/stock_picking_auto_print + :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-stock_picking_auto_print + :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| + +When a picking is done, automatically trigger the printing of some documents. +This can be used to print a delivery slip (report) or labels received from the carrier (attachment). + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +Go to the Operation Type and configure which report or attachment to print. + +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 + +Contributors +~~~~~~~~~~~~ + +* Jacques-Etienne Baudoux (BCIM) +* 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/stock_picking_auto_print/__init__.py b/stock_picking_auto_print/__init__.py new file mode 100644 index 00000000000..0650744f6bc --- /dev/null +++ b/stock_picking_auto_print/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/stock_picking_auto_print/__manifest__.py b/stock_picking_auto_print/__manifest__.py new file mode 100644 index 00000000000..367e4d2507b --- /dev/null +++ b/stock_picking_auto_print/__manifest__.py @@ -0,0 +1,23 @@ +# Copyright 2022 Jacques-Etienne Baudoux (BCIM) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "application": False, + "author": "BCIM, Odoo Community Association (OCA)", + "category": "Warehouse Management", + "data": [ + "security/ir.model.access.csv", + "views/stock_auto_printing_views.xml", + "views/stock_picking_type_views.xml", + ], + "demo_xml": [], + "depends": [ + "stock", + "base_report_to_printer", + ], + "installable": True, + "license": "AGPL-3", + "name": "stock_picking_auto_print", + "version": "14.0.1.0.0", + "website": "https://github.com/OCA/report-print-send", +} diff --git a/stock_picking_auto_print/models/__init__.py b/stock_picking_auto_print/models/__init__.py new file mode 100644 index 00000000000..dcfe69590d5 --- /dev/null +++ b/stock_picking_auto_print/models/__init__.py @@ -0,0 +1,3 @@ +from . import stock_auto_printing +from . import stock_picking +from . import stock_picking_type diff --git a/stock_picking_auto_print/models/stock_auto_printing.py b/stock_picking_auto_print/models/stock_auto_printing.py new file mode 100644 index 00000000000..455c74e6867 --- /dev/null +++ b/stock_picking_auto_print/models/stock_auto_printing.py @@ -0,0 +1,135 @@ +# Copyright 2022 Jacques-Etienne Baudoux (BCIM) +# 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, Warning +from odoo.osv import expression +from odoo.tools.safe_eval import safe_eval + + +class StockAutoPrinting(models.Model): + _name = "stock.auto.printing" + _description = "Stock Auto Printing" + + name = fields.Char(string="Name", required=True) + file_type = fields.Selection( + [ + ("report", "Report"), + ("attachment", "Attachment"), + ], + string="Type", + 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)" + ), + ) + field_object = fields.Char( + "Object", help="Select on which document the report must be executed" + ) + + report_id = fields.Many2one("ir.actions.report") + + attachment = fields.Char("Attachment", default="[]") + condition = fields.Char( + "Condition", + default="[]", + help="Give a domain that must be valid for printing this", + ) + + printer_id = fields.Many2one("printing.printer", string="Printer") + printer_tray_id = fields.Many2one("printing.tray") + nbr_of_copies = fields.Integer("Number of Copies", default=1) + + picking_type_id = fields.Many2one("stock.picking.type", required=True) + label = fields.Boolean(string="Is Label") + + def do_automated_printing(self, pickings): + for rec in self: + for pick in pickings: + rec._do_automated_printing(pick) + + def _do_automated_printing(self, picking): + self.ensure_one() + picking.ensure_one() + if not self.check_condition(picking): + return False + + printer, behaviour = self.get_printer_and_behaviour() + + record = self._get_record(picking) + for content in self.get_content(record): + for n in range(self.nbr_of_copies): + self._send_file_to_printer(content, behaviour, printer) + + def _send_file_to_printer(self, content, behaviour, printer): + return printer.print_document( + report=None, content=content, doc_formart="qweb-pdf", **behaviour + ) + + def _get_record(self, record): + if self.field_object: + try: + return safe_eval(f"obj.{self.field_object}", {"obj": record}) + except Exception as e: + raise ValidationError( + _("The Object field could not be applied because: %s") % str(e) + ) from e + return record + + def check_condition(self, picking): + domain = safe_eval(self.condition, {"env": self.env}) + return picking.filtered_domain(domain) + + def get_content(self, record): + if self.file_type == "report": + return [self.generate_data_from_report(record)] + attachments = self.get_attachment(record) + return [base64.b64decode(a.datas) for a in attachments] + + def get_behaviour(self): + if self.printer_id: + tray = self.printer_tray_id and self.printer_tray_id.system_name + return {"printer": self.printer_id, "tray": tray} + elif self.file_type == "report": + return self.report_id.behaviour() + elif self.label: + return self.env.user.default_label_printer_id + else: + return self.env["ir.actions.report"]._get_user_default_print_behaviour() + + def get_printer_and_behaviour(self): + behaviour = self.get_behaviour() + printer = behaviour.pop("printer", None) + if not printer: + raise Warning(_("No printer configured to print this file.")) + return printer, behaviour + + def get_attachment(self, record): + user_domain = safe_eval(self.attachment) + attachment_domain = [ + ("res_id", "=", record.id), + ("res_model", "=", record._name), + ] + domain = expression.AND([attachment_domain, user_domain]) + attachments = self.env["ir.attachment"].search(domain) + if not attachments: + raise ValidationError(_("No attachment was found.")) + return attachments + + def generate_data_from_report(self, record): + self.ensure_one() + data, _ = self.report_id.with_context( + must_skip_send_to_printer=True + )._render_qweb_pdf(record.id) + return data + + @api.constrains("report_id", "file_type") + def check_report(self): + for rec in self: + if rec.file_type == "report" and not rec.report_id: + raise UserError(_("Report was not set")) diff --git a/stock_picking_auto_print/models/stock_picking.py b/stock_picking_auto_print/models/stock_picking.py new file mode 100644 index 00000000000..1bdc9f9c295 --- /dev/null +++ b/stock_picking_auto_print/models/stock_picking.py @@ -0,0 +1,16 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import models + + +class Picking(models.Model): + _inherit = "stock.picking" + + def _action_done(self): + result = super()._action_done() + self.send_files_to_printer() + return result + + def send_files_to_printer(self): + for rec in self: + rec.picking_type_id.do_automated_printing(rec) diff --git a/stock_picking_auto_print/models/stock_picking_type.py b/stock_picking_auto_print/models/stock_picking_type.py new file mode 100644 index 00000000000..2d9e789d501 --- /dev/null +++ b/stock_picking_auto_print/models/stock_picking_type.py @@ -0,0 +1,17 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class Picking(models.Model): + _inherit = "stock.picking.type" + + auto_printing_ids = fields.One2many( + "stock.auto.printing", "picking_type_id", "Auto Printing Configuration" + ) + + def do_automated_printing(self, pickings): + for rec in self: + if not rec.auto_printing_ids: + continue + rec.auto_printing_ids.do_automated_printing(pickings) diff --git a/stock_picking_auto_print/readme/CONFIGURE.rst b/stock_picking_auto_print/readme/CONFIGURE.rst new file mode 100644 index 00000000000..70d33136cea --- /dev/null +++ b/stock_picking_auto_print/readme/CONFIGURE.rst @@ -0,0 +1 @@ +Go to the Operation Type and configure which report or attachment to print. diff --git a/stock_picking_auto_print/readme/CONTRIBUTORS.rst b/stock_picking_auto_print/readme/CONTRIBUTORS.rst new file mode 100644 index 00000000000..72acc8c0c7a --- /dev/null +++ b/stock_picking_auto_print/readme/CONTRIBUTORS.rst @@ -0,0 +1,2 @@ +* Jacques-Etienne Baudoux (BCIM) +* Christopher Hansen diff --git a/stock_picking_auto_print/readme/DESCRIPTION.rst b/stock_picking_auto_print/readme/DESCRIPTION.rst new file mode 100644 index 00000000000..8190ccf510e --- /dev/null +++ b/stock_picking_auto_print/readme/DESCRIPTION.rst @@ -0,0 +1,2 @@ +When a picking is done, automatically trigger the printing of some documents. +This can be used to print a delivery slip (report) or labels received from the carrier (attachment). diff --git a/stock_picking_auto_print/security/ir.model.access.csv b/stock_picking_auto_print/security/ir.model.access.csv new file mode 100644 index 00000000000..d6010d509d7 --- /dev/null +++ b/stock_picking_auto_print/security/ir.model.access.csv @@ -0,0 +1,3 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_manager,stock_picking_auto_print - manager,model_stock_auto_printing,stock.group_stock_manager,1,1,1,1 +access_user,stock_picking_auto_print - user,model_stock_auto_printing,stock.group_stock_user,1,0,0,0 diff --git a/stock_picking_auto_print/static/description/index.html b/stock_picking_auto_print/static/description/index.html new file mode 100644 index 00000000000..c335eecb3a9 --- /dev/null +++ b/stock_picking_auto_print/static/description/index.html @@ -0,0 +1,426 @@ + + + + + + +stock_picking_auto_print + + + +
+

stock_picking_auto_print

+ + +

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

+

When a picking is done, automatically trigger the printing of some documents. +This can be used to print a delivery slip (report) or labels received from the carrier (attachment).

+

Table of contents

+ +
+

Configuration

+

Go to the Operation Type and configure which report or attachment to print.

+
+
+

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
  • +
+
+
+

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/stock_picking_auto_print/tests/__init__.py b/stock_picking_auto_print/tests/__init__.py new file mode 100644 index 00000000000..bdeaada4936 --- /dev/null +++ b/stock_picking_auto_print/tests/__init__.py @@ -0,0 +1 @@ +from . import test_stock_auto_printing diff --git a/stock_picking_auto_print/tests/test_stock_auto_printing.py b/stock_picking_auto_print/tests/test_stock_auto_printing.py new file mode 100644 index 00000000000..f493ed881fa --- /dev/null +++ b/stock_picking_auto_print/tests/test_stock_auto_printing.py @@ -0,0 +1,177 @@ +import logging + +from odoo.exceptions import UserError +from odoo.tests import common + +logger = logging.getLogger(__name__) + + +class TestAutoPrinting(common.SavepointCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.server = cls.env["printing.server"].create({}) + cls.stock_picking = cls.env.ref("stock.outgoing_shipment_main_warehouse") + cls.printer_1 = cls.env["printing.printer"].create( + { + "name": "Printer", + "server_id": cls.server.id, + "system_name": "Sys Name", + "default": True, + "status": "unknown", + "status_message": "Msg", + "model": "res.users", + "location": "Location", + "uri": "URI", + } + ) + cls.report = cls.env.ref("stock.action_report_delivery") + cls.auto_printing = cls.env["stock.auto.printing"].create( + { + "name": "Auto Printing 1", + "picking_type_id": cls.stock_picking.picking_type_id.id, + "report_id": cls.report.id, + "printer_id": cls.printer_1.id, + } + ) + cls.auto_printing_2 = cls.env["stock.auto.printing"].create( + { + "name": "Auto Printing 2", + "picking_type_id": cls.stock_picking.picking_type_id.id, + "printer_id": cls.printer_1.id, + "file_type": "attachment", + } + ) + cls.report_datas, a = cls.report.with_context( + must_skip_send_to_printer=True, + )._render_qweb_pdf(cls.stock_picking.id) + cls.attachment_1 = cls.env["ir.attachment"].create( + { + "name": "attachment_1.pdf", + "res_model": "stock.picking", + "res_id": cls.stock_picking.id, + "raw": cls.report_datas, + } + ) + cls.tray = cls.env["printing.tray"].create( + { + "name": "Tray", + "system_name": "TrayName", + "printer_id": cls.printer_1.id, + } + ) + + def test_00_test_general(self): + with self.assertRaises(UserError): + self.stock_picking.send_files_to_printer() + + def test_10_test_print_report(self): + with self.assertRaises(UserError): + self.auto_printing.do_automated_printing(self.stock_picking) + + def test_20_test_print_attachment(self): + with self.assertRaises(UserError): + self.auto_printing_2.do_automated_printing(self.stock_picking) + + def test_30_test_condition_filter(self): + self.auto_printing.condition = [("id", "=", self.stock_picking.id)] + auto_printing = self.auto_printing.check_condition(self.stock_picking) + self.assertEqual(auto_printing, self.stock_picking) + + self.auto_printing.condition = [("id", "=", 0)] + auto_printing = self.auto_printing.check_condition(self.stock_picking) + self.assertEqual(len(auto_printing), 0) + + def test_40_test_attachment_filter(self): + attachment_2 = self.env["ir.attachment"].create( + { + "name": "attachment_2.pdf", + "res_model": "stock.picking", + "res_id": self.stock_picking.id, + "datas": self.report_datas, + } + ) + self.auto_printing.attachment = [("id", "=", attachment_2.id)] + test_attachment = self.auto_printing.get_attachment(self.stock_picking) + self.assertEqual(attachment_2, test_attachment) + + def test_50_test_field_object(self): + self.auto_printing.field_object = "picking_type_id" + field_object = self.auto_printing._get_record(self.stock_picking) + self.assertEqual(field_object, self.stock_picking.picking_type_id) + + def test_50_test_printer_and_behavoir(self): + printer, behaviour = self.auto_printing.get_printer_and_behaviour() + self.assertEqual(printer, self.auto_printing.printer_id) + + self.auto_printing.printer_id = None + printer, behaviour = self.auto_printing.get_printer_and_behaviour() + behaviour_test = self.report.behaviour() + printer_test = behaviour_test.pop("printer") + self.assertEqual(printer, printer_test) + self.assertEqual(behaviour, behaviour_test) + + printer, behaviour = self.auto_printing_2.get_printer_and_behaviour() + tray = behaviour.pop("tray") + self.assertEqual(printer, self.auto_printing_2.printer_id) + self.assertFalse(tray) + + self.auto_printing_2.printer_tray_id = self.tray + printer, behaviour = self.auto_printing_2.get_printer_and_behaviour() + tray = behaviour.pop("tray") + self.assertEqual(printer, self.auto_printing_2.printer_id) + self.assertEqual(tray, self.tray.system_name) + + self.auto_printing_2.printer_tray_id = None + self.auto_printing_2.printer_id = None + behaviour_test = self.env[ + "ir.actions.report" + ]._get_user_default_print_behaviour() + printer_test = behaviour_test.pop("printer") + printer, behaviour = self.auto_printing_2.get_printer_and_behaviour() + self.assertEqual(printer, printer_test) + self.assertEqual(behaviour, behaviour_test) + + def test_60_output(self): + # Report + for content in self.auto_printing.get_content(self.stock_picking): + self.assertTrue(self.check_pdf(content)) + + # -- Comment out to test it visually. -- + # data_dir = os.path.join(os.path.realpath(os.path.dirname(__file__))) + # path = os.path.join(data_dir, attachment_name) + + # f = open(path, 'wb') + # f.write(content) + # f.close() + + # Attachments + attachment_prefix = "ir_attachment" + attachment_names = [] + attachment_len = 2 + for i in range(attachment_len): + attachment_name = "{name}_{i}".format(i=i, name=attachment_prefix) + attachment_names.append(attachment_name) + self.env["ir.attachment"].create( + { + "name": attachment_name, + "res_id": self.stock_picking.id, + "res_model": self.stock_picking._name, + "raw": content, + } + ) + self.auto_printing_2.attachment = [("name", "in", attachment_names)] + contents = self.auto_printing_2.get_content(self.stock_picking) + self.assertEqual(len(contents), attachment_len) + for content in contents: + self.assertTrue(self.check_pdf(content)) + + # -- Comment out to test it visually. -- + # for content, i in enumerate(contents): + # path = os.path.join(data_dir, attachment_names[i]) + # f = open(path, 'wb') + # f.write(content) + # f.close() + + def check_pdf(self, content): + return "WH/OUT/00001" in str(content) diff --git a/stock_picking_auto_print/views/stock_auto_printing_views.xml b/stock_picking_auto_print/views/stock_auto_printing_views.xml new file mode 100644 index 00000000000..85662a02add --- /dev/null +++ b/stock_picking_auto_print/views/stock_auto_printing_views.xml @@ -0,0 +1,55 @@ + + + stock.auto.printing.view.form + stock.auto.printing + +
+ + + + + + + + + + + + + + +
+
+
+ + + stock.auto.printing.view.tree + stock.auto.printing + + + + + + + + + + + + + + + +
diff --git a/stock_picking_auto_print/views/stock_picking_type_views.xml b/stock_picking_auto_print/views/stock_picking_type_views.xml new file mode 100644 index 00000000000..63f4935afdb --- /dev/null +++ b/stock_picking_auto_print/views/stock_picking_type_views.xml @@ -0,0 +1,13 @@ + + + stock.picking.type.inherit.view.form + stock.picking.type + + + + + + + + +