Four message_post calls were passing strings with HTML tags as
plain `body=_(...)` instead of `body=Markup(_(...))`. Odoo escapes
non-Markup strings, so the chatter rendered "<b>QA Review failed</b>"
as literal text instead of bolding it.
Original bug surfaced via the Contract Review (QA-005) flow:
body: "<b>QA Review failed</b> by Garry Singh. Awaiting
client information.<br/><b>Reason:</b><br/>
<div data-oe-version=\"2.0\">Need to get updated
drawing...</div>"
Audit scan turned up three more identical patterns:
fusion_plating/models/fp_parent_numbered_mixin.py:118
"Issued <strong>%s</strong> to ..."
fusion_plating_jobs/models/sale_order.py:282
"Confirmed quote <strong>%s</strong> as <strong>%s</strong>."
fusion_plating_quality/models/fp_contract_review.py:430
"<b>QA Review failed</b> by ... <b>Reason:</b><br/>%(reason)s"
fusion_plating_quality/models/fp_contract_review.py:524
"<b>QA Review completed</b> by ... <b>Special Instructions
captured:</b><br/>%(notes)s"
Fixes:
- Wrapped each body=_(...) with Markup(_(...)) using the
Markup(template) % values pattern (auto-escapes the substituted
values; user-supplied free text stays safe).
- For Html-field substitutions (qa_failure_reason,
special_instructions), explicitly wrapped the value in Markup()
so already-formatted HTML editor content (with data-oe-version="2.0"
wrapper divs) flows through without being re-escaped.
- Added `from markupsafe import Markup` to the two files that
didn't already import it (mixin + contract_review).
Drift cleanup: pulled the 180-line newer fp_contract_review.py
from entech to the local repo (added action_qa_review_failed,
action_open_client_email_wizard, action_view_client_emails,
action_complete_after_info, awaiting_info state, qa_failure_reason
+ special_instructions Html fields, etc. that had been edited on
entech without being committed).
Tested by re-posting via odoo shell on review 10: body now stores
"<b>QA Review failed</b>..." with literal HTML tags instead of
the double-escaped "<b>..." entities. Old chatter records
with the bad escape stay as-is in the audit trail.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Fusion Plating
Core module of the Fusion Plating product family. A configurable, multi-tenant capable ERP for plating and metal-finishing shops, built for Odoo 19 Community and Enterprise.
Copyright © 2026 Nexa Systems Inc. License: OPL-1 (Odoo Proprietary License v1.0)
What this module is
fusion_plating is the process-agnostic foundation that every plating or
metal-finishing shop needs, regardless of size, jurisdiction, process mix, or
industry. It provides:
- Facility — physical sites with their own tanks, operators, capabilities
- Process Type — extensible taxonomy (filled in by process packs)
- Work Center — lines and stations inside a facility
- Tank — physical vessel with QR code, state, bath history
- Bath — the chemistry currently in a tank, with its own lifecycle
- Bath Parameter — schema for chemistry readings
- Bath Log — daily/per-shift chemistry readings with pass/warn/fail rollup
- Security — Operator / Supervisor / Manager / Administrator roles
- Theme-aware UI — respects Odoo light/dark mode with zero duplication
What this module is not
This core intentionally ships with:
- No process chemistry — install
fusion_plating_process_en,_chrome,_anodize,_black_oxideetc. to get actual process types and their bath parameter schemas. - No regulatory data — install
fusion_plating_compliance_<region>to get jurisdiction-specific limits, forms, and reporting workflows. - No industry specialisations — install
fusion_plating_aerospace,_nuclear,_cgpetc. for industry-specific QMS overlays. - No client-specific strings — everything is data-driven.
Product family
| Module | Purpose | Status |
|---|---|---|
fusion_plating |
Core (this module) | MVP |
fusion_plating_quality |
QMS: NCR, CAPA, doc control, calibration, CoC | planned |
fusion_plating_compliance |
Generic compliance framework | planned |
fusion_plating_compliance_on |
Ontario regulatory pack | planned |
fusion_plating_compliance_tor |
Toronto Ch. 681 municipal pack | planned |
fusion_plating_safety |
SDS, WHMIS/TDG, JHSC, exposure | planned |
fusion_plating_shopfloor |
Tablet operator stations, QR scanning, bake-window enforcer | planned |
fusion_plating_portal |
Customer portal | planned |
fusion_plating_process_en |
Electroless nickel — low/mid/high phos | planned |
fusion_plating_process_chrome |
Chrome coating (hex & trivalent) | planned |
fusion_plating_process_anodize |
Aluminum anodizing (Type II, III) | planned |
fusion_plating_process_black_oxide |
Black oxidizing | planned |
fusion_plating_aerospace |
AS9100 + Nadcap AC7108 | planned |
fusion_plating_nuclear |
CSA N299, CNSC, NQA-1 | planned |
fusion_plating_cgp |
Controlled Goods Program | planned |
fusion_plating_logistics |
Pickup & delivery routing | planned |
fusion_plating_culture |
Values / fundamentals framework | planned |
fusion_plating_bridge_sign |
EE bridge: e-sign CoC acceptance | planned |
fusion_plating_bridge_documents |
EE bridge: Documents workspace | planned |
fusion_plating_bridge_quality |
EE bridge: native quality module |
planned |
Installation
# Development
docker exec odoo-dev-app odoo -d fusion-dev -u fusion_plating --stop-after-init
# Production — after rsync to target server
docker exec <odoo-container> odoo -d <db> -u fusion_plating --stop-after-init
No external Python dependencies. Depends only on standard Odoo 19 Community
base modules (base, mail, contacts, product, stock, sale_management,
purchase, hr, uom).
Design principles
- Works on both Odoo Community and Enterprise. Never depends on
quality,documents,sign,studio, ormrp_plm. EE-specific integrations live in separatefusion_plating_bridge_*modules. - No client-specific strings in core. Configuration, not code.
- Regions are data, not code. Sewer limits, waste classes, reporting forms come from region packs.
- Processes are plug-ins. New process (copper, zinc, tin) = new
fusion_plating_process_*module, core untouched. - Dashboards are configured, not coded. Shops pick their own headline KPIs.
- Theme-aware. Uses Odoo/Bootstrap CSS variables. One source of truth for colours; Odoo's theme engine decides light vs dark.
Security groups
| Group | Intended for |
|---|---|
| Operator | Shop-floor staff. Reads reference data, writes chemistry logs. |
| Supervisor | Line supervisors. Manages baths, schedules jobs, reviews logs. |
| Manager | Quality, EHS, plant manager, engineer. Full CRUD on configuration. |
| Administrator | Owner, system admin. All manager rights + system settings. |
Field naming convention
- New models use
fusion.plating.*namespace. - Fields on our own models use simple names (no prefix).
- Fields added to base Odoo models (
res.company,res.partner,product.template, etc.) use thex_fc_prefix per the repo convention.
Developer notes
- All models inheriting from
mail.threaduse the Odoo 19 chatter pattern. - Security follows the Odoo 19
res.groups.privilegepattern (module category → privilege → groups), not the legacycategory_id-on-group pattern. - Sequence numbers use
ir.sequenceseeded indata/fp_sequence_data.xml. - SCSS uses
color-mix()against CSS custom properties — never hardcodes hex values. Seestatic/src/scss/fusion_plating.scssfor the theming contract. - No
group expand="0"in search views (Odoo 19 incompatibility). - No
category_idorusersfield onres.groups(Odoo 19 incompatibility).