Battle-tested complete workflow on entech: ABC Manufacturing + Anodize
recipe (id=136) cloned to part-variant (id=1775) → SO S00276 confirmed →
fp.job 1234 with 17 steps → recorded 56 measurement values exercising all
13 input types (incl. all 4 new types) → CoC chronological report renders
69KB with all values incl. photo thumbnails.
Bugs found and fixed:
1. fp.process.node.input_ids missing copy=True — when a master recipe
was cloned per-part (the standard variant pattern), the operator
prompts on each step did NOT get copied to the variant. Result: jobs
built from variants ran with zero prompts even though the master had
them. Fixed: input_ids now copy=True so cloning auto-duplicates.
2. CoC chronological template read dest.input_ids where dest is
fp.job.step. Steps don't carry input_ids — that field lives on the
recipe node. Result: AttributeError aborted the entire CoC render.
Fixed: walk via dest.recipe_node_id.input_ids; preserves the existing
collect=True filter.
3. CoC chronological template used hasattr() in a t-value expression.
QWeb's expression engine doesn't expose Python builtins, raised
KeyError: 'hasattr'. Fixed: use 'collect' in i._fields instead.
Also enhanced photo rendering in CoC: was just "[Attachment]" placeholder;
now renders an actual <img> thumbnail (max 80px tall) plus the filename.
Battle-test script saved to fusion_plating/scripts/bt_e2e_anodize_v2.py
for re-runs / regression testing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implements 2026-04-29-step-library-audit-design.md. Bumps fusion_plating
to 19.0.18.7.0, fusion_plating_jobs to 19.0.8.12.0, fusion_plating_reports
to 19.0.10.2.0.
LIBRARY EXPANSION
- 8 new Step Kinds: Receiving, Electroclean, Strike, Salt Spray,
Adhesion Test, Hardness Test, Packaging, Tank Replenishment
- 4 new input types: photo, multi_point_thickness, bath_chemistry_panel, ph
- DEFAULT_INPUTS_BY_KIND rewritten to seed audit-grade prompts on every
kind (bath IDs, photos, multi-point thickness, signatures, etc.)
- + Common Audit Fields one-click button on the library template form
- Default Operator Instructions relabel + alert callout
PER-RECIPE CONFIGURABILITY
- collect (Boolean) per recipe-step input prompt — opt out without delete
- collect_measurements (Boolean) master switch on recipe step — when off,
wizard skips entirely
- template_input_id (Many2one) traceability link from recipe to library
- Recipe-step backend form view exposes the new fields with handle drag,
toggle, target range, and library-source column
RUNTIME WIRING
- Step input wizard filters node.input_ids to step_input AND collect=True;
short-circuits on collect_measurements=False
- New input types: photo (image widget + ir.attachment), multi-point
thickness (5 readings + auto avg, skips empty cells), bath chemistry
panel (pH/conc/temp/bath bundle), pH (0-14 numeric)
- Composite values JSON-serialized into value_text; photo via attachment
CoC REPORT
- Filters captured prompts to collect=True only
- Renders new input types with appropriate format
MIGRATION (post-migrate.py for 19.0.18.7.0)
- Backfills collect=True on recipe-step inputs
- Backfills collect_measurements=True on recipe steps
- Re-runs action_seed_default_inputs on every existing template
(idempotent, preserves user edits)
- Backfills template_input_id by name-matching against source library
template (handles JSONB vs varchar name columns)
SEED DATA
- 8 example templates (one per new kind) in fp_step_template_data.xml
with noupdate=1
BATTLE TEST
- bt_step_library_audit.py: 29 assertions all PASS on entech
OWL EDITOR EXTENSION DEFERRED
- The simple recipe editor's per-step Instructions/Measurements
expansions were not implemented in this pass; users configure via the
backend recipe-step form. Track follow-up.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Gap 1 — Rack Travel Ticket PDF (Sub 12b's Save+Print 404):
+ report_fp_rack_travel.xml in fusion_plating_reports — A5 landscape
single page, big rack name, Code 128 of FP-RACK:<name>, tag chips,
contained part-batches table.
+ ir.actions.report bound to fusion.plating.rack so it appears in
the rack form's Print menu too.
+ Sub 12b's rack_parts_dialog.js Save+Print URL fixed to use the
standard /report/pdf/<xmlid>/<id> route.
Gap 2 — Per-customer cert statement:
+ res.company.x_fc_default_cert_statement (company-level fallback).
+ res.partner.x_fc_cert_statement (per-customer override).
+ Surfaced on the partner form under the existing Cert + Document
Routing block.
+ Chronological CoC body resolves: customer override → company
default → hardcoded AS9100/ISO 9001 boilerplate. Three-tier
fallback so existing certs without overrides keep working.
Gap 3 — Chronological CoC 'Actual' column:
+ Build a captured_values_by_input dict from the move's
transition_input_value_ids (Sub 12b captures these on every
Move Parts commit).
+ Render typed Actual: text → as-is, number → with target unit,
boolean → PASS/FAIL, date → formatted, attachment → '[Attachment]'
placeholder.
+ Falls back to prompts from the destination step's step_input list
when no values were captured (still useful as audit-of-what-was-
asked even if blank).
Version bumps:
fusion_plating → 19.0.10.3.0
fusion_plating_reports → 19.0.10.1.0
fusion_plating_certificates → 19.0.5.3.0
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New template: fusion_plating_reports.coc_body_chronological.
Walks fp.job.step.move records in time order (chain-of-custody).
Per-move heading 'Step Name (Tank Code)' with 'Moved By / Time / Qty'
meta line + a 5-column measurement sub-table (Name / Description /
Target / Actual / Recorded By) when the destination step has captured
inputs. Heading-only when there are no inputs (gating moves).
New router template: coc_body_router. Picks chronological vs classic
based on fp.certificate.body_style. Existing certs default to 'classic'
so no regressions. Both English + French CoC templates rerouted.
fp.certificate.body_style ('classic' | 'chronological') exposed on
the cert form alongside certified_by_id. Operator picks per cert.
Sign-off block reuses the existing owner_user_id signature pattern +
x_fc_coc_signature_override fallback. Cert statement boilerplate is
inline (Sub 12d will move it to a configurable per-customer field).
The Actual column in the measurement sub-table is rendered blank
because Sub 12a/12b runtime captures step_input values via the
operator's per-step input form which lives in a model not yet wired
into this template — Sub 12d follow-up.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fusion_plating → 19.0.10.2.0 (Labor History views)
fusion_plating_jobs → 19.0.7.0.0 (Operator Traveller v2)
fusion_plating_reports → 19.0.10.0.0 (Chronological CoC body)
Adds data entries for the 2 new XML files (timelog views + coc
chronological).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After the fp.job migration, every MRP-bound Print action in
fusion_plating_reports has a fp.job-bound canonical version in
fusion_plating_jobs. Having both registered means clicking Print on a
record shows two identical entries.
Removed 7 ir.actions.report records (templates kept for backwards
compat — only the menu bindings are gone):
action_report_wo_margin (mrp.production)
action_report_fp_work_order_portrait (mrp.workorder)
action_report_fp_work_order_landscape (mrp.workorder)
action_report_fp_wo_sticker (mrp.workorder)
action_report_fp_mo_sticker (mrp.production)
action_report_fp_job_traveller_mo_landscape (mrp.production)
action_report_fp_job_traveller_mo_portrait (mrp.production)
Kept:
action_report_fp_job_traveller_so_* (sale.order)
action_report_fp_so_sticker (sale.order)
The shared inner sticker templates (report_fp_wo_sticker_inner /
_defaults) stay registered because fp.job + sale.order stickers
both t-call them.
Version: reports 19.0.7.17 -> 19.0.7.18.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Re-detect BarcodeDetector / window.jsQR at every modal open instead
of only at component setup. Avoids the trap where a stale cached
bundle reports "no decoder" even after a redeploy.
- Add a one-line status indicator at the top of the scan modal showing
exactly which decoder is active ("Decoder: native" / "Decoder: jsqr"
/ "Decoder: none — paste URL below"). Lets the operator see at a
glance whether scanning is even possible without round-tripping
through Safari Web Inspector.
- Sticker: strip a leading "Rev " (case-insensitive) from
fp.part.catalog.revision before printing so values like "Rev 1"
don't render as "Rev Rev 1".
Versions: shopfloor 19.0.18 -> 19.0.19, reports 19.0.7.16 -> 19.0.7.17.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two bugs were colluding to make iPhone scans look like "nothing
happens":
1. The in-app scanner was calling action.doAction({res_model: 'fp.job',
res_id: <decoded-id>}). Old physical stickers (still on every box)
encode /fp/wo/<mrp.production.id> — that id space doesn't match
fp.job, so the form opened on a non-existent record and silently
showed nothing. New /fp/job/<id> stickers happened to work because
the IDs lined up by coincidence.
2. The /fp/wo/<id> controller redirected to mrp.production / mrp.workorder
forms, both of which still exist as legacy records but aren't the
canonical source of truth post-migration.
Fix:
- qr_scanner._handleCode now navigates via window.location.href instead
of action.doAction. It hands /fp/job/<n> and /fp/wo/<n> URLs straight
to the existing server-side controllers, which know how to resolve
the right record. Bare numeric ids pasted manually -> /fp/job/<n>.
Anything else surfaces the decoded text as an error so the operator
can see decode worked but the value isn't a sticker.
- Modal now shows "Detected: <value>" the moment a code is decoded
(before navigation), so even on slow phones the operator sees
immediate feedback that the camera read the QR.
- wo_scan.py now resolves in this order:
1. fp.job by legacy_mrp_production_id (migration-aware — old
stickers route to the new model)
2. mrp.production direct browse
3. mrp.workorder direct browse
4. fall back to /odoo/plating-jobs (or work-orders list)
Versions: shopfloor 19.0.17.0.0 -> 19.0.18.0.0,
reports 19.0.7.15.0 -> 19.0.7.16.0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The original mrp.production / mrp.workorder sticker (logo + WO# stack
on the left, big QR on the right, 7-row body with PO/Customer/Process/
Part Number/Due/Qty/Notes — the design ENTECH has been printing for
months) lives in fusion_plating_reports.report_fp_wo_sticker_inner.
The new fp.job sticker had been rebuilt from scratch with a different
look. This wires fp.job into the existing canonical template instead.
What changed:
- report_fp_wo_sticker_inner — every t-set now uses the
"_var or fallback-from-_mo" pattern so callers can pre-resolve
values; mrp.production/mrp.workorder callers still work via the
fallback path.
- report_fp_wo_sticker_defaults — new shared template that initialises
every overridable name to False so the inner's `or` chain doesn't
NameError when an outer hasn't set it.
- report_fp_job_sticker_template — replaces the parallel layout with
a t-call to report_fp_wo_sticker_inner, feeding it from fp.job
fields (name, partner_id, qty, date_deadline, sale_order_id,
sale_order_line_ids, recipe_id, part_catalog_id, coating_config_id).
- report_fp_so_sticker — new outer that iterates sale.order.order_line
and emits one sticker per line that has a part_catalog_id. Bound to
sale.order's print menu via action_report_fp_so_sticker.
Versions: reports 19.0.7.14.0 -> 19.0.7.15.0,
jobs 19.0.5.0.0 -> 19.0.5.1.0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The QR <img> src used `% quote(_scan_url)` to URL-encode the value,
but `quote` isn't always in the QWeb render context — particularly
on the fusion_pdf_preview / account render chains. Result:
KeyError: 'quote' when printing from the browser.
_scan_url is always base_url + '/fp/wo/<int>' — no characters that
need encoding. Replaced the % + quote() formatting with simple
string concatenation, dropping the quote dependency entirely.
Smoke verified on entech: MO + WO stickers render cleanly at 27KB
each (with QR image included).
fusion_plating_reports → 19.0.7.2.0
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User reported two issues with the sticker:
1. "Print → WO Box Sticker" didn't appear on the MO form
(WH/MO/00067). The operator workflow lives on the MO form, not
the WO — binding only to mrp.workorder meant they couldn't see
the option. Now bound to BOTH:
* mrp.workorder (per-WO sticker)
* mrp.production (per-MO sticker — prints the MO friendly
name after "WO #" so it reads naturally in shop-floor
vocabulary)
Internal refactor: factored the layout into a shared inner
template report_fp_wo_sticker_inner; the two outer templates
normalise their input to the same _order_id / _scan_id / _mo
variables and t-call the inner.
2. Design polish. The previous layout was a plain label/value
table that looked rough. Redesigned with:
* Proper sticker chrome: 0.5mm black border, 1.5mm rounded
corners, edge padding.
* Header row with bottom border rule separating logo+WO-# on
the left from QR+caption on the right.
* Grid rows now alternate white / #f4f5f7 zebra-striping with
a right-aligned vertical rule between label and value.
* ALL-CAPS, letter-spaced, gray-333 labels at 7.5pt; values
at 8.5pt with strong (9.5pt, 700) emphasis on the key data
(PO, Part Number, Qty) so it reads at a glance from across
the warehouse.
* Helvetica Neue font stack.
* "SCAN TO OPEN" caption under the QR.
Scan endpoint updated: /fp/wo/<id> now tries mrp.production first
(operator home form) then falls back to mrp.workorder. Numeric
collisions between the two id spaces are possible; MO wins because
the MO view carries the full context.
fusion_plating_reports → 19.0.7.1.0
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Client is migrating from Steelhead and needs to keep the small
parts-box sticker format the warehouse crew already knows. Two
pieces shipped together so scanning is seamless from day one:
1. report_fp_wo_sticker — 4x3" QWeb label bound to mrp.workorder.
Layout mirrors the Steelhead sticker:
* ENTECH logo top-left (via env.company.logo)
* QR code top-right encoding /fp/wo/<id>
* Grid: PO (RO) / Customer / Process / Part Number / Due
Date / Qty / Notes
Dedicated paperformat_fp_wo_sticker at 102x76mm, 300 DPI,
landscape, 3mm margins — sized for thermal / inkjet label
printers without shrink-to-fit.
Binding added so "Print → WO Box Sticker" appears on every
mrp.workorder record.
2. FpWoScanController — GET /fp/wo/<int:wo_id> redirects the
scanner straight to the work-order form
(/odoo/action-mrp.action_mrp_workorder/<id>). auth='user' so
logged-in scanners land on the WO immediately; others bounce
through Odoo's login and return to the same URL. No custom
client work needed — any phone camera, handheld barcode
scanner, or tablet browser opens the URL on scan.
Process row resolution chain: part.default_process_id →
coating.recipe_id → fallback. So the sticker prints whichever
process is actually going to drive WO generation for this line,
matching the direct-order wizard's Effective Process column.
fusion_plating_reports → 19.0.7.0.0
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User request: on customer-facing PDFs the single "Part" column was
stacking part number + revision + description together. Split into
two distinct columns so customers see Part Number in its own field
and the Description column carries only the prose.
Macro split:
* customer_line_part_number — strong part number + "(Rev X)"
* customer_line_description — line.name + any populated line
metadata (serial, job#, thickness)
* customer_line_header (legacy) kept as a thin wrapper that
t-calls both macros stacked so older reports still render.
Reports updated — each gains a new first "PART NUMBER" column and
the old "PART" column renamed to "DESCRIPTION":
* report_fp_sale (both portrait variants)
* report_fp_invoice (both portrait variants)
* report_fp_packing_slip (both variants — delivery + MO-origin)
Column widths rebalanced per report. Section / note colspans bumped
to account for the extra column.
report_fp_bol left as-is — its "Description of Goods" td is a
freight-convention combined field, intentionally keeps the stacked
layout via the legacy customer_line_header macro.
fusion_plating_reports → 19.0.6.0.0
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Four new fields on every sale.order.line, propagated through to MO,
Delivery, and Invoice for end-to-end traceability:
- fp.serial registry (new model in configurator) with smart-button
traceability to Sale Order, MO, Delivery, Invoice, Part. M2O on SO
line; optional; user types a customer serial or clicks Generate
Serial for a sequence-backed one. Reverse O2M links split across
configurator (invoice) / bridge_mrp (MO) / logistics (delivery) so
module load order is respected.
- x_fc_job_number on SO line, auto-sequenced FP-JOB-NNNNN on SO
confirm. Editable — shops can override for customer/legacy schemes.
- fp.coating.thickness (new child of fp.coating.config) with per-
config discrete thickness options; x_fc_thickness_id on SO line
domain-filtered to the line's coating. Auto-clears when coating
changes.
- x_fc_revision_snapshot Char on SO line, frozen from
x_fc_part_catalog_id.revision at save. Protects historical SOs from
later catalog edits. Secondary "Revision" picker on the tree view
lets users switch between prior revisions of the same part number;
the Part M2O still surfaces only is_latest_revision rows.
Reports (CoC, packing slip, invoice, BoL) pick up all four via the
Sub 2 customer_line_header macro — one macro edit, four reports.
Smoke on entech: 11 assertions pass including revision snapshot,
generate-serial button, typed-serial create-on-fly, coating→thickness
domain reset, SO confirm auto job#, and MO traceability carry.
Module version bumps:
fusion_plating_configurator → 19.0.12.0.0
fusion_plating_bridge_mrp → 19.0.11.0.0
fusion_plating_logistics → 19.0.2.0.0 (+depends configurator)
fusion_plating_reports → 19.0.5.1.0
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Rewired portrait + landscape variants of report_fp_bol. The BoL had no
line collection of its own (fusion.plating.delivery only has a soft
`job_ref` Char), so the previous cargo-description block was a single
hardcoded row. Restructured to look up the job's mrp.production via
`job_ref`, iterate its `move_finished_ids` (excluding cancelled), and
render each finished-goods move through the shared
customer_line_header macro using the `move.sale_line_id or move`
adapter pattern.
When no MO is found or there are no finished moves, the template falls
back to the previous single-row "Plated parts — Job X" behavior so
legacy records without a backing MO still print correctly. Per-row QTY
now reflects the individual move's `product_uom_qty` instead of the
MO's aggregate `product_qty`.
Both variants render successfully on entech against a delivery whose
job_ref matches a real MO with one finished move.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Rewired portrait + landscape variants of report_fp_packing_slip to use the
shared customer_line_header QWeb macro. The packing slip iterates
stock.move records (doc.move_ids_without_package); the adapter
`<t t-set="line" t-value="move.sale_line_id or move"/>` bridges the macro's
`line.x_fc_part_catalog_id` lookup to the sale line when the move is tied
to a sale (preferred path), falling back to rendering the stock.move's
product_id for stray moves with no sale line.
SKU + PRODUCT columns collapsed into a single PART column (width
adjusted to absorb the removed SKU column). Both variants render
successfully on entech with a real picking whose move has a sale_line_id.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Invoice PDF (portrait + landscape) now collapses SKU + Description into
a single Part column rendered via fusion_plating_reports.customer_line_header,
so customer-facing invoices print the customer's part number (with
revision) instead of the internal service SKU.
To feed the macro on invoice lines, add x_fc_part_catalog_id to
account.move.line and override sale.order.line._prepare_invoice_line so
the part reference propagates automatically when an SO is invoiced.
Collapse the SKU and Description columns in both the portrait and
landscape sale-order PDFs into a single Part column rendered through
the shared customer_line_header macro, so customer-facing quotes and
confirmed orders print the customer's part number (with revision)
instead of the internal service SKU.
Updates column widths, section/note colspans, and the conditional
col_count used for the landscape template's optional discount column
to reflect the collapsed header.
Earlier I built report_fp_so_acknowledgement.xml as a separate
customer-facing document. On review there was no good reason — our
existing report_fp_sale.xml already flips its title between
"Quotation" and "Sales Order" based on state, and carried ~90% of
the same content. Two documents would have meant the shop had to
remember which to send when, and the customer would get two
near-identical PDFs in their inbox.
Consolidation:
1. Merged the four unique blocks from the acknowledgement into
report_fp_sale.xml (both portrait AND landscape variants):
- CUSTOMER JOB # / PLANNED START / CUSTOMER DEADLINE / SHIP VIA
info row (shown only when any of those fields is populated)
- Blanket / block-partial highlight-box callout (shown only
when the flags are set)
- External notes (x_fc_external_note) block above Terms and
Conditions
2. Deleted fusion_plating_reports/report/report_fp_so_acknowledgement.xml
and removed it from the module manifest. Also purged the orphan
ir.actions.report and ir.ui.view DB rows + the stale
ir.model.data entries.
3. Re-pointed the fp_mail_template_so_confirmed mail template's
report_template_ids from the now-gone acknowledgement report to
action_report_fp_sale_portrait. Updated hooks.py accordingly; the
hook now uses "set" semantics (replace all) instead of "add" so
re-running it cleans up stale attachments from prior refactors.
4. UAT on S00071: the Send button pre-selects the FP: Order
Confirmation template with SalesOrder_S00071.pdf attached. The
PDF renders with the new plating rows populated — Customer Job #
AMPH-2026-0420-01, Customer Deadline 05/14/2026 08:00:00 PM,
"Partial shipments blocked" callout, all lines + totals.
One PDF, one Send button behaviour, matching what Odoo and most
ERP systems do.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
D7 template was originally in fusion_plating_configurator with a
Bootstrap-only look-and-feel that didn't match the other Fusion
Plating reports. Re-styled and relocated:
- Moved to fusion_plating_reports/report/report_fp_so_acknowledgement.xml
alongside sale / work-order / job-traveller / invoice templates.
- Uses fp_portrait_styles (company primary colour for headers, .bordered
tables, .info-header row, .totals-table, .highlight-box, .sig-box /
.sig-line / .small-muted).
- Layout now mirrors report_fp_sale.xml: Billing / Shipping address
pair, references row (Customer PO / Customer Job / Order Date /
Salesperson), scheduling row (Planned Start / Internal / Customer
Deadline / Ship Via), blanket-order callout, order line table
(PART / DESCRIPTION / TREATMENT / QTY / UNIT PRICE / SUBTOTAL),
totals table with subtotal / taxes / grand total, and a two-column
signature block.
fusion_plating_configurator no longer ships report/ files — it
depends on fusion_plating_reports transitively via installed modules
order. Report XML ID changed from
'fusion_plating_configurator.report_fp_so_acknowledgement_doc' to
'fusion_plating_reports.report_fp_so_acknowledgement_doc'.
UAT on S00066: PDF renders cleanly with ENTECH branding, contact
footer, subtotal \$3,025 / taxes \$393.25 / grand total \$3,418.25,
signature lines — visually identical to the Quotation/Sales Order
report.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Users were seeing both Odoo's stock PDFs and FP's branded equivalents
in the Print dropdown side-by-side, and accidentally sending the wrong
(unbranded, missing PO# / job ref / plating fields) PDF to customers.
Add fp_hide_default_reports.xml that drops the Print-menu binding on:
| Model | Hidden | FP replacement |
|-----------------|-------------------------------------------------------------|---------------------------------|
| sale.order | sale.action_report_saleorder | action_report_fp_sale_* |
| sale.order | sale_pdf_quote_builder.action_report_saleorder_raw | action_report_fp_sale_* |
| account.move | account.account_invoices | action_report_fp_invoice_* |
| account.move | account.account_invoices_without_payment | action_report_fp_invoice_* |
| stock.picking | stock.action_report_delivery | action_report_fp_packing_slip_* |
| mrp.production | mrp.action_report_production_order | action_report_fp_job_traveller_*|
| account.payment | account.action_report_payment_receipt | action_report_fp_receipt_* |
Mechanism: set binding_model_id=False + binding_type=action — removes
from the Print dropdown but leaves the report record + template intact.
Fully reversible from Settings → Technical → Reports if anyone needs
the stock PDF back.
Intentionally NOT touched:
- sale.action_report_pro_forma_invoice (no FP pro-forma yet)
- account.action_account_original_vendor_bill (vendor bills, internal)
- stock.action_report_picking / picking_packages / return_label_report
(internal warehouse ops, not customer-facing)
- mrp.action_report_finished_product / mrp.label_manufacture_template
(production labels — ZPL, not customer-facing)
- sale_timesheet.* (timesheet integration)
Added sale_pdf_quote_builder to depends so the data file always finds
that record when applied (it ships in entech's repackaged enterprise
bundle and was already installed there).
Verified on entech: re-running the print-menu audit shows zero stock
Odoo customer-facing PDFs left where FP has an equivalent.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User reported "multiple unwanted vertical lines in the boxes" on the
portrait BoL. Pixel analysis confirmed it: previous design had 3
separate `<div class="sig-box">` each with its own 1px border, with a
4-8px gap between adjacent boxes — visually those adjacent borders
read as a doubled / "duplicate" line between cells.
Fix: replace 3-box layout with a single `<table class="bordered
sig-table">` containing 3 td cells. With border-collapse: collapse,
adjacent cells share their border — so the row now shows 4 vertical
lines (1 outer left + 2 internal dividers + 1 outer right) instead
of 6 close-together border lines.
- Dropped `.sig-box` class entirely (no per-box border anymore)
- Added `.sig-table` + `.sig-cell` with explicit 1px borders so the
layout works without depending on `.bordered` class inheritance
- Applied to both portrait + landscape variants
- Landscape sig-row was still using the OLD Bootstrap row+col-4
layout (never got replaced earlier) — also migrated to the new
table layout
Verified: page count unchanged (portrait 1, landscape 1), all
labels and content present, structure clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Last fix kept signatures intact but the landscape BoL still overflowed
to a second page (with the signature row pushed entirely to page 2).
The real ask was for the landscape variant to fit on one page since
landscape has plenty of vertical room.
Aggressive landscape compaction:
- Body font 11pt → 10pt, td font 10 → 9.5pt, th font 10 → 9pt
- Cell padding 8/10px → 4/8px
- Table margin-bottom 12px → 6px
- h2 title 26pt → 18pt with tighter top/bottom margins
- BoL # subtitle 14pt → 11pt
- Shipper/consignee row height 120 → 70px
- highlight-box (cert) padding 10px → 6/10, font 10 → 9pt
- sig-box padding 12 → 8/10px
- sig-line height 70 → 45px
Verified with pypdf: landscape BoL now renders as exactly 1 page
with cert + all 3 signature labels + company info all present.
137 KB clean PDF.
Portrait variant left untouched (it already fit on one page and
the bigger title is appropriate for portrait).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Last fix added page-break-inside: avoid but the boxes still split
because wkhtmltopdf 0.12 ignores that rule inside flex containers,
and BOTH the .sig-box (display: flex) AND the Bootstrap .row
wrapper were flex.
Replace both with non-flex equivalents:
- .sig-box: dropped `display: flex` + `flex-direction: column` +
`justify-content: flex-end`. Layout now uses padding + a fixed-
height .sig-line block + the muted label below. Same visual
result, but a plain block element so wkhtmltopdf honors the
page-break rule.
- Replaced `<div class="row">` + 3 `<div class="col-4">` (Bootstrap
flex grid) with a `<table class="sig-table">` containing one row
of three 33% tds. wkhtmltopdf treats table rows as atomic for
page-breaking, so the whole signature row now stays on a single
page.
Verified with pypdf: page 1 has the cert statement, page 2 has
all three signature labels together — no more sliced boxes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Landscape BoL was splitting the signature row down the middle —
boxes half on page 1, half on page 2. Two complementary fixes:
1. **Per-element rule**: added `page-break-inside: avoid` +
`break-inside: avoid` to `.sig-box` (both portrait + landscape
styles) so an individual signature box can never split across
pages.
2. **Wrapper rule**: introduced `.fp-keep-together` utility +
wrapped the BoL's certification statement + signature row in
it, so the whole "sign here" block moves to the next page as
one unit if it doesn't fit. Also applied
`page-break-inside: avoid` to `table tr` so cargo lines don't
split mid-row either.
Lives in shared `report_base_styles.xml` so any FP template that
opts into `.fp-keep-together` benefits automatically.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Five fixes applied to the Bill of Lading and (where relevant) all
report templates:
1. **Bigger title + BoL #** — portrait now uses h2 24pt (was h4 16pt),
landscape h2 26pt; BoL # ticker is 13/14pt instead of body size.
2. **Shipper info missing** — root cause: `_fp_build_delivery_vals`
was creating deliveries without `company_id`, so the BoL's
`<span t-field="doc.company_id.name"/>` rendered empty. Two fixes:
- Hook now sets `company_id = mo.company_id.id or env.company.id`.
- Template falls back defensively to `env.company` when
`doc.company_id` is empty (covers any legacy delivery that
somehow slips through without it).
- Backfilled 14 existing deliveries via SQL on entech.
3. **Uniform header backgrounds** — replaced mixed `info-header`
(gray) + default-th (brand black) headers with a single
`fp-header-primary` (brand black) across all sub-tables for a
consistent look.
4. **Cargo description alignment + missing column** — added a QTY
column (matches landscape variant), pulled from the linked MO
via job_ref → mrp.production.product_qty. Added `.fp-cell-mid`
utility class with `vertical-align: middle !important;` and
applied it to every cargo + info cell so values sit centred
instead of jammed against the top border.
5. **Signature box too short** — bumped `.sig-box` from 70 → 110 px
(portrait) / 130 px (landscape), `.sig-line` from 28 → 60/70 px,
added flex layout so the label sits at the bottom and signers
have a real space to write in. Lives in the shared
`report_base_styles.xml` so EVERY FP template benefits, not just
the BoL.
Verified: BoL portrait renders cleanly at 140 KB with full shipper
block + uniform headers + middle-aligned cargo cells.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Bill of Lading template assigned a temp variable
`<t t-set="dest" t-value="doc.delivery_address_id or doc.partner_id"/>`
and then tried `<div t-field="dest" .../>`. Odoo 19 QWeb asserts
t-field must be `record.field_name` (have a dot) — the temp variable
form fails compilation and the report renders as a multi-page
"Oops! Something went wrong" PDF stuffed with the traceback.
Fix: branch with `t-if`/`t-else` and call `t-field="doc.delivery_address_id"`
or `t-field="doc.partner_id"` directly. Same pattern in both header
and second-page-header sections (lines 49/235).
Verified: BoL render goes from 39 KB error page to 138 KB clean PDF.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three reported PDF bugs from the customer-facing email package:
1. Invoice body was empty — Odoo 19 sets display_type='product' on
regular invoice/SO lines (was empty string in 18.0). Both
report_fp_invoice.xml and report_fp_sale.xml only matched
`not line.display_type`, so every product line was skipped.
Fixed both portrait + landscape variants to also match
display_type == 'product'.
2. CoC PDF was a bare 30 KB header — _fp_generate_cert_pdf was
rendering action_report_coc, which is bound to portal_job and
has minimal content. Rewrote to use the rich fp.certificate-bound
report (action_report_coc_en / action_report_coc_fr based on
cert.partner_id.lang) and slugged the filename to
CoC-<Customer>-<CertName>.pdf so the email attachment reads
nicely instead of CERT-00123.pdf.
3. Thickness cert was an exact duplicate of the CoC — the CoC
template already embeds thickness readings. Skip thickness cert
creation entirely when the customer also wants CoC; only create
a standalone thickness cert when the customer opted out of CoC.
Also: dispatcher in fp_notification_template now prefers
portal_job.coc_attachment_id (the rich one we just generated) and
falls back to rendering action_report_coc_en against fp.certificate
by partner.lang — never the bare portal-job report.
Versions bumped: bridge_mrp 19.0.6.0.0, notifications 19.0.4.0.0,
reports 19.0.4.0.0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Database stores datetimes naive-UTC, but the dashboards and emails were
showing UTC strings to users in EST/EDT — making 9pm Toronto look like 1am
the next day. Adds a single helper module + auto-detection on install.
Core changes (fusion_plating):
- New fp_tz.py helper: fp_user_tz, fp_format, fp_isoformat_utc, fp_time_ago
Resolves user.tz → company.x_fc_default_tz → UTC.
- res.company.x_fc_default_tz Selection (full pytz IANA list)
- res.config.settings exposes the company tz under a new "Regional
Settings" block in Settings > Fusion Plating
- post_init_hook auto-populates the tz on first install: tries admin
user → server /etc/timezone → America/Toronto fallback
- fp_process_node._to_dict now sends create_date/write_date as ISO with
explicit +00:00 marker so JS new Date() parses it as UTC and the
recipe tree editor's "time ago" math works correctly
Shop-floor controllers:
- shopfloor_controller.py: every fields.Datetime.to_string() and naive
.strftime() swapped for fp_format(env, ...) — due_at, bake times,
last_log_date, gates, server_time all now in user's tz
- _time_ago() removed; replaced with fp_time_ago helper which compares
tz-aware datetimes (the local one was naive-vs-naive and could be
off by hours)
- manager_controller.py date_planned: str(...)[:10] slice replaced
with fp_format MM/DD in user's tz
Notifications + reports:
- mail_template_data.xml: 5 .strftime() calls in body_html → babel
format_datetime / format_date with tz=(user.tz or company tz)
- report_fp_job_traveller.xml: rec.received_date (Datetime) gets
t-options="{'widget':'datetime'}" so Odoo's QWeb renders in user tz
Settings view layout:
- fusion_plating now owns the Settings page "Fusion Plating" app shell
- fusion_plating_certificates xpaths into it instead of redefining
(prevents app-name collision)
Verified on odoo-entech (LXC 111): post_init_hook detects
America/Toronto from /etc/timezone, MO date_start 2026-04-17 05:28 UTC
correctly displays as 2026-04-17 01:28 EDT.
Module versions bumped: fusion_plating 19.0.3.0.0,
fusion_plating_shopfloor 19.0.9.0.0, plus certificates / notifications /
reports → 19.0.3.0.0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
One PDF that follows a job through the shop — prints from either the
Sale Order or the Manufacturing Order. Matches existing design language
(fp_landscape_styles, .fp-header-primary banners, bordered tables,
.sig-line for sign-off, .highlight-box for callouts).
Sections per traveller:
1. Title bar with REWORK / RUSH ORDER badges
2. Job header — customer, PO #, part #, coating, recipe, facility,
qty, dates, current parts location
3. Receiving summary — received qty, state, damage flag
4. Process Routing table — one row per WO with step #, operation,
work centre, bath, tank, target thickness, dwell, expected
duration, + sign-off columns (operator, date/time, initials,
qty pass/reject)
5. Bath chemistry targets snapshot per bath used
6. Quality holds — red callout only when present
7. Certificates issued + Delivery info (side-by-side)
8. Rework reason block (only on rework MOs)
9. Ruled notes / exceptions area
10. Final supervisor + QA sign-off
Four ir.actions.report entries registered:
- Job Traveller (Landscape) on mrp.production [default print]
- Job Traveller (Portrait) on mrp.production
- Job Traveller (Landscape) on sale.order [iterates MOs]
- Job Traveller (Portrait) on sale.order
Regression-tested all 15 existing reports (SO, WO, MO margin, invoice,
BoL, CoC EN, receipt) — every one still renders.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Moved doc.partner_id.image_1920 from a standalone right-aligned div
below the accreditation table to a third column (20% width, centre-
aligned) of the customer-info table — sits inline with Customer
Address (40%) and Contact Name/Email/Phone (40%). The customer
block is now a single bordered 3-column row.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Body was overlapping the company letterhead band — added padding-top
to .fp-coc so the title starts below it cleanly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per feedback, dropped the custom company-contact header and paperformat
in favour of Odoo's standard web.external_layout. This gives the CoC:
- Company-branded header (logo, name, address, phone, email, tax id)
matching whichever layout variant the company picked in
Settings → General → Document Layout (Standard / Boxed / Clean /
Striped). Automatically themed with company.primary_color.
- Consistent page X/Y footer + "Printed on" timestamp.
- Correct header_spacing so the letterhead band lines up with the
default paperformat.
Our body now owns:
- Centred "Certificate of Conformance" / "Certificat de Conformité"
- 3-column bordered accreditation table — one logo per cell (Nadcap,
AS9100D/ISO 9001, CGP) with equal 33.33% widths and #000 borders,
2.8cm cell height so logos centre vertically
- Optional customer logo (res.partner.image_1920) right-aligned
below the accreditation row
- Customer info block (name, address, contact, email, phone)
- Certification info table (date, generated-by, WO#)
- Quantities table (part, process, PO, shipped, NC qty, job no)
- Signature image + bordered cert statement
- "Fusion Plating by Nexa Systems" brand note
Template plumbing:
- Explicit `<t t-set="company" t-value="doc.sale_order_id.company_id
or doc.production_id.company_id or env.company"/>` in the EN/FR
wrappers because QWeb's t-call scoping doesn't expose variables set
inside external_layout to the body we pass through. Without this,
coc_body's `company.x_fc_owner_user_id` raises KeyError.
- Removed paperformat_fp_coc from the report actions (now uses the
default paperformat, which is designed for external_layout's
reserved header_spacing).
Verified: 332KB PDF, 1 page, all 5 images embedded, Amphenol logo on
right side of accreditation row, signature renders, company header
band at top, page footer at bottom.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The CoC was rendering on 2 pages with ~35mm of dead whitespace at the
top. Three compounding causes:
1. Default Odoo paperformat reserves header_spacing=35mm (where the
standard letterhead would sit when using web.external_layout). Our
CoC has its own full-bleed header so that reservation was pure
empty space.
→ New paperformat_fp_coc with header_spacing=0, 8mm all-around
margins, attached to both report_coc_en and report_coc_fr actions.
2. The `<div class="article o_report_layout_boxed">` and nested
`<div class="page">` wrappers inherited Odoo's CSS which applies
`page-break-after: always` on `.page` and additional padding on
`.article`.
→ Dropped both wrappers — template now renders body directly
inside html_container.
3. Inline style block didn't override Odoo's body/main padding.
→ Aggressive !important reset at the top of the style block on
html, body, main, .article, .page, and the hidden header/footer
classes. Also shrunk all paddings by ~30% and bumped base font
to 9pt to guarantee single-page fit.
Verified: PDF is now 1 page, content starts at the top (title flush
with top margin), accreditation logos + customer logo + signature all
render correctly within the single page.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Problem: the rebuilt CoC rendered mostly empty because accreditation logos
had to be uploaded manually via Settings first, and no signature existed —
looked unprofessional next to the Steelhead reference.
Fix:
- Seeder now auto-generates clean text-based accreditation badges with PIL
(Nadcap blue, AS9100D/ISO 9001 blue, CGP red) sized to match the
reference layout. Client can swap in real trademarked logos via Settings
→ Fusion Plating → Accreditation Logos at any time.
- Seeder creates a demo "Kris Pathinather" user, sets them as the
certificate owner on res.company, and renders a scripted-looking
signature image that matches the printed name on the cert.
- Seeder uploads a generated "Amphenol Canada Corp." badge to Amphenol's
res.partner.image_1920 so that customer's CoCs include their logo
on the top-right corner (mirrors how the reference shows it).
- coc_body template: guard hr.employee.signature access with a field-
exists check (the field is provided by an optional module not
installed on every Odoo).
- CoC uses web.html_container directly instead of wrapping in
web.basic_layout — the outer wrapper was injecting top padding that
pushed the title ~25% down the page. Now starts cleanly at the top.
- Tightened CoC CSS: removed unused label classes, added @page margin
directive, fixed vertical-align on header cells so logos and company
contact stay middle-aligned regardless of row height.
- Invoice PDF PAID stamp now also triggers on payment_state =
'in_payment', so historical demo invoices look paid without needing
full bank reconciliation.
Verified: renders a 152KB PDF with 5 embedded images, signer name
matches signature, all accreditation badges visible.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Demo seeder (scripts/fp_demo_seed.py):
- Idempotent Python script run via odoo shell; populates ~60 records
across 6 customer stories covering every workflow state for live demo
- Customers: Amphenol (net-terms, deep history), Magellan (progress
billing, active), Cyclone (deposit, in-production), Honeywell
(net-terms, just confirmed), Westin (COD, direct-order path),
Delinquent Industries (account hold — Confirm raises UserError)
- Coating configs with realistic AMS specs (2404, 2700 Rev G, 2406)
and bake-relief flags set on applicable processes
- Part catalog with revision chains (Rev 1 / Rev 2 / Rev 3 for hot parts)
- Customer price lists with volume tiers
- Per-customer invoice strategy defaults
- Bath chemistry logs (15 readings, last 2 OOS → pending replenishment
suggestion visible in menu)
- Racks: 4 active + 1 needing strip (MTO 3.2 / 3.0) for kanban demo
- Bake windows: 1 awaiting (ticking down), 1 baked, 1 missed (alert)
- Quote configurator sessions: 3 draft, 3 confirmed/won, 3 lost (with
reasons), 1 expired — populates the win/loss analysis
- Historical closed orders: 8 jobs backdated across 4 months with
SO → MO → Delivery → Invoice → Payment run through each hook so
portal-job progression, certificates with thickness readings, and
invoice AR aging all look real
- Active orders at every workflow stage for the live demo cycle
Polish:
- report_fp_invoice PAID stamp now also triggers on payment_state ==
'in_payment' (in addition to 'paid'). Odoo leaves payments in
'in_payment' until the bank reconciliation job matches them against
a statement line, so historical demo invoices would otherwise never
show as stamped even though the payment is posted and the customer
owes nothing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bug review fixes (found by code review + live QWeb error):
- report_fp_sale.xml: product_uom → product_uom_id (Odoo 19 renamed;
was raising KeyError during PDF render, blocking all sale-order prints)
- mrp_production.button_mark_done: add idempotency guard on delivery
auto-create (was duplicating on every re-close)
- fp.certificate._compute_batch_ids: use empty recordset instead of
False for Many2many computed fields
- fp_notification_template._collect_attachments: collapse attach_quotation
+ attach_sale_order into a single render so email doesn't double-attach
the same PDF
- fp.operator.certification: SQL unique on computed state was unreliable;
added explicit `revoked` boolean, made state pure-compute, replaced
SQL constraint with @api.constrains that checks active-only uniqueness;
has_active_cert now reads revoked + expires_date directly (no stale
stored state between nightly recomputes)
Two missing invoice strategies implemented + 1 pre-existing deposit bug fix:
- Progress Billing: new x_fc_progress_initial_percent field on sale.order;
_create_progress_initial_invoice bills the configured % on SO confirm
via down-payment wizard, _create_final_balance_invoice bills the
remainder on delivery
- Net Terms: no invoice on confirm; full invoice auto-created when
fusion.plating.delivery.action_mark_delivered fires
- Fix for deposit (pre-existing, silent): sale.advance.payment.inv
reads active_ids at wizard-create time, not on create_invoices();
context was being set on the wrong call, so every deposit attempt
raised "Expected singleton" and message-posted to chatter instead
of actually invoicing
- New fusion_plating_invoicing/models/fp_delivery.py hooks
action_mark_delivered to dispatch final invoice for progress/net_terms
- fp.direct.order.wizard + SO form surface the progress_initial_percent
field (conditional on strategy)
Report styling cleanup:
- Hide DISCOUNT column from sale + invoice landscape reports unless at
least one line has a non-zero discount; colspan auto-adjusts
- Replace hardcoded #0066a1 in all reports with company.primary_color
driven by doc.company_id → company → user.company_id fallback chain,
with #1d1f1e as ultimate fallback; new .fp-header-primary class
exposes the colour for inline section headers (CARGO DESCRIPTION,
PAYMENT DETAILS, OPERATOR SIGN-OFF, etc.) so they retint with the
company theme without template edits
Certificate of Conformance — formal ENTECH-style rebuild:
- New res.company fields: x_fc_owner_user_id (default signer, sig from
hr.employee.signature), x_fc_coc_signature_override (manual upload),
x_fc_{nadcap,as9100,cgp}_logo + _active toggles for accreditation
badges
- New res.config.settings section "Fusion Plating" exposing the above
as configurable blocks; manager-only menu under Configuration →
Fusion Plating Settings
- New fp.certificate fields: nc_quantity, customer_job_no,
contact_partner_id (child contact for Name / Email / Phone block)
- New report_coc_en + report_coc_fr templates (primary): custom header
(company contact | accreditations | company logo), bilingual labels
per variant, customer info block with customer logo, 3-column cert
info table, 6-column line-item table (Part # | Process | Customer
PO | Shipped | NC Qty | Customer Job No.), signature image + bordered
certification statement, footer "Fusion Plating by Nexa Systems"
- Legacy report_coc + report_coc_portrait kept for existing portal-job
bindings (no behaviour change)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>