From e376202f4e8b10a5b09e8d1b32216ac4b4faa353 Mon Sep 17 00:00:00 2001 From: "r.perez" Date: Tue, 17 Feb 2026 19:50:36 -0500 Subject: [PATCH 1/5] [FIX] report_py3o: make div container role alert conditionally invisible --- report_py3o/views/ir_actions_report.xml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/report_py3o/views/ir_actions_report.xml b/report_py3o/views/ir_actions_report.xml index 8669dce1fd..20bbf065fa 100644 --- a/report_py3o/views/ir_actions_report.xml +++ b/report_py3o/views/ir_actions_report.xml @@ -8,11 +8,13 @@ - " + ) + return Markup("".join(parts)) + + def _insert_html_into_header(self, header, html_to_inject): + if Markup("") in header: + return header.replace( + Markup(""), html_to_inject + Markup(""), 1 + ) + if Markup("") in header: + return header.replace( + Markup(""), Markup("") + html_to_inject, 1 + ) + return header + html_to_inject + + def _inject_images_into_header(self, header, image_configs): + image_html = self._build_image_html(image_configs) + return self._insert_html_into_header(header, image_html) + + def _get_positioned_image_configs(self): + company = self.env.company + images = self.report_positioned_image_ids.filtered( + lambda img: img.company_id == company or not img.company_id + ) + if self.include_company_images: + images |= company.report_positioned_image_ids + return [ + { + "image": img.image, + "pos_top": img.pos_top, + "pos_left": img.pos_left, + "width": img.width, + "height": img.height, + "first_page_only": img.first_page_only, + } + for img in images + if img.image + ] + + def _prepare_html(self, html, report_model=False): + image_configs = self._get_positioned_image_configs() + if not image_configs: + return super()._prepare_html(html, report_model=report_model) + result = super()._prepare_html(html, report_model=report_model) + if not isinstance(result, tuple): + return result + bodies, res_ids, header, footer, specific_paperformat_args = result + header = self._inject_images_into_header(header, image_configs) + return bodies, res_ids, header, footer, specific_paperformat_args + + def _get_report_company(self, res_ids): + if not res_ids or not self.model: + return self.env.company + model = self.env[self.model] + if "company_id" not in model._fields: + return self.env.company + records = model.browse(res_ids).exists() + companies = records.mapped("company_id") + return companies[0] if len(companies) == 1 else self.env.company + + def _render_qweb_pdf(self, report_ref, res_ids=None, data=None): + """Set company context so _get_positioned_image_configs uses the + correct company. + """ + company = self._get_report_company(res_ids) + return super(IrActionsReport, self.with_company(company))._render_qweb_pdf( + report_ref, res_ids, data + ) diff --git a/report_positioned_image/models/report_positioned_image.py b/report_positioned_image/models/report_positioned_image.py new file mode 100644 index 0000000000..edfb5f9dc8 --- /dev/null +++ b/report_positioned_image/models/report_positioned_image.py @@ -0,0 +1,116 @@ +# Copyright 2026 Quartile (https://www.quartile.co) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +import base64 +from io import BytesIO + +from PIL import Image + +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError + + +class ReportPositionedImage(models.Model): + _name = "report.positioned.image" + _description = "Report Positioned Image" + + name = fields.Char(required=True) + image = fields.Binary(attachment=True, required=True) + pos_top = fields.Float(string="Top (mm)", default=5.0) + pos_left = fields.Float(string="Left (mm)", default=5.0) + width = fields.Float(string="Width (mm)") + height = fields.Float(string="Height (mm)") + respect_image_ratio = fields.Boolean( + default=True, + help="When enabled, changing width or height will automatically adjust " + "the other dimension to maintain the original image aspect ratio.", + ) + first_page_only = fields.Boolean() + company_id = fields.Many2one( + comodel_name="res.company", + default=lambda self: self._default_company_id(), + help="Leave empty to apply to all companies. Set a specific company to " + "restrict this image to that company only.", + ) + + def _default_company_id(self): + return self.env.context.get("default_company_id") + + @api.constrains("pos_top", "pos_left", "width", "height") + def _check_positive_values(self): + """Ensure position and dimension fields have positive values.""" + for record in self: + if record.pos_top < 0: + raise ValidationError(_("Top position must be a positive value.")) + if record.pos_left < 0: + raise ValidationError(_("Left position must be a positive value.")) + if record.width <= 0: + raise ValidationError(_("Width must be greater than zero.")) + if record.height <= 0: + raise ValidationError(_("Height must be greater than zero.")) + + def _get_aspect_ratio(self): + """Get image aspect ratio (width/height).""" + if not self.image: + return None + try: + img = Image.open(BytesIO(base64.b64decode(self.image))) + return img.width / img.height + except Exception: + return None + + @api.onchange("image") + def _onchange_image(self): + if not self.image: + return + ratio = self._get_aspect_ratio() + if not ratio: + return + # Set default width to 50mm and calculate height maintaining aspect ratio + self.width = 50.0 + self.height = round(50.0 / ratio, 2) + + @api.onchange("width", "respect_image_ratio") + def _onchange_width(self): + if self._context.get("from_height_onchange"): + return + if not (self.respect_image_ratio and self.width): + return + ratio = self._get_aspect_ratio() + if ratio and self.width > 0: + # Set context flag to prevent circular onchange + self.with_context(from_width_onchange=True).height = round( + self.width / ratio, 2 + ) + + @api.onchange("height") + def _onchange_height(self): + if self._context.get("from_width_onchange"): + return + if not (self.respect_image_ratio and self.height): + return + ratio = self._get_aspect_ratio() + if ratio and self.height > 0: + # Set context flag to prevent circular onchange + self.with_context(from_height_onchange=True).width = round( + self.height * ratio, 2 + ) + + @api.onchange("company_id") + def _onchange_company_id(self): + """Prevent assigning to a different company when created from company form.""" + default_company_id = self.env.context.get("default_company_id") + if not default_company_id: + return + if self.company_id and self.company_id.id != default_company_id: + self.company_id = default_company_id + return { + "warning": { + "title": _("Company Assignment"), + "message": _( + "You cannot assign this image to a different company. " + "Please use the dedicated wizard to assign images to other " + "companies." + ), + } + } diff --git a/report_positioned_image/models/res_company.py b/report_positioned_image/models/res_company.py new file mode 100644 index 0000000000..21df82a227 --- /dev/null +++ b/report_positioned_image/models/res_company.py @@ -0,0 +1,16 @@ +# Copyright 2026 Quartile (https://www.quartile.co) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class ResCompany(models.Model): + _inherit = "res.company" + + report_positioned_image_ids = fields.Many2many( + comodel_name="report.positioned.image", + relation="res_company_positioned_image_rel", + column1="company_id", + column2="image_id", + string="Company Images", + ) diff --git a/report_positioned_image/pyproject.toml b/report_positioned_image/pyproject.toml new file mode 100644 index 0000000000..4231d0cccb --- /dev/null +++ b/report_positioned_image/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/report_positioned_image/readme/CONFIGURE.md b/report_positioned_image/readme/CONFIGURE.md new file mode 100644 index 0000000000..0342fbd98b --- /dev/null +++ b/report_positioned_image/readme/CONFIGURE.md @@ -0,0 +1,33 @@ +To configure company-level images: + +1. Go to *Settings / Companies* +2. Open your company record +3. Navigate to the *Report Images* tab +4. Add images with position settings: + - Upload an image - width defaults to 50mm and height is automatically + calculated to maintain the original aspect ratio + - *Top (mm)*: Distance from the top of the page + - *Left (mm)*: Distance from the left edge of the page + - *Width (mm)*: Width of the image (changing this auto-adjusts height) + - *Height (mm)*: Height of the image (changing this auto-adjusts width) + - *Respect Image Ratio*: When enabled (default), changing width or height + automatically adjusts the other dimension to maintain aspect ratio. + Uncheck for manual control of both dimensions. + - *First Page Only*: Check to show only on the first page + - *Company*: Automatically set to the current company when creating from + the company form. To create shared images, leave empty. + +To configure report-specific images: + +1. Go to *Settings / Technical / Actions / Reports* +2. Open the report you want to customize +3. Navigate to the *Report Images* tab +4. Check *Include Company Images* if you want to show company-level images + in addition to report-specific images +5. Add report-specific images in the list with the same position settings + as above + +**Note**: By default, images maintain their aspect ratio. When you upload an +image, it's automatically sized to 50mm width with proportional height. You can +then adjust either dimension and the other will update automatically to prevent +distortion. diff --git a/report_positioned_image/readme/CONTRIBUTORS.md b/report_positioned_image/readme/CONTRIBUTORS.md new file mode 100644 index 0000000000..c1911de180 --- /dev/null +++ b/report_positioned_image/readme/CONTRIBUTORS.md @@ -0,0 +1,3 @@ +- Quartile \<\> + - Tatsuki Kanda + - Aung Ko Ko Lin diff --git a/report_positioned_image/readme/DESCRIPTION.md b/report_positioned_image/readme/DESCRIPTION.md new file mode 100644 index 0000000000..0220a9c075 --- /dev/null +++ b/report_positioned_image/readme/DESCRIPTION.md @@ -0,0 +1,14 @@ +This module allows you to add positioned images (such as watermarks, logos, +or stamps) to PDF reports. Images can be precisely positioned using millimeter +coordinates (top, left) and you can control whether they appear on all pages +or only the first page. + +The module supports two types of images: + +- *Company-level Images*: Define images at the company level that can be + included in reports by enabling the *Include Company Images* option +- *Report-specific Images*: Configure specific images for individual reports, + filtered by company context and always shown when configured + +Images can be assigned to a specific company or left as shared records +(without company assignment) for use across multiple companies diff --git a/report_positioned_image/security/ir.model.access.csv b/report_positioned_image/security/ir.model.access.csv new file mode 100644 index 0000000000..419de3303f --- /dev/null +++ b/report_positioned_image/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_positioned_image_user,report.positioned.image user,model_report_positioned_image,base.group_user,1,0,0,0 +access_report_positioned_image_manager,report.positioned.image manager,model_report_positioned_image,base.group_system,1,1,1,1 diff --git a/report_positioned_image/security/report_positioned_image_security.xml b/report_positioned_image/security/report_positioned_image_security.xml new file mode 100644 index 0000000000..aa22618b04 --- /dev/null +++ b/report_positioned_image/security/report_positioned_image_security.xml @@ -0,0 +1,12 @@ + + + + Report Positioned Image: multi-company + + [ + '|', + ('company_id', '=', False), + ('company_id', 'in', company_ids) + ] + + diff --git a/report_positioned_image/static/description/index.html b/report_positioned_image/static/description/index.html new file mode 100644 index 0000000000..a88a559316 --- /dev/null +++ b/report_positioned_image/static/description/index.html @@ -0,0 +1,481 @@ + + + + + +Report Positioned Image + + + +
+

Report Positioned Image

+ + +

Beta License: AGPL-3 OCA/reporting-engine Translate me on Weblate Try me on Runboat

+

This module allows you to add positioned images (such as watermarks, +logos, or stamps) to PDF reports. Images can be precisely positioned +using millimeter coordinates (top, left) and you can control whether +they appear on all pages or only the first page.

+

The module supports two types of images:

+
    +
  • Company-level Images: Define images at the company level that can +be included in reports by enabling the Include Company Images +option
  • +
  • Report-specific Images: Configure specific images for individual +reports, filtered by company context and always shown when configured
  • +
+

Images can be assigned to a specific company or left as shared records +(without company assignment) for use across multiple companies

+

Table of contents

+ +
+

Configuration

+

To configure company-level images:

+
    +
  1. Go to Settings / Companies
  2. +
  3. Open your company record
  4. +
  5. Navigate to the Report Images tab
  6. +
  7. Add images with position settings:
      +
    • Upload an image - width defaults to 50mm and height is +automatically calculated to maintain the original aspect ratio
    • +
    • Top (mm): Distance from the top of the page
    • +
    • Left (mm): Distance from the left edge of the page
    • +
    • Width (mm): Width of the image (changing this auto-adjusts +height)
    • +
    • Height (mm): Height of the image (changing this auto-adjusts +width)
    • +
    • Respect Image Ratio: When enabled (default), changing width or +height automatically adjusts the other dimension to maintain +aspect ratio. Uncheck for manual control of both dimensions.
    • +
    • First Page Only: Check to show only on the first page
    • +
    • Company: Automatically set to the current company when creating +from the company form. To create shared images, leave empty.
    • +
    +
  8. +
+

To configure report-specific images:

+
    +
  1. Go to Settings / Technical / Actions / Reports
  2. +
  3. Open the report you want to customize
  4. +
  5. Navigate to the Report Images tab
  6. +
  7. Check Include Company Images if you want to show company-level +images in addition to report-specific images
  8. +
  9. Add report-specific images in the list with the same position +settings as above
  10. +
+

Note: By default, images maintain their aspect ratio. When you +upload an image, it’s automatically sized to 50mm width with +proportional height. You can then adjust either dimension and the other +will update automatically to prevent distortion.

+
+
+

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

+
    +
  • Quartile
  • +
+
+
+

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

+

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

+
+
+
+ + diff --git a/report_positioned_image/tests/__init__.py b/report_positioned_image/tests/__init__.py new file mode 100644 index 0000000000..20a6a6863a --- /dev/null +++ b/report_positioned_image/tests/__init__.py @@ -0,0 +1,4 @@ +# Copyright 2026 Quartile (https://www.quartile.co) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from . import test_report_positioned_image diff --git a/report_positioned_image/tests/test_report_positioned_image.py b/report_positioned_image/tests/test_report_positioned_image.py new file mode 100644 index 0000000000..e52e5302f8 --- /dev/null +++ b/report_positioned_image/tests/test_report_positioned_image.py @@ -0,0 +1,274 @@ +# Copyright 2026 Quartile (https://www.quartile.co) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from markupsafe import Markup + +from odoo import Command +from odoo.exceptions import ValidationError +from odoo.tests.common import TransactionCase + + +class TestReportPositionedImage(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.company_a = cls.env.ref("base.main_company") + cls.company_b = cls.env["res.company"].create({"name": "Company B"}) + cls.report = cls.env["ir.actions.report"].create( + { + "name": "Test Report", + "model": "res.partner", + "report_type": "qweb-pdf", + "report_name": "test_report", + } + ) + # Create a simple 1x1 transparent PNG for testing (base64-encoded) + cls.test_image = ( + b"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhg" + b"GAWjR9awAAAABJRU5ErkJggg==" + ) + cls.image_a = cls.env["report.positioned.image"].create( + { + "name": "Company A Image", + "image": cls.test_image, + "pos_top": 10.0, + "pos_left": 15.0, + "width": 25.0, + "height": 30.0, + "first_page_only": False, + "company_id": cls.company_a.id, + } + ) + cls.company_a.write( + {"report_positioned_image_ids": [Command.set([cls.image_a.id])]} + ) + cls.image_b = cls.env["report.positioned.image"].create( + { + "name": "Company B Image", + "image": cls.test_image, + "pos_top": 50.0, + "pos_left": 60.0, + "width": 70.0, + "height": 80.0, + "first_page_only": True, + "company_id": cls.company_b.id, + } + ) + cls.company_b.write( + {"report_positioned_image_ids": [Command.set([cls.image_b.id])]} + ) + cls.global_image = cls.env["report.positioned.image"].create( + { + "name": "Global Image", + "image": cls.test_image, + "pos_top": 5.0, + "pos_left": 5.0, + "width": 10.0, + "height": 10.0, + "company_id": False, + } + ) + + def test_company_images_respects_company_context(self): + self.report.include_company_images = True + configs = self.report.with_company( + self.company_a + )._get_positioned_image_configs() + self.assertEqual(len(configs), 1) + self.assertEqual(configs[0]["pos_top"], 10.0) + self.assertEqual(configs[0]["pos_left"], 15.0) + self.assertFalse(configs[0]["first_page_only"]) + configs = self.report.with_company( + self.company_b + )._get_positioned_image_configs() + self.assertEqual(len(configs), 1) + self.assertEqual(configs[0]["pos_top"], 50.0) + self.assertEqual(configs[0]["pos_left"], 60.0) + self.assertTrue(configs[0]["first_page_only"]) + + def test_report_images_filter_by_company(self): + self.report.write( + { + "include_company_images": False, + "report_positioned_image_ids": [ + Command.set([self.image_a.id, self.image_b.id]) + ], + } + ) + configs = self.report.with_company( + self.company_a + )._get_positioned_image_configs() + self.assertEqual(len(configs), 1) + self.assertEqual(configs[0]["pos_top"], 10.0) + configs = self.report.with_company( + self.company_b + )._get_positioned_image_configs() + self.assertEqual(len(configs), 1) + self.assertEqual(configs[0]["pos_top"], 50.0) + + def test_combined_company_and_report_images(self): + custom_image = self.env["report.positioned.image"].create( + { + "name": "Custom Report Image", + "image": self.test_image, + "pos_top": 100.0, + "pos_left": 110.0, + "width": 120.0, + "height": 130.0, + "first_page_only": False, + "company_id": self.company_a.id, + } + ) + self.report.write( + { + "include_company_images": True, + "report_positioned_image_ids": [Command.set([custom_image.id])], + } + ) + configs = self.report.with_company( + self.company_a + )._get_positioned_image_configs() + self.assertEqual(len(configs), 2) + self.assertEqual(configs[0]["pos_top"], 100.0) + self.assertEqual(configs[1]["pos_top"], 10.0) + + def test_validation_negative_dimensions(self): + with self.assertRaises(ValidationError): + self.env["report.positioned.image"].create( + { + "name": "Invalid Image", + "image": self.test_image, + "width": -10.0, + "company_id": self.company_a.id, + } + ) + with self.assertRaises(ValidationError): + self.image_a.write({"height": -5.0}) + + def test_build_image_html_positioning(self): + images = [ + { + "image": self.test_image, + "pos_top": 5, + "pos_left": 10, + "width": 20, + "height": 15, + } + ] + html = self.report._build_image_html(images) + html_str = str(html) + self.assertIn("position: fixed", html_str) + self.assertIn("top: 5mm", html_str) + self.assertIn("left: 10mm", html_str) + self.assertIn("width: 20mm", html_str) + self.assertIn("height: 15mm", html_str) + self.assertIn('") + result = self.report._inject_images_into_header(header, images) + result_str = str(result) + # Should contain the first-page class + self.assertIn('class="first-page"', result_str) + + def test_global_images_appear_for_all_companies(self): + self.report.write( + { + "report_positioned_image_ids": [ + Command.set([self.global_image.id, self.image_a.id]) + ] + } + ) + configs_a = self.report.with_company( + self.company_a + )._get_positioned_image_configs() + self.assertEqual(len(configs_a), 2) + # Company B sees: global only (not image_a) + configs_b = self.report.with_company( + self.company_b + )._get_positioned_image_configs() + self.assertEqual(len(configs_b), 1) + + def test_company_id_onchange_with_context(self): + image = ( + self.env["report.positioned.image"] + .with_context(default_company_id=self.company_a.id) + .new( + { + "name": "Test Image", + "image": self.test_image, + "width": 10.0, + "height": 10.0, + "company_id": self.company_a.id, + } + ) + ) + image.company_id = self.company_b + result = image._onchange_company_id() + self.assertIsNotNone(result) + self.assertIn("warning", result) + self.assertEqual(image.company_id, self.company_a) + image.company_id = self.company_a + result = image._onchange_company_id() + self.assertIsNone(result) + self.assertEqual(image.company_id, self.company_a) + image.company_id = False + result = image._onchange_company_id() + self.assertIsNone(result) + self.assertFalse(image.company_id) + image_no_context = self.env["report.positioned.image"].new( + { + "name": "Free Image", + "image": self.test_image, + "width": 10.0, + "height": 10.0, + "company_id": self.company_b.id, + } + ) + result = image_no_context._onchange_company_id() + self.assertIsNone(result) + self.assertEqual(image_no_context.company_id, self.company_b) diff --git a/report_positioned_image/views/ir_actions_report_views.xml b/report_positioned_image/views/ir_actions_report_views.xml new file mode 100644 index 0000000000..ee27a39005 --- /dev/null +++ b/report_positioned_image/views/ir_actions_report_views.xml @@ -0,0 +1,37 @@ + + + + ir.actions.report.positioned.image + ir.actions.report + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/report_positioned_image/views/report_positioned_image_views.xml b/report_positioned_image/views/report_positioned_image_views.xml new file mode 100644 index 0000000000..25e21faf7b --- /dev/null +++ b/report_positioned_image/views/report_positioned_image_views.xml @@ -0,0 +1,42 @@ + + + + report.positioned.image.view.form + report.positioned.image + +
+ + + + + + + + + + + + + + + + + +
+
+
+ + report.positioned.image.view.tree + report.positioned.image + + + + + + + + +
diff --git a/report_positioned_image/views/res_company_views.xml b/report_positioned_image/views/res_company_views.xml new file mode 100644 index 0000000000..d69a373e48 --- /dev/null +++ b/report_positioned_image/views/res_company_views.xml @@ -0,0 +1,36 @@ + + + + res.company.form.positioned.image + res.company + + + + + + + + + + + + + + + + + + + + + + From f279b416a6d9d1a3c8963e47ef7f19f332df02d9 Mon Sep 17 00:00:00 2001 From: oca-ci Date: Tue, 21 Apr 2026 18:15:54 +0000 Subject: [PATCH 3/5] [UPD] Update report_positioned_image.pot --- .../i18n/report_positioned_image.pot | 191 ++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 report_positioned_image/i18n/report_positioned_image.pot diff --git a/report_positioned_image/i18n/report_positioned_image.pot b/report_positioned_image/i18n/report_positioned_image.pot new file mode 100644 index 0000000000..1a6ba219d2 --- /dev/null +++ b/report_positioned_image/i18n/report_positioned_image.pot @@ -0,0 +1,191 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * report_positioned_image +# +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: report_positioned_image +#: model:ir.model,name:report_positioned_image.model_res_company +msgid "Companies" +msgstr "" + +#. module: report_positioned_image +#: model:ir.model.fields,field_description:report_positioned_image.field_report_positioned_image__company_id +msgid "Company" +msgstr "" + +#. module: report_positioned_image +#. odoo-python +#: code:addons/report_positioned_image/models/report_positioned_image.py:0 +msgid "Company Assignment" +msgstr "" + +#. module: report_positioned_image +#: model:ir.model.fields,field_description:report_positioned_image.field_res_company__report_positioned_image_ids +msgid "Company Images" +msgstr "" + +#. module: report_positioned_image +#: model:ir.model.fields,field_description:report_positioned_image.field_report_positioned_image__create_uid +msgid "Created by" +msgstr "" + +#. module: report_positioned_image +#: model:ir.model.fields,field_description:report_positioned_image.field_report_positioned_image__create_date +msgid "Created on" +msgstr "" + +#. module: report_positioned_image +#: model:ir.model.fields,field_description:report_positioned_image.field_report_positioned_image__display_name +msgid "Display Name" +msgstr "" + +#. module: report_positioned_image +#: model:ir.model.fields,field_description:report_positioned_image.field_report_positioned_image__first_page_only +msgid "First Page Only" +msgstr "" + +#. module: report_positioned_image +#: model:ir.model.fields,field_description:report_positioned_image.field_report_positioned_image__height +#: model_terms:ir.ui.view,arch_db:report_positioned_image.view_act_report_form_positioned_image +#: model_terms:ir.ui.view,arch_db:report_positioned_image.view_company_form_positioned_image +msgid "Height (mm)" +msgstr "" + +#. module: report_positioned_image +#. odoo-python +#: code:addons/report_positioned_image/models/report_positioned_image.py:0 +msgid "Height must be greater than zero." +msgstr "" + +#. module: report_positioned_image +#: model:ir.model.fields,field_description:report_positioned_image.field_report_positioned_image__id +msgid "ID" +msgstr "" + +#. module: report_positioned_image +#: model:ir.model.fields,help:report_positioned_image.field_ir_actions_report__include_company_images +#: model:ir.model.fields,help:report_positioned_image.field_report_pdf_form__include_company_images +msgid "" +"If checked, company-level images will be shown in addition to report-" +"specific images." +msgstr "" + +#. module: report_positioned_image +#: model:ir.model.fields,field_description:report_positioned_image.field_report_positioned_image__image +msgid "Image" +msgstr "" + +#. module: report_positioned_image +#: model:ir.model.fields,field_description:report_positioned_image.field_ir_actions_report__include_company_images +#: model:ir.model.fields,field_description:report_positioned_image.field_report_pdf_form__include_company_images +msgid "Include Company Images" +msgstr "" + +#. module: report_positioned_image +#: model:ir.model.fields,field_description:report_positioned_image.field_report_positioned_image__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: report_positioned_image +#: model:ir.model.fields,field_description:report_positioned_image.field_report_positioned_image__write_date +msgid "Last Updated on" +msgstr "" + +#. module: report_positioned_image +#: model:ir.model.fields,help:report_positioned_image.field_report_positioned_image__company_id +msgid "" +"Leave empty to apply to all companies. Set a specific company to restrict " +"this image to that company only." +msgstr "" + +#. module: report_positioned_image +#: model:ir.model.fields,field_description:report_positioned_image.field_report_positioned_image__pos_left +#: model_terms:ir.ui.view,arch_db:report_positioned_image.view_act_report_form_positioned_image +#: model_terms:ir.ui.view,arch_db:report_positioned_image.view_company_form_positioned_image +msgid "Left (mm)" +msgstr "" + +#. module: report_positioned_image +#. odoo-python +#: code:addons/report_positioned_image/models/report_positioned_image.py:0 +msgid "Left position must be a positive value." +msgstr "" + +#. module: report_positioned_image +#: model:ir.model.fields,field_description:report_positioned_image.field_report_positioned_image__name +msgid "Name" +msgstr "" + +#. module: report_positioned_image +#: model:ir.model,name:report_positioned_image.model_ir_actions_report +msgid "Report Action" +msgstr "" + +#. module: report_positioned_image +#: model:ir.model.fields,field_description:report_positioned_image.field_ir_actions_report__report_positioned_image_ids +#: model:ir.model.fields,field_description:report_positioned_image.field_report_pdf_form__report_positioned_image_ids +#: model_terms:ir.ui.view,arch_db:report_positioned_image.view_act_report_form_positioned_image +#: model_terms:ir.ui.view,arch_db:report_positioned_image.view_company_form_positioned_image +msgid "Report Images" +msgstr "" + +#. module: report_positioned_image +#: model:ir.model,name:report_positioned_image.model_report_positioned_image +msgid "Report Positioned Image" +msgstr "" + +#. module: report_positioned_image +#: model:ir.model.fields,field_description:report_positioned_image.field_report_positioned_image__respect_image_ratio +msgid "Respect Image Ratio" +msgstr "" + +#. module: report_positioned_image +#: model:ir.model.fields,field_description:report_positioned_image.field_report_positioned_image__pos_top +#: model_terms:ir.ui.view,arch_db:report_positioned_image.view_act_report_form_positioned_image +#: model_terms:ir.ui.view,arch_db:report_positioned_image.view_company_form_positioned_image +msgid "Top (mm)" +msgstr "" + +#. module: report_positioned_image +#. odoo-python +#: code:addons/report_positioned_image/models/report_positioned_image.py:0 +msgid "Top position must be a positive value." +msgstr "" + +#. module: report_positioned_image +#: model:ir.model.fields,help:report_positioned_image.field_report_positioned_image__respect_image_ratio +msgid "" +"When enabled, changing width or height will automatically adjust the other " +"dimension to maintain the original image aspect ratio." +msgstr "" + +#. module: report_positioned_image +#: model:ir.model.fields,field_description:report_positioned_image.field_report_positioned_image__width +#: model_terms:ir.ui.view,arch_db:report_positioned_image.view_act_report_form_positioned_image +#: model_terms:ir.ui.view,arch_db:report_positioned_image.view_company_form_positioned_image +msgid "Width (mm)" +msgstr "" + +#. module: report_positioned_image +#. odoo-python +#: code:addons/report_positioned_image/models/report_positioned_image.py:0 +msgid "Width must be greater than zero." +msgstr "" + +#. module: report_positioned_image +#. odoo-python +#: code:addons/report_positioned_image/models/report_positioned_image.py:0 +msgid "" +"You cannot assign this image to a different company. Please use the " +"dedicated wizard to assign images to other companies." +msgstr "" From 8287bad99a85329ff5a77841fcb542e79c47b78b Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Tue, 21 Apr 2026 18:20:32 +0000 Subject: [PATCH 4/5] [BOT] post-merge updates --- README.md | 1 + report_positioned_image/README.rst | 51 +++++++++--------- .../static/description/icon.png | Bin 0 -> 10254 bytes .../static/description/index.html | 37 +++++++------ setup/_metapackage/pyproject.toml | 3 +- 5 files changed, 51 insertions(+), 41 deletions(-) create mode 100644 report_positioned_image/static/description/icon.png diff --git a/README.md b/README.md index 18f85670fc..5dd21b55a6 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ addon | version | maintainers | summary [report_partner_address](report_partner_address/) | 18.0.1.0.0 | yostashiro aungkokolin1997 | Translatable partner address details for reports and portal [report_pdf_form](report_pdf_form/) | 18.0.1.0.0 | grindtildeath | Fill custom PDF form reports [report_pdf_zip_download](report_pdf_zip_download/) | 18.0.1.0.0 | | Report PDF ZIP Download +[report_positioned_image](report_positioned_image/) | 18.0.1.0.0 | | Add positioned images to PDF reports. [report_py3o](report_py3o/) | 18.0.1.0.2 | | Reporting engine based on Libreoffice (ODT -> ODT, ODT -> PDF, ODT -> DOC, ODT -> DOCX, ODS -> ODS, etc.) [report_py3o_fusion_server](report_py3o_fusion_server/) | 18.0.1.0.0 | | Let the fusion server handle format conversion. [report_qr](report_qr/) | 18.0.1.0.0 | | Web QR Manager diff --git a/report_positioned_image/README.rst b/report_positioned_image/README.rst index 35c7aeb93a..d55eb0d9cf 100644 --- a/report_positioned_image/README.rst +++ b/report_positioned_image/README.rst @@ -1,3 +1,7 @@ +.. image:: https://odoo-community.org/readme-banner-image + :target: https://odoo-community.org/get-involved?utm_source=readme + :alt: Odoo Community Association + ======================= Report Positioned Image ======================= @@ -7,13 +11,13 @@ Report Positioned Image !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:8bc2f08c57ac7bd7e62467501b1ac95394b9e6047b1a4fa48e08a4a99a760e2e + !! source digest: sha256:7492b81c95084617eacd2468003a6783881838c17e7ef230b84bf4d6733dc0e3 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |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 +.. |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%2Freporting--engine-lightgray.png?logo=github @@ -35,11 +39,10 @@ they appear on all pages or only the first page. The module supports two types of images: -- *Company-level Images*: Define images at the company level that can - be included in reports by enabling the *Include Company Images* - option -- *Report-specific Images*: Configure specific images for individual - reports, filtered by company context and always shown when configured +- *Company-level Images*: Define images at the company level that can be + included in reports by enabling the *Include Company Images* option +- *Report-specific Images*: Configure specific images for individual + reports, filtered by company context and always shown when configured Images can be assigned to a specific company or left as shared records (without company assignment) for use across multiple companies @@ -59,20 +62,20 @@ To configure company-level images: 3. Navigate to the *Report Images* tab 4. Add images with position settings: - - Upload an image - width defaults to 50mm and height is - automatically calculated to maintain the original aspect ratio - - *Top (mm)*: Distance from the top of the page - - *Left (mm)*: Distance from the left edge of the page - - *Width (mm)*: Width of the image (changing this auto-adjusts - height) - - *Height (mm)*: Height of the image (changing this auto-adjusts - width) - - *Respect Image Ratio*: When enabled (default), changing width or - height automatically adjusts the other dimension to maintain - aspect ratio. Uncheck for manual control of both dimensions. - - *First Page Only*: Check to show only on the first page - - *Company*: Automatically set to the current company when creating - from the company form. To create shared images, leave empty. + - Upload an image - width defaults to 50mm and height is + automatically calculated to maintain the original aspect ratio + - *Top (mm)*: Distance from the top of the page + - *Left (mm)*: Distance from the left edge of the page + - *Width (mm)*: Width of the image (changing this auto-adjusts + height) + - *Height (mm)*: Height of the image (changing this auto-adjusts + width) + - *Respect Image Ratio*: When enabled (default), changing width or + height automatically adjusts the other dimension to maintain aspect + ratio. Uncheck for manual control of both dimensions. + - *First Page Only*: Check to show only on the first page + - *Company*: Automatically set to the current company when creating + from the company form. To create shared images, leave empty. To configure report-specific images: @@ -110,10 +113,10 @@ Authors Contributors ------------ -- Quartile +- Quartile - - Tatsuki Kanda - - Aung Ko Ko Lin + - Tatsuki Kanda + - Aung Ko Ko Lin Maintainers ----------- diff --git a/report_positioned_image/static/description/icon.png b/report_positioned_image/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..1dcc49c24f364e9adf0afbc6fc0bac6dbecdeb11 GIT binary patch literal 10254 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 -Report Positioned Image +README.rst -
-

Report Positioned Image

+
+ + +Odoo Community Association + +
+

Report Positioned Image

-

Beta License: AGPL-3 OCA/reporting-engine Translate me on Weblate Try me on Runboat

+

Beta License: AGPL-3 OCA/reporting-engine Translate me on Weblate Try me on Runboat

This module allows you to add positioned images (such as watermarks, logos, or stamps) to PDF reports. Images can be precisely positioned using millimeter coordinates (top, left) and you can control whether they appear on all pages or only the first page.

The module supports two types of images:

    -
  • Company-level Images: Define images at the company level that can -be included in reports by enabling the Include Company Images -option
  • +
  • Company-level Images: Define images at the company level that can be +included in reports by enabling the Include Company Images option
  • Report-specific Images: Configure specific images for individual reports, filtered by company context and always shown when configured
@@ -398,7 +402,7 @@

Report Positioned Image

-

Configuration

+

Configuration

To configure company-level images:

  1. Go to Settings / Companies
  2. @@ -414,8 +418,8 @@

    Configuration

  3. Height (mm): Height of the image (changing this auto-adjusts width)
  4. Respect Image Ratio: When enabled (default), changing width or -height automatically adjusts the other dimension to maintain -aspect ratio. Uncheck for manual control of both dimensions.
  5. +height automatically adjusts the other dimension to maintain aspect +ratio. Uncheck for manual control of both dimensions.
  6. First Page Only: Check to show only on the first page
  7. Company: Automatically set to the current company when creating from the company form. To create shared images, leave empty.
  8. @@ -438,7 +442,7 @@

    Configuration

    will update automatically to prevent distortion.

-

Bug Tracker

+

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 @@ -446,15 +450,15 @@

Bug Tracker

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

-

Credits

+

Credits

-

Authors

+

Authors

  • Quartile
-

Contributors

+

Contributors

-

Maintainers

+

Maintainers

This module is maintained by the OCA.

Odoo Community Association @@ -477,5 +481,6 @@

Maintainers

+
diff --git a/setup/_metapackage/pyproject.toml b/setup/_metapackage/pyproject.toml index f6f8c36fce..945924d5cd 100644 --- a/setup/_metapackage/pyproject.toml +++ b/setup/_metapackage/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "odoo-addons-oca-reporting-engine" -version = "18.0.20260415.0" +version = "18.0.20260421.0" dependencies = [ "odoo-addon-base_comment_template==18.0.*", "odoo-addon-bi_sql_editor==18.0.*", @@ -14,6 +14,7 @@ dependencies = [ "odoo-addon-report_partner_address==18.0.*", "odoo-addon-report_pdf_form==18.0.*", "odoo-addon-report_pdf_zip_download==18.0.*", + "odoo-addon-report_positioned_image==18.0.*", "odoo-addon-report_py3o==18.0.*", "odoo-addon-report_py3o_fusion_server==18.0.*", "odoo-addon-report_qr==18.0.*", From ce8b49dd88f4a319408cf99c072ac774855af9f3 Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Tue, 21 Apr 2026 19:14:33 +0000 Subject: [PATCH 5/5] [BOT] post-merge updates --- README.md | 2 +- report_py3o/README.rst | 2 +- report_py3o/__manifest__.py | 2 +- report_py3o/static/description/index.html | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 5dd21b55a6..39f544d899 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ addon | version | maintainers | summary [report_pdf_form](report_pdf_form/) | 18.0.1.0.0 | grindtildeath | Fill custom PDF form reports [report_pdf_zip_download](report_pdf_zip_download/) | 18.0.1.0.0 | | Report PDF ZIP Download [report_positioned_image](report_positioned_image/) | 18.0.1.0.0 | | Add positioned images to PDF reports. -[report_py3o](report_py3o/) | 18.0.1.0.2 | | Reporting engine based on Libreoffice (ODT -> ODT, ODT -> PDF, ODT -> DOC, ODT -> DOCX, ODS -> ODS, etc.) +[report_py3o](report_py3o/) | 18.0.1.0.3 | | Reporting engine based on Libreoffice (ODT -> ODT, ODT -> PDF, ODT -> DOC, ODT -> DOCX, ODS -> ODS, etc.) [report_py3o_fusion_server](report_py3o_fusion_server/) | 18.0.1.0.0 | | Let the fusion server handle format conversion. [report_qr](report_qr/) | 18.0.1.0.0 | | Web QR Manager [report_qweb_element_page_visibility](report_qweb_element_page_visibility/) | 18.0.1.0.0 | | Report Qweb Element Page Visibility diff --git a/report_py3o/README.rst b/report_py3o/README.rst index 8f3c5f26dc..6a8375adb2 100644 --- a/report_py3o/README.rst +++ b/report_py3o/README.rst @@ -11,7 +11,7 @@ Py3o Report Engine !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:5247a746d03835a38b3c79cc709156047927649c6f612f0ff0312619cfb57c48 + !! source digest: sha256:e07081016b19fcddf4ee1a4bb69764055b0c6df19b4628a9fb8766d4777abc54 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png diff --git a/report_py3o/__manifest__.py b/report_py3o/__manifest__.py index e15d2e5106..20ead67c3a 100644 --- a/report_py3o/__manifest__.py +++ b/report_py3o/__manifest__.py @@ -4,7 +4,7 @@ "name": "Py3o Report Engine", "summary": "Reporting engine based on Libreoffice (ODT -> ODT, " "ODT -> PDF, ODT -> DOC, ODT -> DOCX, ODS -> ODS, etc.)", - "version": "18.0.1.0.2", + "version": "18.0.1.0.3", "category": "Reporting", "license": "AGPL-3", "author": "XCG Consulting, ACSONE SA/NV, Odoo Community Association (OCA)", diff --git a/report_py3o/static/description/index.html b/report_py3o/static/description/index.html index e9b6ecda26..34b539f356 100644 --- a/report_py3o/static/description/index.html +++ b/report_py3o/static/description/index.html @@ -372,7 +372,7 @@

Py3o Report Engine

!! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -!! source digest: sha256:5247a746d03835a38b3c79cc709156047927649c6f612f0ff0312619cfb57c48 +!! source digest: sha256:e07081016b19fcddf4ee1a4bb69764055b0c6df19b4628a9fb8766d4777abc54 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

Beta License: AGPL-3 OCA/reporting-engine Translate me on Weblate Try me on Runboat

The py3o reporting engine is a reporting engine for Odoo based on