From cb56a38680259839d1812d8746f2fd9ae44703cc Mon Sep 17 00:00:00 2001 From: gsinghpal Date: Wed, 20 May 2026 22:41:17 -0400 Subject: [PATCH] fix(fusion_repairs): chatter posts render HTML correctly via Markup Reports of literal 'Client Self-Service' showing in the chatter instead of bold formatting. Cause: message_post(body=str) HTML-escapes the string. The Odoo idiom for HTML chatter bodies is markupsafe.Markup, with the % operator auto-escaping substitution values for XSS safety. Fixed every message_post call: models/intake_service.py - 'Service call submitted via ...' (the reported one) - 'This repair MAY be covered by our active warranty ...' models/maintenance_contract.py - 'Sent N-day maintenance reminder to ' - 'Maintenance visit ... booked from reminder link' models/technician_task.py - 'Rolled forward after maintenance task ... completed' wizard/repair_visit_report_wizard.py - 'Spawned follow-up repair ... for "found another issue"' Pattern used: Markup(_('... %(x)s ...')) % {'x': escaped_value}. Verified on local westin-v19 (BR-WA/RO/00026): DB row now reads '

Service call submitted via Client Self-Service by Gurpreet Singh. Session reference: RIS000015.

' which renders correctly in the chatter UI. Bumped to 19.0.1.0.3. Co-authored-by: Cursor --- fusion_repairs/__manifest__.py | 2 +- fusion_repairs/models/intake_service.py | 26 +++++++++++-------- fusion_repairs/models/maintenance_contract.py | 15 +++++++---- fusion_repairs/models/technician_task.py | 9 ++++--- .../wizard/repair_visit_report_wizard.py | 7 +++-- 5 files changed, 36 insertions(+), 23 deletions(-) diff --git a/fusion_repairs/__manifest__.py b/fusion_repairs/__manifest__.py index fd92d0d7..19232951 100644 --- a/fusion_repairs/__manifest__.py +++ b/fusion_repairs/__manifest__.py @@ -4,7 +4,7 @@ { 'name': 'Fusion Repairs', - 'version': '19.0.1.0.2', + 'version': '19.0.1.0.3', 'category': 'Inventory/Repairs', 'summary': 'Guided medical equipment repair intake, dispatch, maintenance, and self-service portal', 'description': """ diff --git a/fusion_repairs/models/intake_service.py b/fusion_repairs/models/intake_service.py index 8f5c5315..ef44a55a 100644 --- a/fusion_repairs/models/intake_service.py +++ b/fusion_repairs/models/intake_service.py @@ -17,6 +17,8 @@ place and the surfaces never drift apart. import logging from datetime import timedelta +from markupsafe import Markup + from odoo import _, api, fields, models from odoo.exceptions import UserError @@ -171,13 +173,14 @@ class FusionRepairIntakeService(models.AbstractModel): # Audit message in chatter. repair.message_post( - body=_( + body=Markup(_( 'Service call submitted via %(source)s by %(user)s. ' - 'Session reference: %(ref)s.', - source=dict(repair._fields['x_fc_intake_source'].selection).get(source), - user=intake_user.name, - ref=session_ref, - ), + 'Session reference: %(ref)s.' + )) % { + 'source': dict(repair._fields['x_fc_intake_source'].selection).get(source) or '', + 'user': intake_user.name or '', + 'ref': session_ref or '', + }, ) return repair @@ -235,12 +238,13 @@ class FusionRepairIntakeService(models.AbstractModel): if not warranty: return repair.message_post( - body=_( + body=Markup(_( 'This repair MAY be covered by our active warranty %(ref)s ' - '(expires %(exp)s). Manager review recommended before invoicing.', - ref=warranty.name, - exp=warranty.expiry_date, - ), + '(expires %(exp)s). Manager review recommended before invoicing.' + )) % { + 'ref': warranty.name or '', + 'exp': warranty.expiry_date and str(warranty.expiry_date) or '', + }, message_type='comment', ) diff --git a/fusion_repairs/models/maintenance_contract.py b/fusion_repairs/models/maintenance_contract.py index aa290d7e..f9fa93b1 100644 --- a/fusion_repairs/models/maintenance_contract.py +++ b/fusion_repairs/models/maintenance_contract.py @@ -15,6 +15,7 @@ import secrets from datetime import timedelta from dateutil.relativedelta import relativedelta +from markupsafe import Markup from odoo import _, api, fields, models @@ -149,9 +150,12 @@ class FusionRepairMaintenanceContract(models.Model): tpl.send_mail(c.id, force_send=False) c.last_reminder_band = band_label c.message_post( - body=_('Sent %(band)s-day maintenance reminder to %(email)s.', - band=band_label, - email=c.partner_id.email), + body=Markup(_( + 'Sent %(band)s-day maintenance reminder to %(email)s.' + )) % { + 'band': band_label, + 'email': c.partner_id.email or '', + }, ) except Exception: continue @@ -181,8 +185,9 @@ class FusionRepairMaintenanceContract(models.Model): }) self.booking_repair_id = repair self.message_post( - body=_('Maintenance visit %(ref)s booked from reminder link.', - ref=repair.name), + body=Markup(_( + 'Maintenance visit %(ref)s booked from reminder link.' + )) % {'ref': repair.name or ''}, ) return repair diff --git a/fusion_repairs/models/technician_task.py b/fusion_repairs/models/technician_task.py index 452f31be..40a59996 100644 --- a/fusion_repairs/models/technician_task.py +++ b/fusion_repairs/models/technician_task.py @@ -2,6 +2,8 @@ # Copyright 2024-2026 Nexa Systems Inc. # License OPL-1 (Odoo Proprietary License v1.0) +from markupsafe import Markup + from odoo import fields, models @@ -47,11 +49,10 @@ class FusionTechnicianTaskRepairs(models.Model): try: contract.last_service_date = fields.Date.context_today(task) contract.roll_next_due_date() - contract.message_post(body=( + contract.message_post(body=Markup( 'Rolled forward after maintenance task ' - f'{task.name} completed. ' - f'Next due {contract.next_due_date}.' - )) + '%s completed. Next due %s.' + ) % (task.name or '', str(contract.next_due_date or ''))) except Exception: # Never let a contract roll failure block the task write. pass diff --git a/fusion_repairs/wizard/repair_visit_report_wizard.py b/fusion_repairs/wizard/repair_visit_report_wizard.py index 45dab865..559cf72c 100644 --- a/fusion_repairs/wizard/repair_visit_report_wizard.py +++ b/fusion_repairs/wizard/repair_visit_report_wizard.py @@ -20,6 +20,8 @@ On confirm: import logging +from markupsafe import Markup + from odoo import _, api, fields, models from odoo.exceptions import UserError @@ -162,8 +164,9 @@ class RepairVisitReportWizard(models.TransientModel): 'x_fc_maintenance_contract_id': False, }) repair.message_post( - body=_('Spawned follow-up repair %(name)s for "found another issue".', - name=stub.name), + body=Markup(_( + 'Spawned follow-up repair %(name)s for "found another issue".' + )) % {'name': stub.name or ''}, ) # If a stub was spawned, open it directly so the tech can fill in details.