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>
Phase 1 collapses the Plating app's 17 top-level menus down to 6
domains (Sales, Operations, Receiving & Shipping, Quality,
Compliance, Configuration) so users no longer scroll a 17-item
sidebar to find one thing.
Re-parented (no XML id changes — bookmarks still work):
- fusion_plating_compliance.menu_fp_compliance_root
→ menu_fp_compliance_hub (renamed 'General')
- fusion_plating_safety.menu_fp_safety_root
→ menu_fp_compliance_hub (renamed 'Safety / WHMIS')
- fusion_plating_aerospace.menu_fp_aerospace
→ menu_fp_compliance_hub (renamed 'Aerospace (AS9100 / Nadcap)')
- fusion_plating_nuclear.menu_fp_nuclear
→ menu_fp_compliance_hub (renamed 'Nuclear (CSA N299 / CNSC)')
- fusion_plating_cgp.menu_fp_cgp
→ menu_fp_compliance_hub (renamed 'Controlled Goods (CGP)')
- fusion_plating_certificates.menu_fp_certificates
→ menu_fp_quality (Certs are a Quality output, not a separate
top-level concern)
- fusion_plating_bridge_maintenance.menu_fp_maintenance
→ menu_fp_operations
- fusion_plating.menu_fp_job_step_move (Move Log)
→ menu_fp_operations
- fusion_plating.menu_fp_job_step_timelog (Labor History)
→ menu_fp_operations
The new menu_fp_compliance_hub is supervisor-gated; underlying
verticals retain their own group locks (CGP officer, etc.).
Settings menu remains manager-gated through inheritance from
menu_fp_config (already in place).
NEW — Plating landing-page resolver:
- ir.actions.act_window.x_fc_pickable_landing (Boolean tag for
curated picklist; flagged on Sale Orders, Quotations, Process
Recipes for Phase 1; more in Phase 2)
- res.company.x_fc_default_landing_action_id (admin sets fallback)
- res.users.x_fc_plating_landing_action_id (per-user override)
- ir.actions.server action_fp_resolve_plating_landing — picks
user → company → Sale Orders fallback at click time
- menu_fp_root rewired to call the resolver
- User profile + Settings tabs surface the dropdowns
Configurator's earlier menu_fp_root override (action_fp_sale_orders
direct) removed — core's resolver now owns the routing.
Versions bumped: fusion_plating 19.0.11.0.0, configurator
19.0.17.16.0, plus 7 dependent modules patched.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- fusion_plating: tank field labels (Code → Tank Number, Tank → Tank Name)
+ state-control header buttons (Mark Empty/Filled/In Use/Draining/
Maintenance/Out of Service) with chatter audit logging.
- fusion_plating_configurator: Plating app default landing screen = Sale
Orders, while keeping menu name as 'Plating'.
- fusion_plating_jobs: SO smart-button label 'Plating Jobs' → 'WO'.
Already deployed and verified on entech earlier in the session.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Customer feedback: some customers don't send their PO with the
initial order — they send it days or weeks later. The system was
blocking SO confirmation without a PO, which forced the shop to
either wait on paperwork or ask a manager for a formal override.
New estimator-level path: a "PO Pending" boolean on sale.order +
an optional "PO Expected By" date.
* Confirm without a PO# / PO document when PO Pending is ticked.
* action_confirm skips the hard error if po_pending OR po_override
is set (keeps the existing manager-override path too).
* On confirm with PO Pending, the system schedules a chase
activity for po_expected_date (or +3 days if blank), assigned
via mail.activity so it shows up in the sales user's activity
list. Chatter note logged so audit is obvious.
* Direct-order wizard: po_number and po_attachment_file become
optional. Ticking "PO Pending" in the wizard is the trade-in;
a help note under the toggle explains the chase behaviour.
* Once the PO arrives, user fills in the PO# / uploads the doc,
and turns PO Pending off — existing downstream flow resumes.
Difference from x_fc_po_override (kept):
* PO Override = manager waiver, permanent ("handshake deal").
* PO Pending = estimator flag, time-boxed ("customer will send it
by Friday").
fusion_plating_configurator → 19.0.14.0.0
fusion_plating_invoicing → 19.0.3.0.0
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Second attempt at widening the wizard modal after the :has() CSS
approach broke layout. This time using Odoo's built-in
dialog_size mechanism — just drop 'dialog_size': 'extra-large'
into the action's context and Odoo applies its own modal-xl
class, sized for wide content. About 30% wider than the default,
which gives the 10+ line columns breathing room without any
custom CSS.
fusion_plating_configurator → 19.0.13.6.0
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reverting the previous wizard-width attempt. The
`.modal-dialog:has(.o_fp_direct_order_wizard)` selector combined
with the fixed width override produced broken layout (modal no
longer rendering correctly).
Better path for widening this wizard is still TBD — the built-in
maximize icon in the modal header already lets users go full-
screen as a workaround until a safe width override lands.
fusion_plating_configurator → 19.0.13.5.0
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User complaint: the direct-order wizard's line table was crammed —
"Primary Treatment" showed as "Primary T..." and "Serial Number"
as "Serial Nu..." at the default Odoo modal width (~992px).
Added a dedicated stylesheet that targets the wizard via a new
o_fp_direct_order_wizard class on the form element. The modal
dialog containing the wizard now opens at min(1600px, 95vw) —
wide enough for the 10+ columns (Part, Primary Treatment, Process,
Thickness, Serial Number, Qty, Unit Price, Subtotal, Part Deadline,
WO Group) to fit without truncation on typical desktop displays,
while still adapting to narrower screens.
Also nudged per-cell horizontal padding from Bootstrap's default
to 10px left/right so the columns have a bit more breathing room
inside the wider table.
fusion_plating_configurator → 19.0.13.4.0
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two small fixes on the direct-order wizard:
1. User reported the Process column from the previous commit didn't
appear on the line list. Root cause: module view definitions
only reload on -u, not on systemctl restart. The earlier ship
bumped the version + ran -u, but the Process column inside the
list view required a fresh view load. Re-running -u now resolves
it (no code change needed for that field).
2. The "+ Add From Prior SO" and "+ Add From Quotes" buttons were
rendering edge-to-edge because their container div used the
default Bootstrap flow (margin-right: 0). Swapped the wrapping
div class from `mb-2` to `mb-2 d-flex gap-2` so Bootstrap lays
the two buttons side-by-side with a consistent 0.5rem gap.
fusion_plating_configurator → 19.0.13.3.0
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User asked why the direct-order wizard only showed "Primary
Treatment" and not Process — aren't they the same? They're not,
but the distinction was invisible on the order line until now.
Mental model (preserved here to keep future decisions aligned with
the user's question):
* Primary Treatment (fp.coating.config) = "WHAT coating" (process
type, thickness range, spec reference — the contractual
deliverable).
* Process (fusion.plating.process.node tree) = "HOW we make it"
(the floor-level sequence of operations and steps that WO
generation turns into work).
Each coating carries a default process (recipe_id). Parts can
override that via the Process Composer, storing a part-scoped
clone (default_process_id on fp.part.catalog). Resolution:
part's composed process wins; coating default is the fallback.
Added a computed read-only `effective_process_id` field on
fp.direct.order.line that displays exactly what process will drive
WO generation for the line, plus a one-line `effective_process_source`
showing whether it came from the part ("customised") or the
coating ("default"). Both surfaced on the wizard list and form so
the estimator can verify before confirming the order.
No behaviour change — this is pure visibility. WO generation still
uses the same resolution chain it did before.
fusion_plating_configurator → 19.0.13.2.0
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two user-reported gaps:
1. The Process Recipes list was about to be flooded by part-scoped
clones as soon as parts started carrying customised processes —
thousands of clones would bury the handful of real shared
templates (General Processing, Anodize, etc.).
Fix: the main Process Recipes action now narrows to
part_catalog_id = False so shared templates stay alone in that
view. A sibling menu "Part Processes" (Plating → Operations →
Part Processes) shows the inverse list — every part-cloned
process, grouped by part — so admins can audit clones without
cluttering the templates list.
Search view gains two toggle filters (Shared Templates /
Part-Scoped) and a "Group by Part" option. The node list gains
an optional "Part" column.
Split across modules: core owns the base search / tree / action
(unchanged); configurator owns all the part_catalog_id-dependent
pieces (filter extensions, list column, narrower domain,
"Part Processes" action + menu). Keeps the dependency direction
clean — configurator always depends on core, never the other way.
2. Added a "Process" smart button to the part form's button box.
Shows either a green check (composed) or "None" (not yet
composed) and opens the part-scoped Composer on click. Gives
users one-tap access from any part form without hunting through
the Process tab.
fusion_plating → 19.0.8.0.0
fusion_plating_configurator → 19.0.13.1.0
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User feedback: the Process tab on fp.part.catalog was displaying a
bare template name ("General Processing"), making it impossible to
tell at a glance that the clone belonged to this specific part.
Root clone now inherits the template name with a part-identifier
suffix appended:
"General Processing — 1234567 Rev 2"
Only the ROOT gets the suffix — child nodes keep their clean source
names so the tree-editor canvas doesn't get cluttered.
Suffix logic avoids doubling "Rev": if part.revision is already
prefixed with "Rev " (e.g. "Rev 2"), we don't prepend another one.
Post-upgrade hook backfills existing part-cloned roots that
pre-date this change so users see the new format without having
to re-compose (which would otherwise wipe their customisations).
fusion_plating_configurator → 19.0.13.0.0
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two separate issues were stacking up on the breadcrumb trail:
1. The composer was launching the tree editor with the name
"Process Composer — …" — identical to its own label. The
breadcrumb trail ended up showing "Process Composer / Process
Composer" because both pages claimed the same name. Renamed the
tree-editor instance to "Process Editor — …" so the two pages
read distinctly.
2. Every "Back to part" click pushed a new entry onto the
breadcrumb stack instead of resetting. After one round-trip the
trail looked like "… / Composer / Editor / Part"; after two it
was "… / Composer / Editor / Part / Composer / Editor / Part".
Added clearBreadcrumbs: true to both back paths (composer's
backToPart and tree-editor's onBackToList) so a RETURN action
actually resets the stack.
fusion_plating → 19.0.6.2.0
fusion_plating_configurator → 19.0.12.4.0
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Icon + label now share an inline-flex row with align-items: center,
so both sit on the button's vertical midline regardless of line-
height inheritance.
- Dedicated gap (10px) replaces the me-2 utility so the _hint block's
.fa { margin-bottom: 16px } can't bleed in and push the icon off.
- Icon colour forced to #ffffff to match the button's white label —
contrast against btn-primary green is now clean.
fusion_plating_configurator → 19.0.12.3.0
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Icon: fa-edit → fa-sitemap (pencil suggests inline editing; the
button actually navigates to a separate tree editor — sitemap
matches the intent).
- Icon spacing: swap the fragile leading-space-in-span for the
Bootstrap me-2 utility so the icon-to-text gap is consistent.
- Label: "Open Tree Editor" → "Open Process Editor" to match the
page title ("Process Composer") and the menu vocabulary the rest
of the product uses.
fusion_plating_configurator → 19.0.12.2.0
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Process Composer was rendering a white card background on dark-mode
tenants because the SCSS relied on var(--bs-body-bg, #ffffff) — the
Bootstrap CSS variable doesn't flip reliably in Odoo 19's backend,
and the hardcoded fallback kicked in.
Rewrote with the shopfloor token pattern from CLAUDE.md: branch on
$o-webclient-color-scheme == dark at compile time, expose via CSS
custom properties (--fp-composer-card / -border / -panel / -text /
-muted), drop every --bs-* fallback for layout colours.
Cleared the ir_attachment asset cache on entech so both the backend
and web_dark bundles recompile cleanly.
fusion_plating_configurator → 19.0.12.1.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>
- Add fp_part_composer_controller with 3 JSON-RPC endpoints:
/fp/part/composer/state, /fp/part/composer/templates,
/fp/part/composer/load_template (deep-clones a shared template
into a part-owned tree inside a cr.savepoint, sets
fp.part.catalog.default_process_id atomically)
- _clone_subtree copies name/sequence/opt_in_out/treatment_uom plus
description/notes/icon/color/timing/behaviour/work_center/process_type
and stamps part_catalog_id + cloned_from_id on every node
- Add fp_part_process_composer OWL client action (JS + XML + SCSS):
picks template from dropdown, clones, hands off to existing
fp_recipe_tree_editor via context={recipe_id, part_id}
- Add Process tab on part form with readonly default_process_id
field and Compose button calling action_open_part_composer
- Register new assets in web.assets_backend, bump configurator
version to 19.0.11.0.0
The wizard was calling so.action_confirm() immediately after creating the
sale order, which flipped it from draft to sale state and triggered the
fusion_plating_notifications hook that auto-emails the customer.
Client wants a review step: keep the SO in quotation (draft) so the
user can adjust before the customer sees anything. They manually click
Send (to email the quotation) or Confirm (to convert to sale order,
which intentionally fires the confirmation email).
Changes:
- Remove so.action_confirm() call in action_create_order
- Update docstring + inline comment to reflect manual-confirm flow
- Update the chatter message on the created SO
CLAUDE.md updated to mark Sub 1 + Sub 2 as Shipped.
Verified:
- Static check: wizard.action_create_order contains no action_confirm
- Dynamic check: SO created programmatically stays in draft
- Manual action_confirm() flow still works as designed
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When Sub 2 Task 26 flipped x_fc_internal_description to required=True,
any programmatic sale.order.line creation that doesn't set the field
fails at the Postgres NOT NULL constraint. Callers include:
- sale_mrp stock-move line creation (doesn't set name either)
- demo seeders
- external integrations
- test scripts
The UI-side onchange populates the field when the user picks a
description template; this hook mirrors that for programmatic callers.
Fallback chain: explicit vals['x_fc_internal_description'] → vals['name']
→ product_id.display_name → '—'. Matches the migration's backfill rule.
Also adds Sub 2 end-to-end smoke test (6 cases, all green):
1. Required-field rejection on part creation
2. Required-field rejection on template creation
3. Template picker populates both SO-line descriptions
4. Cert resolver: part-level override wins over partner
5. display_name renders part_number + revision + name
6. certificate_requirement defaults to 'inherit'
QC Phase 1-3 regression suite remains green after the fix.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Removes the `description` field from `fp.sale.description.template` now
that all readers (reports, wizard, sale line) consume the new
`internal_description` + `customer_facing_description` pair.
- Model: drop `description = fields.Text(...)` declaration
- Migration 19.0.9.0.0 Step 6: `ALTER TABLE ... DROP COLUMN IF EXISTS description`
- Template form/search views: swap `description` for the two new fields
- Seed data: write new fields instead of legacy column (dupes old text into both)
- Direct-order wizard: remove `tpl.description` fallback in both onchange handlers
Entech column dropped via Odoo's auto-schema-sync during module upgrade
(migration step is for fresh installs).
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.
Adds an `internal_description` text field to the direct-order wizard
line so the shop-floor copy is captured at order entry alongside the
customer-facing text. Picking a template now fires both sides of the
onchange: `line_description` gets `customer_facing_description` (with
fallback to the legacy `description` field for backward compat) and
`internal_description` gets the template's internal text. The
auto-suggest onchange was refactored around a tiny `_apply` helper so
all three fallback paths populate both fields consistently.
The template picker is surfaced as an optional column on the wizard
list (hidden until a part is chosen, domain-scoped to that part) and
as a dedicated labeled row in the per-line form. The internal text
field is also surfaced in the form under "Line Description" so the
estimator can review / edit it before confirm. On create_order, both
`x_fc_description_template_id` and `x_fc_internal_description` are
written through to the generated sale.order.line so the audit trail
and WO printout stay linked.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Surfaces the per-part description template on the SO line list alongside
a hidden-by-default internal description column. Picking a template
fires an onchange that copies `customer_facing_description` into Odoo's
standard `name` (customer-visible) and `internal_description` into
x_fc_internal_description (shop-floor / WO only). Estimator can edit
either field after the template is applied.
The template picker's domain filters by the line's part, and the field
stays hidden until a part is chosen — avoids showing every global
template when the line is blank.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Five fixes from the end-to-end UAT debrief:
1. Menu discoverability (HIGH)
Added a prominent "+ New Direct Order" button in the Sale Orders
list header toolbar (class=btn-primary, display=always). The
existing menuitem at Plating > Sales > New Direct Order was
buried in a submenu that didn't always expand; the toolbar
button is a guaranteed entry point from the most common screen.
2. Escape/X destroys wizard state (HIGH)
Added a prominent info banner at the top of the wizard form:
"Changes are not saved until you click Create & Confirm Order.
Closing this window (Esc or X) discards your entries." The
Cancel button now has confirm="Discard this order? All header
data and line items will be lost." so the intentional-cancel
path also prompts.
3. Shell/cron crash in _fp_auto_create_mo (MEDIUM)
bridge_mrp/models/sale_order.py:232-264 used _() inside list
comprehensions to format the internal chatter summary of newly
created / adopted MOs. _() resolves language from env.context,
which is empty in odoo-shell and cron contexts — triggering a
translate.get_text_alias crash AFTER the MOs had been created.
These strings are internal audit log text, not user-facing UI;
dropped the _() wrappers so the message builds safely from any
context. Same for the per-group error-message on savepoint
rollback.
4. Misleading "100%" margin (MEDIUM)
x_fc_margin_percent displayed 100% on every SO because the cost
rollup from fp.coating.config.unit_cost isn't populated yet.
Added x_fc_margin_available Boolean (True only when at least
one line's coating has a non-zero unit_cost). The SO Plating
tab now hides the margin numbers when margin_available=False
and shows an inline muted note: "Margin n/a — coating cost
rollup not yet populated on any line's treatment."
5. Account Hold banner too loud (LOW)
fusion_plating_invoicing was injecting a full-height danger
alert above every SO header. Slimmed it to a one-line compact
alert with icon: "Account Hold — SO confirmation, invoicing
and shipping are blocked for non-managers." Half the vertical
footprint, less visual competition with the Plating chip bar.
Verified via UAT on S00071.
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>
Acknowledgement PDF rendering failed with "QWeb widgets do not work
correctly on 'td' elements" — Odoo's qweb compiler rejects
t-field/t-options directly on <td>. Wrap the monetary / qty widgets
in an inner <span> for every cell that uses them (body rows + footer
total).
Caught during browser UAT on S00066 — shell _render_qweb_pdf smoke
test passed earlier because it bypasses the full compile path, but
the production /report/pdf/ endpoint fails the assertion.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase D8 compute was returning x_fc_margin_percent already-multiplied
by 100, but the 'percentage' widget in the SO form multiplies again
for display. Result was 10000% instead of 100%.
Store as 0.0-1.0 fraction; widget handles the multiplier. Caught
during UAT on S00066.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>