diff --git a/report_label/__init__.py b/report_label/__init__.py new file mode 100644 index 00000000..aee8895e --- /dev/null +++ b/report_label/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import wizards diff --git a/report_label/__manifest__.py b/report_label/__manifest__.py new file mode 100644 index 00000000..e1e7cf48 --- /dev/null +++ b/report_label/__manifest__.py @@ -0,0 +1,26 @@ +{ + 'name': 'Report Labels', + 'version': '12.0.1.0.0', + 'summary': 'Print configurable self-adhesive labels reports', + 'author': 'Iván Todorovich, Moka Tourisme, Odoo Community Association (OCA)', + 'website': 'https://github.com/OCA/reporting-engine', + 'license': 'AGPL-3', + 'category': 'Reporting', + 'maintainers': [ + 'ivantodorovich' + ], + 'depends': [ + 'base', + ], + 'data': [ + 'security/ir.model.access.csv', + 'data/paperformat_label.xml', + 'views/ir_actions_server.xml', + 'views/report_paperformat_label.xml', + 'reports/report_label.xml', + 'wizards/report_label_wizard.xml', + ], + 'demo': [ + 'demo/demo.xml', + ] +} diff --git a/report_label/data/paperformat_label.xml b/report_label/data/paperformat_label.xml new file mode 100644 index 00000000..e6e8ec81 --- /dev/null +++ b/report_label/data/paperformat_label.xml @@ -0,0 +1,27 @@ + + + + + + Label: Agipa 114016 + A5 + Portrait + + + + + + + + + + + + + + + + + + + diff --git a/report_label/demo/demo.xml b/report_label/demo/demo.xml new file mode 100644 index 00000000..39a7794c --- /dev/null +++ b/report_label/demo/demo.xml @@ -0,0 +1,33 @@ + + + + + + + Partner Label + A4 + + + + + + + + + + Print Address Labels + report_label + + + report_label.label_template_partner_address + + + + + + diff --git a/report_label/models/__init__.py b/report_label/models/__init__.py new file mode 100644 index 00000000..2536ea82 --- /dev/null +++ b/report_label/models/__init__.py @@ -0,0 +1,3 @@ +from . import report_paperformat_label +from . import ir_actions_server +from . import ir_actions_report diff --git a/report_label/models/ir_actions_report.py b/report_label/models/ir_actions_report.py new file mode 100644 index 00000000..7394bf98 --- /dev/null +++ b/report_label/models/ir_actions_report.py @@ -0,0 +1,14 @@ +from odoo import api, models + + +class IrActionsReport(models.Model): + _inherit = "ir.actions.report" + + @api.model + def get_paperformat(self): + # Allow to define paperformat via context + res = super().get_paperformat() + if self.env.context.get("paperformat_id"): + res = self.env["report.paperformat"].browse( + self.env.context.get("paperformat_id")) + return res diff --git a/report_label/models/ir_actions_server.py b/report_label/models/ir_actions_server.py new file mode 100644 index 00000000..45962d2a --- /dev/null +++ b/report_label/models/ir_actions_server.py @@ -0,0 +1,57 @@ +from odoo import api, models, fields + + +class IrActionsServer(models.Model): + _inherit = "ir.actions.server" + + state = fields.Selection( + selection_add=[("report_label", "Print self-adhesive labels")] + ) + label_template = fields.Char( + "Label QWeb Template", + help="The QWeb template key to render the labels", + states={ + "report_label": [("required", True)] + } + ) + label_paperformat_id = fields.Many2one( + "report.paperformat.label", + "Label Paper Format", + states={ + "report_label": [("required", True)] + } + ) + + @api.multi + def report_label_associated_view(self): + """ View the associated qweb templates """ + self.ensure_one() + action = self.env.ref('base.action_ui_view', raise_if_not_found=False) + if not action or len(self.label_template.split('.')) < 2: + return False + res = action.read()[0] + res['domain'] = [ + ('type', '=', 'qweb'), + '|', + ('name', 'ilike', self.label_template.split('.')[1]), + ('key', '=', self.label_template), + ] + return res + + @api.model + def run_action_report_label_multi(self, action, eval_context=None): + """ Show report label wizard """ + context = dict(self.env.context) + context.update({ + "label_template": action.label_template, + "label_paperformat_id": action.label_paperformat_id.id, + "res_model_id": action.model_id.id, + }) + return { + "name": action.name, + "type": "ir.actions.act_window", + "res_model": "report.label.wizard", + "context": str(context), + "view_mode": "form", + "target": "new", + } diff --git a/report_label/models/report_paperformat_label.py b/report_label/models/report_paperformat_label.py new file mode 100644 index 00000000..b77b2c08 --- /dev/null +++ b/report_label/models/report_paperformat_label.py @@ -0,0 +1,38 @@ +from odoo import models, fields + + +class ReportPaperformatLabel(models.Model): + _name = "report.paperformat.label" + _inherits = {"report.paperformat": "paperformat_id"} + _description = "Label Paper Format" + + paperformat_id = fields.Many2one( + "report.paperformat", + string="Paper Format", + required=True, + ondelete="cascade", + ) + label_width = fields.Float( + "Label Width (mm)", + default=60, + required=True, + ) + label_height = fields.Float( + "Label Height (mm)", + default=42.3, + required=True, + ) + label_padding_top = fields.Float("Label Padding Top (mm)", default=2) + label_padding_right = fields.Float("Label Padding Right (mm)", default=2) + label_padding_bottom = fields.Float("Label Padding Bottom (mm)", default=2) + label_padding_left = fields.Float("Label Padding Left (mm)", default=2) + label_margin_top = fields.Float("Label Margin Top (mm)", default=2) + label_margin_right = fields.Float("Label Margin Right (mm)", default=2) + label_margin_bottom = fields.Float("Label Margin Bottom (mm)", default=2) + label_margin_left = fields.Float("Label Margin Left (mm)", default=2) + + # Overload inherits defaults + orientation = fields.Selection(inherited=True, default="Portrait") + header_spacing = fields.Integer(inherited=True, default=0) + margin_top = fields.Float(inherited=True, default=7) + margin_bottom = fields.Float(inherited=True, default=7) diff --git a/report_label/readme/CONFIGURE.rst b/report_label/readme/CONFIGURE.rst new file mode 100644 index 00000000..2d61181f --- /dev/null +++ b/report_label/readme/CONFIGURE.rst @@ -0,0 +1,10 @@ +Go to **Settings > Technical > Analysis > Label Paper Format** and create +your self-adhesive label paper formats. + +.. image:: ../static/description/configure_paperformat.png + +Go to **Settings > Technical > Analysis > Label Report** and create your label +report, and its context action. You'll also need to create or reuse a +QWeb template for you label. + +.. image:: ../static/description/configure_report_label.png diff --git a/report_label/readme/CONTRIBUTORS.rst b/report_label/readme/CONTRIBUTORS.rst new file mode 100644 index 00000000..678fbcb7 --- /dev/null +++ b/report_label/readme/CONTRIBUTORS.rst @@ -0,0 +1,6 @@ +* Iván Todorovich + +* `Moka Tourisme `_: + * Grégory Schreiner + +* Sylvain LE GAL diff --git a/report_label/readme/DESCRIPTION.rst b/report_label/readme/DESCRIPTION.rst new file mode 100644 index 00000000..0beb29c6 --- /dev/null +++ b/report_label/readme/DESCRIPTION.rst @@ -0,0 +1 @@ +This module allows you to create self-adhesive label printing actions on any model. \ No newline at end of file diff --git a/report_label/readme/ROADMAP.rst b/report_label/readme/ROADMAP.rst new file mode 100644 index 00000000..89943805 --- /dev/null +++ b/report_label/readme/ROADMAP.rst @@ -0,0 +1,3 @@ +* `wkhtmltopdf` doesn't always respect dpi, and mm measures don't match. For + this matter, it's recommended to use this module along with + `report_wkhtmltopdf_param` and enable `--disable-smart-shrinking`. diff --git a/report_label/readme/USAGE.rst b/report_label/readme/USAGE.rst new file mode 100644 index 00000000..f6aa7893 --- /dev/null +++ b/report_label/readme/USAGE.rst @@ -0,0 +1,5 @@ +1. In the target model's tree view, select the records to print. +2. Click *Action* and your label report action name. +3. Select the number of labels per record to print, and click Print. + +.. image:: ../static/description/label_wizard.png \ No newline at end of file diff --git a/report_label/reports/report_label.xml b/report_label/reports/report_label.xml new file mode 100644 index 00000000..254fa6e8 --- /dev/null +++ b/report_label/reports/report_label.xml @@ -0,0 +1,49 @@ + + + + + + + Label Report + report.label.wizard + qweb-pdf + report_label.report_label_template + report_label.report_label_template + + + diff --git a/report_label/security/ir.model.access.csv b/report_label/security/ir.model.access.csv new file mode 100644 index 00000000..08ec46c1 --- /dev/null +++ b/report_label/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_report_paperformat_label_all,report.paperformat.label all,model_report_paperformat_label,,1,,, +access_report_label_layout_admin,report.paperformat.label admin,model_report_paperformat_label,base.group_system,1,1,1,1 diff --git a/report_label/static/description/configure_paperformat.png b/report_label/static/description/configure_paperformat.png new file mode 100644 index 00000000..cf91c5f6 Binary files /dev/null and b/report_label/static/description/configure_paperformat.png differ diff --git a/report_label/static/description/configure_report_label.png b/report_label/static/description/configure_report_label.png new file mode 100644 index 00000000..0d52c671 Binary files /dev/null and b/report_label/static/description/configure_report_label.png differ diff --git a/report_label/static/description/label_wizard.png b/report_label/static/description/label_wizard.png new file mode 100644 index 00000000..20198813 Binary files /dev/null and b/report_label/static/description/label_wizard.png differ diff --git a/report_label/tests/__init__.py b/report_label/tests/__init__.py new file mode 100644 index 00000000..255f7911 --- /dev/null +++ b/report_label/tests/__init__.py @@ -0,0 +1 @@ +from . import test_report_label diff --git a/report_label/tests/test_report_label.py b/report_label/tests/test_report_label.py new file mode 100644 index 00000000..4720af16 --- /dev/null +++ b/report_label/tests/test_report_label.py @@ -0,0 +1,21 @@ +from odoo.tests import common +from ast import literal_eval + + +class TestReportLabel(common.TransactionCase): + + def setUp(self): + super().setUp() + self.partner_label = self.env.ref( + "report_label.actions_server_label_partner_address") + + def test_01_print_partner_label(self): + self.partner_label.create_action() + action = self.partner_label.run() + model = action["res_model"] + context = literal_eval(action["context"]) + context["active_model"] = "res.partner" + context["active_ids"] = self.env["res.partner"].search([]).ids + wizard = self.env[model].with_context(context).create({}) + report_action = wizard.print_report() + self.assertEquals(report_action["type"], "ir.actions.report") diff --git a/report_label/views/ir_actions_server.xml b/report_label/views/ir_actions_server.xml new file mode 100644 index 00000000..9bcdd6d6 --- /dev/null +++ b/report_label/views/ir_actions_server.xml @@ -0,0 +1,40 @@ + + + + + ir.actions.server + + +
+
+ + + + + +
+ + + Label Reports + ir.actions.server + form + tree,form + [("state", "=", "report_label")] + {"default_state": "report_label"} + + + + +
diff --git a/report_label/views/report_paperformat_label.xml b/report_label/views/report_paperformat_label.xml new file mode 100644 index 00000000..3a20ba69 --- /dev/null +++ b/report_label/views/report_paperformat_label.xml @@ -0,0 +1,70 @@ + + + + + report.paperformat.label + + primary + + + + 1 + + + 1 + + + 1 + + +
+ + + + + + + + + + + + + + + + + + +
+
+
+ + + report.paperformat.label + + + + + + + + + + + Label paper format configuration + report.paperformat.label + form + tree,form + [] + {} + + + + +
diff --git a/report_label/wizards/__init__.py b/report_label/wizards/__init__.py new file mode 100644 index 00000000..c7b605c9 --- /dev/null +++ b/report_label/wizards/__init__.py @@ -0,0 +1 @@ +from . import report_label_wizard diff --git a/report_label/wizards/report_label_wizard.py b/report_label/wizards/report_label_wizard.py new file mode 100644 index 00000000..ec61c69d --- /dev/null +++ b/report_label/wizards/report_label_wizard.py @@ -0,0 +1,103 @@ +from odoo import api, models, fields + + +class ReportLabelWizard(models.TransientModel): + _name = "report.label.wizard" + _description = "Report Label Wizard" + + @api.model + def _default_line_ids(self): + """ Compute line_ids based on context """ + active_model = self.env.context.get("active_model") + active_ids = self.env.context.get("active_ids", []) + if not active_model or not active_ids: + return False + return [ + (0, 0, { + "res_id": res_id, + "quantity": 1, + }) + for res_id in active_ids + ] + + model_id = fields.Many2one( + "ir.model", + "Model", + required=True, + default=lambda self: self.env.context.get("res_model_id"), + ) + label_paperformat_id = fields.Many2one( + "report.paperformat.label", + "Label Paper Format", + readonly=True, + required=True, + default=lambda self: self.env.context.get("label_paperformat_id"), + ) + label_template = fields.Char( + "Label QWeb Template", + readonly=True, + required=True, + default=lambda self: self.env.context.get("label_template"), + ) + offset = fields.Integer( + help="Number of labels to skip when printing", + ) + line_ids = fields.One2many( + "report.label.wizard.line", + "wizard_id", + "Lines", + default=_default_line_ids, + required=True, + ) + + def _prepare_report_data(self): + self.ensure_one() + return { + "label_format": self.label_paperformat_id.read()[0], + "label_template": self.label_template, + "offset": self.offset, + "res_model": self.model_id.model, + "lines": [ + { + "res_id": line.res_id, + "quantity": line.quantity, + } + for line in self.line_ids + ], + } + + def print_report(self): + self.ensure_one() + report = self.env.ref("report_label.report_label") + action = report.report_action(self, data=self._prepare_report_data()) + action["context"] = { + "paperformat_id": self.label_paperformat_id.paperformat_id.id, + } + return action + + +class ReportLabelWizardLine(models.TransientModel): + _name = "report.label.wizard.line" + _description = "Report Label Wizard Line" + _order = "sequence" + + wizard_id = fields.Many2one( + "report.label.wizard", + "Wizard", + required=True, + ondelete="cascade", + ) + sequence = fields.Integer(default=10) + res_id = fields.Integer("Resource ID", required=True) + res_name = fields.Char(compute="_compute_res_name") + quantity = fields.Integer(default=1, required=True) + + @api.depends("wizard_id.model_id", "res_id") + def _compute_res_name(self): + wizard = self.mapped("wizard_id") + wizard.ensure_one() + res_model = wizard.model_id.model + res_ids = self.mapped("res_id") + names_map = dict(self.env[res_model].browse(res_ids).name_get()) + for rec in self: + rec.res_name = names_map.get(rec.res_id) diff --git a/report_label/wizards/report_label_wizard.xml b/report_label/wizards/report_label_wizard.xml new file mode 100644 index 00000000..284ffbfd --- /dev/null +++ b/report_label/wizards/report_label_wizard.xml @@ -0,0 +1,36 @@ + + + + + report.label.wizard + +
+ + + + + + + + + + + + + + + + + + + + + +
+
+
+ +