From 838d941640bce10fe0e1fc8f1f6f1c90dca0b049 Mon Sep 17 00:00:00 2001 From: Daniel Gergely Date: Thu, 5 Nov 2026 09:07:59 +0100 Subject: [PATCH 1/6] [T3616] FIX: lay out the B2S translation before printing, as in v14 Translations saved on extra scanned pages were missing from the PDF. - Spread the translation into the boxes before printing, like v14 (writes once per letter). - Text under 5 characters stays put, so no more empty pages. --- sbc_compassion/models/correspondence_paragraph.py | 4 ++++ sbc_compassion/models/ir_actions_report.py | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/sbc_compassion/models/correspondence_paragraph.py b/sbc_compassion/models/correspondence_paragraph.py index ed1530945..eed83fb87 100644 --- a/sbc_compassion/models/correspondence_paragraph.py +++ b/sbc_compassion/models/correspondence_paragraph.py @@ -16,6 +16,8 @@ BOX_SEPARATOR = "#BOX#" PAGE_SEPARATOR = "#PAGE#" +# Shorter texts are leftovers (dots, initials), as in v14 +MIN_TEXT_LENGTH = 5 class CorrespondenceParagraph(models.Model): @@ -86,6 +88,8 @@ def check_overflow(self, field_name): if not text_box: # The page has no box for this text (e.g. B2S L4/L6 print the # translation on page 2 only): move it to the next box. + if len(text.strip()) < MIN_TEXT_LENGTH: + return text, "" return "", text try: return text_box.split_text(text) diff --git a/sbc_compassion/models/ir_actions_report.py b/sbc_compassion/models/ir_actions_report.py index 3dd9c0ed6..1f46692c4 100644 --- a/sbc_compassion/models/ir_actions_report.py +++ b/sbc_compassion/models/ir_actions_report.py @@ -23,6 +23,12 @@ def _render_qweb_pdf(self, report_ref, res_ids=None, data=None): ): return super()._render_qweb_pdf(report_ref, res_ids=res_ids, data=data) + # As the v14 composition did, lay the translation out in the boxes + # before printing, whatever page it was saved on. Writes only once. + letters = self.env["correspondence"].sudo().browse(res_ids) + for letter in letters.filtered("report_needs_final_text"): + letter.spread_text_to_pages(["translated_text"]) + attachments = self.env["ir.attachment"].search( [ ("res_model", "=", "correspondence"), From d01e47b3e1ac1ef9015ffcb1402c750dccb496e2 Mon Sep 17 00:00:00 2001 From: Emanuel Cino Date: Thu, 5 Nov 2026 10:05:30 +0100 Subject: [PATCH 2/6] T3646 FIX fit B2S letter pages to A4 in correspondence report Print the page images at an absolute width so that they never overflow on the next page, and fetch the page from Connect first when the Cloudinary image is a crop of a stack of pages (pages of different heights were cut wrongly). Assisted-by: claude-sonnet-5-5, medium, claude-code --- sbc_compassion/models/correspondence_page.py | 61 +++++++++++++------ .../reports/correspondence_report.xml | 16 ++--- 2 files changed, 53 insertions(+), 24 deletions(-) diff --git a/sbc_compassion/models/correspondence_page.py b/sbc_compassion/models/correspondence_page.py index 858ee24a2..ce37a2432 100644 --- a/sbc_compassion/models/correspondence_page.py +++ b/sbc_compassion/models/correspondence_page.py @@ -19,6 +19,7 @@ from odoo import api, fields, models from ..tools.onramp_connector import SBCConnector +from .correspondence_text_box import PAGE_HEIGHT_PX, PAGE_WIDTH_PX _logger = logging.getLogger(__name__) @@ -78,25 +79,29 @@ def _compute_page_image(self, image_type="original"): image_field = f"{image_type}_page_image" url_field = f"{image_type}_page_url" for page in self: - if getattr(page, cloudinary_url_field): - setattr( - page, - image_field, - page._get_cloundinary_image(getattr(page, cloudinary_url_field)), - ) - if not getattr(page, image_field) and getattr(page, url_field): - connector = SBCConnector(self.env) - setattr( - page, - image_field, - connector.get_letter_image(getattr(page, url_field), {"dpi": 96}), - ) - if ( - not getattr(page, image_field) - and page.correspondence_id.direction == "Beneficiary To Supporter" - ): + cloudinary_url = getattr(page, cloudinary_url_field) + page_url = getattr(page, url_field) + image = False + # A cropped image slices a stack of pages in equal parts, which is + # wrong when the pages don't have the same height: the page of the + # document is used first. + connect_first = bool(cloudinary_url and "c_crop" in cloudinary_url) + if connect_first: + image = page._get_connect_image(page_url) + if not image and cloudinary_url: + image = page._get_cloundinary_image(cloudinary_url) + if not image and not connect_first: + image = page._get_connect_image(page_url) + if image: + setattr(page, image_field, image) + elif page.correspondence_id.direction == "Beneficiary To Supporter": setattr(page, image_field, page.template_id.background) + def _get_connect_image(self, page_url): + if not page_url: + return False + return SBCConnector(self.env).get_letter_image(page_url, {"dpi": 96}) or False + def _compute_original_page_image(self): self._compute_page_image("original") @@ -157,6 +162,28 @@ def fetch_image(self): self._compute_page_image(image_field.replace("_page_image", "")) return getattr(self, image_field) + @api.model + def fetch_image_width(self, image): + """ + Used by the PDF report: width in px at which the image is printed so + that it fits on one A4 page. The width must be absolute, because a + percentage resolves to the natural size of the image in wkhtmltopdf, + which makes large images overflow on the next page. Images that are + higher than A4 are shrunk, the others keep the full page width. + """ + if not image: + return PAGE_WIDTH_PX + try: + width, height = Image.open(BytesIO(base64.b64decode(image))).size + except Exception: + _logger.warning("Could not read the size of a page image", exc_info=True) + return PAGE_WIDTH_PX + if not width or not height: + return PAGE_WIDTH_PX + # Keep a small margin so that rounding never pushes it onto a new page + max_width = (PAGE_HEIGHT_PX - 2) * width / height + return round(min(PAGE_WIDTH_PX, max_width), 1) + def _fetch_sponsor_pdf_image(self): self.ensure_one() if self.correspondence_id.sponsor_letter_scan: diff --git a/sbc_compassion/reports/correspondence_report.xml b/sbc_compassion/reports/correspondence_report.xml index eb1e42aa2..9a350959c 100644 --- a/sbc_compassion/reports/correspondence_report.xml +++ b/sbc_compassion/reports/correspondence_report.xml @@ -32,18 +32,20 @@
+ +
-
Date: Thu, 5 Nov 2026 10:11:18 +0100 Subject: [PATCH 3/6] ADD button to open the translation form of a letter to translate Assisted-by: claude-sonnet-5-5, medium, claude-code --- sbc_translation/models/correspondence.py | 17 +++++++++++++++++ sbc_translation/views/correspondence_view.xml | 2 ++ 2 files changed, 19 insertions(+) diff --git a/sbc_translation/models/correspondence.py b/sbc_translation/models/correspondence.py index 1e98c0d45..4b5df7ea8 100644 --- a/sbc_translation/models/correspondence.py +++ b/sbc_translation/models/correspondence.py @@ -254,6 +254,23 @@ def action_open_full_view(self): "res_id": self.id, } + def action_open_translation_view(self): + """Open the letter in the form used in the translation pool.""" + self.ensure_one() + return { + "type": "ir.actions.act_window", + "res_model": self._name, + "views": [ + [ + self.env.ref( + "sbc_translation.correspondence_simple_translation_form" + ).id, + "form", + ] + ], + "res_id": self.id, + } + def publish_b2s_letter(self): """Called when B2S letter is Published. Check if translation is needed and upload to translation platform.""" diff --git a/sbc_translation/views/correspondence_view.xml b/sbc_translation/views/correspondence_view.xml index e5fb574c2..dee8db11d 100644 --- a/sbc_translation/views/correspondence_view.xml +++ b/sbc_translation/views/correspondence_view.xml @@ -9,6 +9,8 @@ class="oe_highlight" invisible="state != 'Global Partner translation queue'"/>