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>
Tier 2 — Quality & audit readiness:
- T2.1 SPC on thickness readings (fp.certificate)
- spec_min_mils / spec_max_mils auto-pulled from coating config on create
- Computed: std_dev_mils, min/max, cpk, cpk_status (incapable/marginal/
capable/excellent/insufficient)
- Western Electric trend rules (rule 1: any point beyond 3σ; rule 4:
8 consecutive on one side of mean) → trend_alert + explanation
- New SPC group on certificate form with badge-coloured indicators
- T2.2 Operator certification enforcement (fp.operator.certification)
- Per (employee, process_type) records with issued/expires dates,
training record attachment, revocation workflow
- State auto-computed: active → expired when date passes
- MrpWorkorder.button_start() blocks with UserError if current user's
linked hr.employee lacks an active cert for the bath's process_type
- Managers bypass the check; expiring-soon filter in search view
- HR Employee form: "Plating Certifications" tab
- T2.3 Material traceability chain
- fusion.plating.batch.workorder_id (new Many2one) + production_id
(related through WO) for full chain
- fp.certificate gets computed batch_ids / bath_ids / batch_count
- "Batches" stat button → list of batches used for this cert's MO,
with their chemistry logs intact
- T2.4 Pre-treatment as first-class baths
- process_family selection on fusion.plating.process.type
(pre_treatment / plating / post_treatment / bake / strip / passivation /
masking / inspection)
- Bath search view: Pre-Treatments / Plating / Post-Treatments / Strip
quick filters
- Existing bath infra (logs, replenishment, SPC) now applies to pre-
treatment baths equally
Tier 3 — Business / revenue:
- T3.1 Customer-specific price lists (fp.customer.price.list)
- Per (customer, coating_config) with unit_price + basis (per_part /
sqin / sqft / lb)
- effective_from / effective_to for annual contract pricing
- min_quantity for volume breaks (cheapest price at requested qty wins)
- _find_price() helper resolves active entry by date + qty
- Direct Order wizard auto-fills unit_price on (partner, coating, qty)
change unless operator has typed an override
- Configurator menu → Customer Price Lists
- T3.2 Quote win/loss tracking (fp.quote.configurator)
- State values: draft → confirmed (won) / lost / expired / cancelled
- lost_reason selection (price / lead_time / tech / spec_mismatch /
no_bid / no_response / competitor / other) + lost_competitor_name
+ lost_details text
- Action buttons: Mark as Lost (requires reason), Mark as Expired
- won_date auto-set on SO creation; lost_date auto-set on mark_lost
- New "Win / Loss" tab on configurator form
- T3.3 Actuals vs. quoted margin (mrp.production)
- Computed monetary fields: x_fc_consumables_cost, x_fc_labour_cost,
x_fc_actual_cost, x_fc_quoted_revenue, x_fc_margin_actual,
x_fc_margin_pct
- Labour = sum(WO duration × workcentre cost_hour)
- Revenue = SO amount_untaxed via mo.origin lookup
- New "Job Costing" group on MO form with badge-coloured margin
- T3.4 Job consumables tracking (fp.job.consumption)
- One row per consumable event (bath replenisher, masking tape, PPE,
chemistry): product, qty, uom, unit_cost (snapshot), total_cost,
source, optional workorder link
- One2many x_fc_consumption_ids on mrp.production
- "Consumables" stat button on MO → filtered list
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three-step self-service quoting flow on the customer portal:
- Step 1: Upload part (STL/PDF) or enter manual measurements + material
- Step 2: Select coating config from card grid with specs and thickness
- Step 3: View estimated price range and submit quote request
Adds dependency on fusion_plating_configurator for fp.coating.config
and fp.pricing.rule models. Price estimation uses the same rule-matching
logic as the backend configurator with a +/-15-25% range.
Dashboard updated with prominent "Get a Quote" button and portal sidebar
entry. Breadcrumbs added for configurator pages.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add OWL field widget (fp_3d_preview) that renders uploaded STL files
in an interactive 3D viewport:
- Three.js r170 ESM loaded lazily via dynamic import with importmap
- STLLoader + OrbitControls for full model interaction
- Fallback binary STL parser when addon import fails
- Toolbar with wireframe toggle and camera reset
- Vertex/face count display
- Theme-aware SCSS using CSS custom properties and $border-color
- Registered on model_attachment_id in the Part Catalog form
Vendored libs: three.module.min.js (691KB), STLLoader.js, OrbitControls.js
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add trimesh-based surface area calculation for uploaded STL files:
- New /fp/configurator/calculate_surface_area jsonrpc endpoint
- action_calculate_surface_area() method on fp.part.catalog
- "Calculate from 3D Model" button visible when a 3D model is attached
- Returns area in sq in (converted from mm2), vertex/face counts,
and bounding box dimensions
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove x_fc_sync_source, x_fc_is_shadow, x_fc_sync_client_name,
x_fc_source_label references from form/list/kanban/calendar XML
views and the map view JS component.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove cross-instance sync fields (x_fc_sync_source, x_fc_sync_remote_id,
x_fc_sync_uuid, x_fc_is_shadow, x_fc_sync_client_name, x_fc_sync_client_phone,
x_fc_source_label), sync push calls in create/write/action methods, shadow
task logic in constraints and email guards, and x_fc_tech_sync_id from
res.users. Also remove task_sync import from models/__init__.py.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add _generate_workorders_from_recipe() which walks the recipe tree,
creates one mrp.workorder per operation node, and formats child step
nodes as plain-text WO instructions. Respects opt-in/out overrides
from the per-job configuration wizard. Called automatically at the end
of action_confirm() after portal job creation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
category_id is not valid on res.groups in Odoo 19.
Use privilege_id + sequence matching core module pattern.
Also remove user_ids (CLAUDE.md rule).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix thickness factor: now scales linearly (thickness * factor), not
multiplicatively. Default factor=1.0 means price scales 1:1 with mils.
- Fix batch_size: setup fee now multiplied by ceil(qty/batch_size) batches
- Fix hardcoded $ in price breakdown HTML: uses currency_id.symbol
- Add coating_config_id.certification_level to @api.depends
- Remove readonly on x_fc_receiving_status (placeholder until receiving module)
- Add currency_id to treatment list view for Monetary widget
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add the core configurator model that collects part geometry, coating
config, and pricing inputs, calculates a price from matching pricing
rules (scored by specificity), and creates sale orders on confirmation.
- fp.quote.configurator model with mail.thread, sequence numbering
- Stored computed price with full breakdown HTML table
- Estimator override price support
- Auto-population from part catalog and coating config onchanges
- Surface area normalization (sq in/ft/cm/m)
- Specificity-scored rule matching (coating > substrate > cert level)
- action_create_quotation creates SO with FP-SERVICE product
- Form/list/search views with statusbar and chatter
- ACL: operator (read), estimator (read/write/create), manager (full)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>