diff --git a/report_label/__init__.py b/report_label/__init__.py
new file mode 100644
index 000000000..aee8895e7
--- /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 000000000..e1e7cf481
--- /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 000000000..e6e8ec814
--- /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 000000000..39a7794cc
--- /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 000000000..2536ea822
--- /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 000000000..7394bf98c
--- /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 000000000..45962d2a0
--- /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 000000000..b77b2c088
--- /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 000000000..2d61181f4
--- /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 000000000..678fbcb70
--- /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 000000000..0beb29c68
--- /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 000000000..899438053
--- /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 000000000..f6aa7893a
--- /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 000000000..254fa6e8a
--- /dev/null
+++ b/report_label/reports/report_label.xml
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+ height: mm;
+ width: mm;
+ padding-top: mm;
+ padding-right: mm;
+ padding-bottom: mm;
+ padding-left: mm;
+ margin-top: mm;
+ margin-right: mm;
+ margin-bottom: mm;
+ margin-left: mm;
+ display: inline-block;
+ overflow: hidden;
+ float: left;
+ position: relative;
+ page-break-inside: avoid;
+ box-sizing: border-box;
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 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 000000000..08ec46c19
--- /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 000000000..cf91c5f64
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 000000000..0d52c6717
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 000000000..201988132
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 000000000..255f7911a
--- /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 000000000..4720af16f
--- /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 000000000..9bcdd6d6e
--- /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 000000000..3a20ba692
--- /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 000000000..c7b605c91
--- /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 000000000..ec61c69d1
--- /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 000000000..284ffbfdc
--- /dev/null
+++ b/report_label/wizards/report_label_wizard.xml
@@ -0,0 +1,36 @@
+
+
+
+
+ report.label.wizard
+
+
+
+
+
+