From 9411fd42e43d73ce3b4e7b35d7ec130900ddc1b5 Mon Sep 17 00:00:00 2001 From: Aungkokolin1997 Date: Mon, 9 Mar 2026 02:15:58 +0000 Subject: [PATCH 1/6] [ADD] report_positioned_image --- report_positioned_image/README.rst | 133 +++++ report_positioned_image/__init__.py | 1 + report_positioned_image/__manifest__.py | 20 + report_positioned_image/models/__init__.py | 3 + .../models/ir_actions_report.py | 115 +++++ .../models/report_positioned_image.py | 116 +++++ report_positioned_image/models/res_company.py | 16 + report_positioned_image/pyproject.toml | 3 + report_positioned_image/readme/CONFIGURE.md | 33 ++ .../readme/CONTRIBUTORS.md | 3 + report_positioned_image/readme/DESCRIPTION.md | 14 + .../security/ir.model.access.csv | 3 + .../report_positioned_image_security.xml | 12 + .../static/description/index.html | 481 ++++++++++++++++++ report_positioned_image/tests/__init__.py | 4 + .../tests/test_report_positioned_image.py | 274 ++++++++++ .../views/ir_actions_report_views.xml | 37 ++ .../views/report_positioned_image_views.xml | 42 ++ .../views/res_company_views.xml | 36 ++ 19 files changed, 1346 insertions(+) create mode 100644 report_positioned_image/README.rst create mode 100644 report_positioned_image/__init__.py create mode 100644 report_positioned_image/__manifest__.py create mode 100644 report_positioned_image/models/__init__.py create mode 100644 report_positioned_image/models/ir_actions_report.py create mode 100644 report_positioned_image/models/report_positioned_image.py create mode 100644 report_positioned_image/models/res_company.py create mode 100644 report_positioned_image/pyproject.toml create mode 100644 report_positioned_image/readme/CONFIGURE.md create mode 100644 report_positioned_image/readme/CONTRIBUTORS.md create mode 100644 report_positioned_image/readme/DESCRIPTION.md create mode 100644 report_positioned_image/security/ir.model.access.csv create mode 100644 report_positioned_image/security/report_positioned_image_security.xml create mode 100644 report_positioned_image/static/description/index.html create mode 100644 report_positioned_image/tests/__init__.py create mode 100644 report_positioned_image/tests/test_report_positioned_image.py create mode 100644 report_positioned_image/views/ir_actions_report_views.xml create mode 100644 report_positioned_image/views/report_positioned_image_views.xml create mode 100644 report_positioned_image/views/res_company_views.xml diff --git a/report_positioned_image/README.rst b/report_positioned_image/README.rst new file mode 100644 index 0000000000..35c7aeb93a --- /dev/null +++ b/report_positioned_image/README.rst @@ -0,0 +1,133 @@ +======================= +Report Positioned Image +======================= + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:8bc2f08c57ac7bd7e62467501b1ac95394b9e6047b1a4fa48e08a4a99a760e2e + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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%2Freporting--engine-lightgray.png?logo=github + :target: https://github.com/OCA/reporting-engine/tree/18.0/report_positioned_image + :alt: OCA/reporting-engine +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/reporting-engine-18-0/reporting-engine-18-0-report_positioned_image + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/reporting-engine&target_branch=18.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +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** + +.. contents:: + :local: + +Configuration +============= + +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. + +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 +------------ + +- Quartile + + - Tatsuki Kanda + - Aung Ko Ko Lin + +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/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/__init__.py b/report_positioned_image/__init__.py new file mode 100644 index 0000000000..0650744f6b --- /dev/null +++ b/report_positioned_image/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/report_positioned_image/__manifest__.py b/report_positioned_image/__manifest__.py new file mode 100644 index 0000000000..683b0de2a9 --- /dev/null +++ b/report_positioned_image/__manifest__.py @@ -0,0 +1,20 @@ +# Copyright 2026 Quartile (https://www.quartile.co) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +{ + "name": "Report Positioned Image", + "summary": "Add positioned images to PDF reports.", + "version": "18.0.1.0.0", + "category": "Reporting", + "author": "Quartile, Odoo Community Association (OCA)", + "website": "https://github.com/OCA/reporting-engine", + "license": "AGPL-3", + "depends": ["web", "report_qweb_element_page_visibility"], + "data": [ + "security/ir.model.access.csv", + "security/report_positioned_image_security.xml", + "views/report_positioned_image_views.xml", + "views/res_company_views.xml", + "views/ir_actions_report_views.xml", + ], + "installable": True, +} diff --git a/report_positioned_image/models/__init__.py b/report_positioned_image/models/__init__.py new file mode 100644 index 0000000000..197c7163dd --- /dev/null +++ b/report_positioned_image/models/__init__.py @@ -0,0 +1,3 @@ +from . import ir_actions_report +from . import report_positioned_image +from . import res_company diff --git a/report_positioned_image/models/ir_actions_report.py b/report_positioned_image/models/ir_actions_report.py new file mode 100644 index 0000000000..9fdca590fc --- /dev/null +++ b/report_positioned_image/models/ir_actions_report.py @@ -0,0 +1,115 @@ +# 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 fields, models +from odoo.tools.image import image_data_uri + + +class IrActionsReport(models.Model): + _inherit = "ir.actions.report" + + include_company_images = fields.Boolean( + help="If checked, company-level images will be shown in addition to " + "report-specific images.", + ) + report_positioned_image_ids = fields.Many2many( + comodel_name="report.positioned.image", + relation="ir_actions_report_positioned_image_rel", + column1="report_id", + column2="image_id", + string="Report Images", + ) + + @staticmethod + def _build_image_html(images): + parts = [] + for image in images: + image_content = image.get("image") + if not image_content: + continue + style_parts = [ + "position: fixed", + f"top: {image.get('pos_top', 5)}mm", + f"left: {image.get('pos_left', 5)}mm", + f"width: {image.get('width', 20)}mm", + f"height: {image.get('height', 20)}mm", + ] + style = "; ".join(style_parts) + ";" + data_uri = image_data_uri(image_content) + # Use 'first-page' class from report_qweb_element_page_visibility + # for images that should only appear on the first page + css_class = "first-page" if image.get("first_page_only") else "" + class_attr = f' class="{css_class}"' if css_class else "" + parts.append( + f'' + f'' + "" + ) + 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 3a89005eca5d0bdde05c17d75aeb967f1dc06f36 Mon Sep 17 00:00:00 2001 From: oca-ci Date: Tue, 21 Apr 2026 18:15:54 +0000 Subject: [PATCH 2/6] [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 d6393eabea25ed7f3fc890c2867e27817fc0ea19 Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Tue, 21 Apr 2026 18:20:32 +0000 Subject: [PATCH 3/6] [BOT] post-merge updates --- report_positioned_image/README.rst | 51 +++++++++--------- .../static/description/icon.png | Bin 0 -> 10254 bytes .../static/description/index.html | 37 +++++++------ 3 files changed, 48 insertions(+), 40 deletions(-) create mode 100644 report_positioned_image/static/description/icon.png 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

+
From 810791b228120fee9581f2dc5751f9a8a0532e31 Mon Sep 17 00:00:00 2001 From: mymage Date: Wed, 22 Apr 2026 09:25:27 +0000 Subject: [PATCH 4/6] Added translation using Weblate (Italian) --- report_positioned_image/i18n/it.po | 192 +++++++++++++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 report_positioned_image/i18n/it.po diff --git a/report_positioned_image/i18n/it.po b/report_positioned_image/i18n/it.po new file mode 100644 index 0000000000..2b4287c7b7 --- /dev/null +++ b/report_positioned_image/i18n/it.po @@ -0,0 +1,192 @@ +# 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: Automatically generated\n" +"Language-Team: none\n" +"Language: it\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +#. 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 452d98096c1bd73992a7da67a8c12239e6f842ba Mon Sep 17 00:00:00 2001 From: mymage Date: Wed, 13 May 2026 06:17:15 +0000 Subject: [PATCH 5/6] Translated using Weblate (Italian) Currently translated at 100.0% (30 of 30 strings) Translation: reporting-engine-18.0/reporting-engine-18.0-report_positioned_image Translate-URL: https://translation.odoo-community.org/projects/reporting-engine-18-0/reporting-engine-18-0-report_positioned_image/it/ --- report_positioned_image/i18n/it.po | 65 +++++++++++++++++------------- 1 file changed, 38 insertions(+), 27 deletions(-) diff --git a/report_positioned_image/i18n/it.po b/report_positioned_image/i18n/it.po index 2b4287c7b7..aba5fe1430 100644 --- a/report_positioned_image/i18n/it.po +++ b/report_positioned_image/i18n/it.po @@ -6,72 +6,74 @@ msgid "" msgstr "" "Project-Id-Version: Odoo Server 18.0\n" "Report-Msgid-Bugs-To: \n" -"Last-Translator: Automatically generated\n" +"PO-Revision-Date: 2026-05-13 08:45+0000\n" +"Last-Translator: mymage \n" "Language-Team: none\n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" "Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 5.15.2\n" #. module: report_positioned_image #: model:ir.model,name:report_positioned_image.model_res_company msgid "Companies" -msgstr "" +msgstr "Aziende" #. module: report_positioned_image #: model:ir.model.fields,field_description:report_positioned_image.field_report_positioned_image__company_id msgid "Company" -msgstr "" +msgstr "Azienda" #. module: report_positioned_image #. odoo-python #: code:addons/report_positioned_image/models/report_positioned_image.py:0 msgid "Company Assignment" -msgstr "" +msgstr "Assegnazione azienda" #. module: report_positioned_image #: model:ir.model.fields,field_description:report_positioned_image.field_res_company__report_positioned_image_ids msgid "Company Images" -msgstr "" +msgstr "Immagini azienda" #. module: report_positioned_image #: model:ir.model.fields,field_description:report_positioned_image.field_report_positioned_image__create_uid msgid "Created by" -msgstr "" +msgstr "Creato da" #. module: report_positioned_image #: model:ir.model.fields,field_description:report_positioned_image.field_report_positioned_image__create_date msgid "Created on" -msgstr "" +msgstr "Creato il" #. module: report_positioned_image #: model:ir.model.fields,field_description:report_positioned_image.field_report_positioned_image__display_name msgid "Display Name" -msgstr "" +msgstr "Nome visualizzato" #. 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 "" +msgstr "Solo la prima pagina" #. 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 "" +msgstr "Altezza (mm)" #. 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 "" +msgstr "L'altezza deve essere maggiore di zero." #. module: report_positioned_image #: model:ir.model.fields,field_description:report_positioned_image.field_report_positioned_image__id msgid "ID" -msgstr "" +msgstr "ID" #. module: report_positioned_image #: model:ir.model.fields,help:report_positioned_image.field_ir_actions_report__include_company_images @@ -80,27 +82,29 @@ msgid "" "If checked, company-level images will be shown in addition to report-" "specific images." msgstr "" +"Se selezionata, le immagini a livello azienda verranno visualizzate in " +"aggiunta alle immagini specifiche del resoconto." #. module: report_positioned_image #: model:ir.model.fields,field_description:report_positioned_image.field_report_positioned_image__image msgid "Image" -msgstr "" +msgstr "Immgine" #. 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 "" +msgstr "Includi immagini azienda" #. module: report_positioned_image #: model:ir.model.fields,field_description:report_positioned_image.field_report_positioned_image__write_uid msgid "Last Updated by" -msgstr "" +msgstr "Ultimo aggiornamento di" #. module: report_positioned_image #: model:ir.model.fields,field_description:report_positioned_image.field_report_positioned_image__write_date msgid "Last Updated on" -msgstr "" +msgstr "Ultimo aggiornamento il" #. module: report_positioned_image #: model:ir.model.fields,help:report_positioned_image.field_report_positioned_image__company_id @@ -108,29 +112,31 @@ msgid "" "Leave empty to apply to all companies. Set a specific company to restrict " "this image to that company only." msgstr "" +"Lasciare vuoto per applicare a tutte le aziende. Impostare un'azienda " +"specifica per limitare solo a quella azienda." #. 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 "" +msgstr "Sinistra (mm)" #. 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 "" +msgstr "La posizione da sinistra deve essere un numero positivo." #. module: report_positioned_image #: model:ir.model.fields,field_description:report_positioned_image.field_report_positioned_image__name msgid "Name" -msgstr "" +msgstr "Nome" #. module: report_positioned_image #: model:ir.model,name:report_positioned_image.model_ir_actions_report msgid "Report Action" -msgstr "" +msgstr "Azione resoconto" #. module: report_positioned_image #: model:ir.model.fields,field_description:report_positioned_image.field_ir_actions_report__report_positioned_image_ids @@ -138,30 +144,30 @@ msgstr "" #: 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 "" +msgstr "Immagini resoconto" #. module: report_positioned_image #: model:ir.model,name:report_positioned_image.model_report_positioned_image msgid "Report Positioned Image" -msgstr "" +msgstr "Immagine posizionata resoconto" #. 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 "" +msgstr "Rispetta proporzioni immagine" #. 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 "" +msgstr "Superiore (mm)" #. 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 "" +msgstr "La posizione superiore deve essere un numero positivo." #. module: report_positioned_image #: model:ir.model.fields,help:report_positioned_image.field_report_positioned_image__respect_image_ratio @@ -169,19 +175,22 @@ msgid "" "When enabled, changing width or height will automatically adjust the other " "dimension to maintain the original image aspect ratio." msgstr "" +"Quando abilitata, la modifica della larghezza o dell'altezza adatterà " +"automaticamente le altre dimensioni per mantenere la proporzione originale " +"dell'immagine." #. 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 "" +msgstr "Larghezza (mm)" #. 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 "" +msgstr "La larghezza deve essere maggiore di zero." #. module: report_positioned_image #. odoo-python @@ -190,3 +199,5 @@ msgid "" "You cannot assign this image to a different company. Please use the " "dedicated wizard to assign images to other companies." msgstr "" +"Non si può assegnare questa immagine ad una azienda diversa. Utilizzare la " +"procedura guidata dedicata per assegnare le immagini ad altre aziende." From 30d013898d27558645f3327a06c4cbb3b81c061a Mon Sep 17 00:00:00 2001 From: Aungkokolin1997 Date: Fri, 28 Aug 2026 07:51:41 +0000 Subject: [PATCH 6/6] [MIG] report_positioned_image: Migration to 19.0 --- report_positioned_image/README.rst | 64 +++++++++++-------- report_positioned_image/__manifest__.py | 3 +- .../models/report_positioned_image.py | 22 ++++--- .../static/description/index.html | 17 +++-- .../tests/test_report_positioned_image.py | 5 +- 5 files changed, 66 insertions(+), 45 deletions(-) diff --git a/report_positioned_image/README.rst b/report_positioned_image/README.rst index d55eb0d9cf..998bea0f7f 100644 --- a/report_positioned_image/README.rst +++ b/report_positioned_image/README.rst @@ -21,13 +21,13 @@ Report Positioned Image :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 - :target: https://github.com/OCA/reporting-engine/tree/18.0/report_positioned_image + :target: https://github.com/OCA/reporting-engine/tree/19.0/report_positioned_image :alt: OCA/reporting-engine .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png - :target: https://translation.odoo-community.org/projects/reporting-engine-18-0/reporting-engine-18-0-report_positioned_image + :target: https://translation.odoo-community.org/projects/reporting-engine-19-0/reporting-engine-19-0-report_positioned_image :alt: Translate me on Weblate .. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png - :target: https://runboat.odoo-community.org/builds?repo=OCA/reporting-engine&target_branch=18.0 + :target: https://runboat.odoo-community.org/builds?repo=OCA/reporting-engine&target_branch=19.0 :alt: Try me on Runboat |badge1| |badge2| |badge3| |badge4| |badge5| @@ -39,10 +39,11 @@ 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 @@ -62,20 +63,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: @@ -98,7 +99,7 @@ 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 `_. +`feedback `_. Do not contact contributors directly about support or help with technical issues. @@ -113,10 +114,10 @@ Authors Contributors ------------ -- Quartile +- Quartile - - Tatsuki Kanda - - Aung Ko Ko Lin + - Tatsuki Kanda + - Aung Ko Ko Lin Maintainers ----------- @@ -131,6 +132,17 @@ 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. +.. |maintainer-AungKoKoLin1997| image:: https://github.com/AungKoKoLin1997.png?size=40px + :target: https://github.com/AungKoKoLin1997 + :alt: AungKoKoLin1997 +.. |maintainer-kanda999| image:: https://github.com/kanda999.png?size=40px + :target: https://github.com/kanda999 + :alt: kanda999 + +Current `maintainers `__: + +|maintainer-AungKoKoLin1997| |maintainer-kanda999| + +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/__manifest__.py b/report_positioned_image/__manifest__.py index 683b0de2a9..08dd654b8b 100644 --- a/report_positioned_image/__manifest__.py +++ b/report_positioned_image/__manifest__.py @@ -3,7 +3,7 @@ { "name": "Report Positioned Image", "summary": "Add positioned images to PDF reports.", - "version": "18.0.1.0.0", + "version": "19.0.1.0.0", "category": "Reporting", "author": "Quartile, Odoo Community Association (OCA)", "website": "https://github.com/OCA/reporting-engine", @@ -16,5 +16,6 @@ "views/res_company_views.xml", "views/ir_actions_report_views.xml", ], + "maintainers": ["AungKoKoLin1997", "kanda999"], "installable": True, } diff --git a/report_positioned_image/models/report_positioned_image.py b/report_positioned_image/models/report_positioned_image.py index edfb5f9dc8..6b6389912d 100644 --- a/report_positioned_image/models/report_positioned_image.py +++ b/report_positioned_image/models/report_positioned_image.py @@ -6,7 +6,7 @@ from PIL import Image -from odoo import _, api, fields, models +from odoo import api, fields, models from odoo.exceptions import ValidationError @@ -41,13 +41,17 @@ 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.")) + raise ValidationError( + self.env._("Top position must be a positive value.") + ) if record.pos_left < 0: - raise ValidationError(_("Left position must be a positive value.")) + raise ValidationError( + self.env._("Left position must be a positive value.") + ) if record.width <= 0: - raise ValidationError(_("Width must be greater than zero.")) + raise ValidationError(self.env._("Width must be greater than zero.")) if record.height <= 0: - raise ValidationError(_("Height must be greater than zero.")) + raise ValidationError(self.env._("Height must be greater than zero.")) def _get_aspect_ratio(self): """Get image aspect ratio (width/height).""" @@ -72,7 +76,7 @@ def _onchange_image(self): @api.onchange("width", "respect_image_ratio") def _onchange_width(self): - if self._context.get("from_height_onchange"): + if self.env.context.get("from_height_onchange"): return if not (self.respect_image_ratio and self.width): return @@ -85,7 +89,7 @@ def _onchange_width(self): @api.onchange("height") def _onchange_height(self): - if self._context.get("from_width_onchange"): + if self.env.context.get("from_width_onchange"): return if not (self.respect_image_ratio and self.height): return @@ -106,8 +110,8 @@ def _onchange_company_id(self): self.company_id = default_company_id return { "warning": { - "title": _("Company Assignment"), - "message": _( + "title": self.env._("Company Assignment"), + "message": self.env._( "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/static/description/index.html b/report_positioned_image/static/description/index.html index a83b124585..6dba8620c2 100644 --- a/report_positioned_image/static/description/index.html +++ b/report_positioned_image/static/description/index.html @@ -374,15 +374,16 @@

Report Positioned Image

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !! source digest: sha256:7492b81c95084617eacd2468003a6783881838c17e7ef230b84bf4d6733dc0e3 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> -

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
@@ -418,8 +419,8 @@

Configuration

  • 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.
  • +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.
  • @@ -446,7 +447,7 @@

    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.

    +feedback.

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

    @@ -476,7 +477,9 @@

    Maintainers

    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.

    +

    Current maintainers:

    +

    AungKoKoLin1997 kanda999

    +

    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/test_report_positioned_image.py b/report_positioned_image/tests/test_report_positioned_image.py index e52e5302f8..96b04553cc 100644 --- a/report_positioned_image/tests/test_report_positioned_image.py +++ b/report_positioned_image/tests/test_report_positioned_image.py @@ -5,10 +5,11 @@ from odoo import Command from odoo.exceptions import ValidationError -from odoo.tests.common import TransactionCase +from odoo.addons.base.tests.common import BaseCommon -class TestReportPositionedImage(TransactionCase): + +class TestReportPositionedImage(BaseCommon): @classmethod def setUpClass(cls): super().setUpClass()