# Fusion Plating — Claude Code Instructions ## Project Fusion Plating is a multi-module Odoo 19 ERP for electroless nickel plating and metal finishing shops. Built by Nexa Systems for EN Technologies (the client). Replaces Steelhead Software. ## Recent Session Handoff — 2026-05-17 (Portal Redesign + Sub-A IA approved) > **For the next Claude session.** Portal dashboard + jobs + detail + configurator are LIVE on entech at `fusion_plating_portal 19.0.3.7.0`. Sub-A (Portal IA + Sidebar) is brainstormed, spec'd, planned, NOT yet executed — pick up there. Full handoff (live state, decisions, gotchas, how-to-deploy, what's deferred): **[`docs/superpowers/handoffs/2026-05-17-portal-redesign-handoff.md`](docs/superpowers/handoffs/2026-05-17-portal-redesign-handoff.md)**. Approved plan ready to execute: **[`docs/superpowers/plans/2026-05-17-portal-ia-sidebar-plan.md`](docs/superpowers/plans/2026-05-17-portal-ia-sidebar-plan.md)** (11 tasks, 4 phases). Don't re-brainstorm; just execute. ### Previous handoff (pre-portal redesign — superseded but kept for context) > All changes below are LIVE on entech (db `admin` on LXC 111 / pve-worker5). Local repo has uncommitted changes against base commit `9ebf89b`; run `git status` to see them. All durable conventions added during this session are folded into the Critical Rules below (rules 6a, 14, 14a, 14b) — read those FIRST before changing reports / stickers / signatures / SO line tax fields. ### Shipped this session | Area | What | Module @ version | |---|---|---| | **Sticker — multi-line PO** | When SO has ≥2 part-bearing lines, SO External / SO Internal / FP Job External / FP Job Internal stickers print ONE consolidated sticker with Part #: "Multiple Line Items" and Qty = sum. | `fusion_plating_reports` 19.0.11.x, `fusion_plating_jobs` 19.0.10.x | | **Sticker — em-dash mojibake** | `_notes_content` strips em-dash/en-dash/smart-quotes/ellipsis before rendering (entech wkhtmltopdf font mangles them). Same defensive pattern as thickness. See rule 14. | `fusion_plating_reports` | | **Sticker — font weight + QR res** | `.fp-sticker-strong` set to font-weight 400 (only labels and WO# header stay bold). QR bumped 600→1000 (under Odoo's 1.2M-pixel barcode cap). px units + dpi=300 are calibrated — don't touch, see rule 14. | `fusion_plating_reports` | | **Portal SO — Part # column** | New leading column on `sale.sale_order_portal_content` showing `line.x_fc_part_catalog_id.part_number`. NEW file `fusion_plating_portal/views/fp_sale_order_portal.xml`. | `fusion_plating_portal` 19.0.2.2.0 | | **Direct order "False Rev" bug** | `fp_direct_order_wizard.py` header used `part.name` (NULL on many parts) → printed literal "False". Now prefers `part.part_number` and suppresses " Rev X" when revision is blank. Cleaned up 6 existing SO lines on entech with a one-shot SQL UPDATE. | `fusion_plating_configurator` 19.0.21.x | | **Direct order — remember last entered** | New `_fp_seed_from_last_so_line` on wizard line + spec carry-over inherit. On `part_catalog_id` onchange, prefills `process_variant_id`, `unit_price`, `tax_ids`, `customer_spec_id` from the most recent SO line for `(part, customer)`. Spec also auto-pushes to part default after first order. "First-Time Part" warning suppressed when history exists. | `fusion_plating_configurator` + `fusion_plating_quality` | | **Direct order — recipe filter + search** | Process / Recipe picker domain narrowed to `templates + this part's variants + customer-used recipes` (cap 500). New compute `recipe_choice_ids` on wizard line. Recipe `_rec_names_search = ['name', 'code']` so typing the code matches. New recipe auto-set as part default if part has no other variants. | `fusion_plating_configurator` + `fusion_plating` | | **Contract Review flow** | Rule 3: forced redirect to QA-005 form on part create via `bus.bus` notification (new JS service `contract_review_redirect.js`). Rule 4: WO contract-review step auto-completes at `fp.job` creation if the part already has a complete review; new "Contract Review (QA-005)" block on Print WO Detail showing Reviewer / Initials / Date / Status. | `fusion_plating_quality` 19.0.6.x, `fusion_plating_jobs` | | **QA Manager (renamed from QA Signers)** | "QA Manager" settings field (under Contract Review block) now ALSO drives the WO Detail "Certified By" signer. Resolution: `company.x_fc_qa_manager_user_ids[:1]` → `job.manager_id` → `company.x_fc_owner_user_id`. | `fusion_plating_quality` | | **Signature unification** | All FP reports (WO Detail, CoC, CoC Chronological) now read signatures from a single source: `signer_user.x_fc_signature_image` (Plating Signature). Retired: HR Employee signature lookup AND `res.company.x_fc_coc_signature_override` (UI removed; column kept, no migration). See rule 14b. | `fusion_plating_certificates`, `fusion_plating_reports`, `fusion_plating_jobs` | | **Report palette overhaul** | Green `res.company.primary_color` → hardcoded neutral palette: `#c1c1c1` header backgrounds, `#1d1f1e` th text, `#2e2e2e` h2/h4 titles (bumped to 20pt portrait / 22pt landscape). Grand Total row also `#c1c1c1`. Work Order Detail blue `#1a4d80` retired in favour of the same palette. Title format now "Type # Number" (Quotation # …, Sales Order # …, Invoice # …, Packing Slip # …, Work Order Traveller # …). See rule 14a. | `fusion_plating_reports` 19.0.11.14.0, `fusion_plating_jobs` 19.0.10.8.0 | | **Report border rendering** | After two failed attempts (px→mm conversion + dpi bump; then `border-collapse: separate` single-side-per-cell), settled on **`border-collapse: collapse` + longhand borders + `background-clip: padding-box`**. Verticals are a hair softer than horizontals on entech wkhtmltopdf — accepted as the lesser evil vs misaligned tables. See rule 14a, last paragraph. **Don't retry the single-side pattern.** | `fusion_plating_reports` | | **Page-break-inside: avoid placement** | When a long QWeb report dumps content into multi-page PDFs via wkhtmltopdf, the company header (rendered as `--header-html`) can overlap body content if a page break lands mid-row in a table. **Apply `page-break-inside: avoid` to `` elements** (and to wrapper `
`s that wrap whole logical sections like signature blocks), not to ``. On entech wkhtmltopdf, `
`-level `page-break-inside` is unreliable when the table is long enough to definitely break; per-row is honoured. Pattern: keep individual readings/rows together so the wkhtmltopdf header zone never overlaps mid-row content. Wrap the larger logical block (cert thickness section, signature + certification statement) in `
` to keep it together when it fits and naturally wrap to a fresh page when it doesn't. | `fusion_plating_reports/report/report_coc.xml` | | **`opacity` + `italic` muted text renders jagged on entech wkhtmltopdf** | The obvious pattern for a subtle footnote — `font-style: italic; opacity: 0.7;` (used by `.fp-coc .small-label`) — produces washed-out, jagged characters that look "broken" or "messed up" on the printed PDF. Visually it reads as garbled text even though the source is clean. **Use solid grey (`color: #555`) at normal weight instead** for muted secondary text. Same workaround applies to any `opacity`-driven greyed-out element bound for wkhtmltopdf. The existing `.small-label` class still exists for legacy callers but new code should prefer an explicit `color:` style. | `fusion_plating_reports` | | **wkhtmltopdf header overlap — paperformat.margin_top, NOT body padding-top** | The wkhtmltopdf header zone is sized by `report.paperformat.margin_top` (and `header_spacing`). If the `web.external_layout` header (logo + address etc.) renders ~28mm tall but paperformat reserves only 8mm, page 2+ has the header bleeding over body content (the overlap shows up as the company logo printed *on top of* the signature, readings table, etc.). The anti-pattern is "fix" it by adding `padding-top: 50mm` to the body wrapper — this only pads page 1 (single one-shot padding) and does nothing for subsequent pages, while also wasting 50mm of usable space on page 1. **Right fix (the CoC pattern — proven to work):** use a TINY `margin_top` (≤8mm) + `header_spacing=0` + `header_line=False`, then put a generous `padding-top: 20mm` on the body wrapper class (`.fp-coc`, `.fp-sale`, etc.). The header HTML is allowed to overflow the reserved zone INTO the body area; the body's wrapper padding is what actually clears it. This is more robust than trying to size `margin_top` to the rendered header height — any future header change (extra phone line, taller logo) immediately causes overlap with the "sized exactly" approach, whereas the overflow+padding approach has slack built in. Reference setup: `paperformat_fp_coc` and `paperformat_fp_a4_portrait` both use `margin_top=8`, `header_spacing=0`, `header_line=False`; CoC's `.fp-coc` and SO's `.fp-sale` both use `padding-top: 20mm` on the wrapper. Each report can have its own paperformat — `report_coc_en` / `report_coc_fr` use "Fusion Plating CoC" (id 13); the legacy `report_coc` uses "A4 Landscape (Fusion Plating)" (id 12); SO portrait uses "Fusion Plating A4 Portrait (Compact)". Update the right one and don't bleed changes across reports. **Corollary — don't use negative `margin-top` to "tighten" the gap** (e.g. `.my-page { margin-top: -10px; }` to pull the H1 up under the header). The body wrapper sits at the bottom edge of the reserved margin_top zone; any negative margin pushes content INTO the header band, where wkhtmltopdf clips the top of glyphs (looks like the title is half-eaten). If the gap really feels too big, shrink the title font instead, or reduce `paperformat.margin_top` so the entire header zone is shorter. **For customer-facing portrait reports** (SO confirmation, quote, invoice, packing slip, BoL) the canonical compact paperformat is `fusion_plating_reports.paperformat_fp_a4_portrait` (margin_top=22mm, header_spacing=3mm, keeps the standard header band). Bind it via `` rather than creating yet-another-one. **Two compounding-padding traps to be aware of:** (1) Odoo's `.page` class has `padding: 1cm` baked in (Bootstrap-derived). If you wrap your body in `
` AND add a body `padding-top: 15mm`, you get the paperformat margin_top + 10mm Odoo + 15mm yours = ~65mm of dead space above the title. To remove the .page contribution without losing its left/right padding, override only the top: `.fp-report.fp-sale .page { padding-top: 0 !important; }`. CoC sidesteps this by NOT using an inner `.page` div — it wraps directly in `
` and puts padding on that. (2) The base `.fp-report table.bordered th, .fp-report table.bordered td` rule applies borders explicitly, BUT a separate cascade still bleeds borders onto NESTED `
` elements even when the inner table has no `.bordered` class — `border: 0 !important` on the cells does NOT reliably override it (some wkhtmltopdf rendering paths still draw the lines). **Don't use a `
` for non-bordered layouts** like a title/barcode strip; use `
` + `float: right` / flexbox instead. Saves an hour of CSS specificity arguments with wkhtmltopdf. (3) **CSS comments inside QWeb `