The customer's Purchase Order is the doc they send US — a separate
artifact, often a PDF attachment on the quote. What lives in our
system is the Sales Order we create in response. Labeling the SO
list as "Purchase Orders" in the customer portal was a wrong-side
mapping.
Reverts and renames in this commit:
- Sidebar item label: "Purchase Orders" → "Sales Orders" (key stays
odoo_orders; URL still /my/orders). _FP_SIDEBAR_LAYOUT.
- Dashboard KPI tile: "Active POs" → "Active Sales Orders". Link
hint: "View POs →" → "View orders →". Link target updated to the
current /my/orders (the legacy /my/purchase_orders still redirects
but we point at the canonical URL now).
- Dashboard panel: "Recent Purchase Orders" → "Recent Sales Orders".
Empty state: "No purchase orders yet." → "No sales orders yet."
View-all link target updated to /my/orders.
- Dashboard docs entries strip: "Purchase Orders" docs entry title
→ "Sales Orders"; URL → /my/orders.
- Removed the three Odoo template rename inherits from
fp_sale_order_portal.xml (sale.portal_my_home_menu_sale,
sale.portal_my_orders, sale.sale_order_portal_content). With those
gone the stock templates emit Odoo's native "Sales Order(s)" and
"Your Orders" wording on the list page header, breadcrumb, and
detail page <h2> — which is now the correct terminology.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1. Odoo's portal_order_page route calls _get_page_view_values which
doesn't touch _prepare_portal_layout_values, so our sidebar
context (fp_sidebar_items, fp_partner_display_name) was missing
on every Odoo detail page (SO, invoice, delivery, quote). Override
_get_page_view_values to setdefault our two keys into the values
dict — non-clobbering, covers every detail route.
2. Rename "Sales Order(s)" / "Your Orders" to "Purchase Order(s)" on
the customer portal so the wording matches the sidebar item and
the customer's perspective (they purchase from us). Inherits in
fp_sale_order_portal.xml replace the relevant text nodes in
sale.portal_my_home_menu_sale / sale.portal_my_orders /
sale.sale_order_portal_content.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
orders|length in t-value parses as orders | length, not as a Jinja
length filter. orders is a sale.order recordset; the `length`
identifier resolves to None; Python evaluates
recordset | None and raises TypeError. Use len(orders) instead.
Also documents the gotcha in CLAUDE.md (rule 19) so future templates
don't reach for Jinja-style filters in t-value.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- views/fp_sale_order_portal.xml: new template inherit
portal_my_orders_fp_search on sale.portal_my_orders. Injects the
fp_portal_list_controls strip before the "no orders" alert. Filter
pills + sort dropdown are disabled here (we don't own the route,
Odoo's sortby is preserved separately). The search input is wired
to .o_portal_my_doc_table tbody (the table class Odoo's
portal.portal_table emits) so real-time keyword filtering works
without needing to monkey-patch the stock route or template.
- CLAUDE.md: documents two conventions surfaced by the recent portal
work:
Rule 17 — test scaffolding for account.move creation must use
with_context(fp_from_so_invoice=True) and pass
invoice_payment_term_id, to satisfy custom gates in
fusion_plating_jobs and fusion_plating_invoicing.
Rule 18 — FP portal list pages don't paginate. They load up to
500 records and rely on fp_portal_list_search.js to filter
client-side. Hidden <td class="d-none"> cells per row carry
extra searchable text.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the tab nav / portal.portal_searchbar on the 4 FP list
pages with the new fp_portal_list_controls macro (filter pills +
search input + sort dropdown) and drops portal_pager in favour of
client-side filtering of up to 500 records:
- Quote Requests (/my/quote_requests):
filters: All / Active / Converted / Declined
sorts: Newest / Reference / Status
extra search fields: contact_name, contact_email, line.part_number,
line.description, line.product_id.default_code
- Work Orders (/my/jobs, cards layout):
filters: All / Active / Ready to Ship / Complete
sorts: Newest / Reference / Status
extra search fields per card: part_catalog.part_number, part_catalog.name,
sale_order.name, sale_order.client_order_ref,
job.notes
- Certifications (/my/certifications):
no filters (all rows are terminal CoC jobs)
sorts: Newest / Reference
extra search fields: part name, processes (already in card text)
- Packing Slips / Deliveries (/my/deliveries):
no filters (all rows are state=done)
sorts: Newest / Reference
adds a visible Origin column (sale order ref) so customers can
locate a slip by the SO it came from
Each route accepts ?filter_state=... and ?sortby=... query params,
returns up to 500 records, and passes result_total + clipped to the
template so the macro can render a "showing latest 500 of N" notice
when the cap is hit.
Hidden <td class="d-none"> cells inside each row carry extra terms
that aren't displayed but are matched by the JS textContent scan.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the shared infrastructure for real-time multi-keyword search on
portal list pages:
- static/src/js/fp_portal_list_search.js — vanilla-JS IIFE that wires
every input.o_fp_list_search to the container at the selector in
its data-fp-target. On every keystroke, walks the container's
direct children and toggles display: none based on whether each
row's textContent contains all whitespace-tokenised keywords. Also
wires .o_fp_sort_select dropdowns on every page EXCEPT Account
Summary (scoped by .o_fp_account_summary closest-ancestor check) so
the existing fp_portal_account_summary.js handler isn't doubled up.
- views/fp_portal_macros.xml — new t-call macro
fusion_plating_portal.fp_portal_list_controls that renders the
filter pills + search input + sort dropdown strip in one block.
Callers pass filters, sorts, active_filter, active_sort, search,
url, extra_qs, target, result_total, clipped via t-set.
- __manifest__.py — registers the new JS in web.assets_frontend
(after fp_portal_account_summary.js). Version bumps 19.0.4.0.0 ->
19.0.4.1.0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previous attempts (e50631c, 6f2bea9) zeroed .container's pt-3 and the
first child's mt-3, but the right column was still sitting ~32px lower
than the sidebar. Reason: Bootstrap 5 ships .pt-3 and .mt-3 as
margin-top: 1rem !important / padding-top: 1rem !important. My
overrides without !important lost the cascade and never took effect.
Match Bootstrap's specificity by adding !important on both rules.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Odoo stock routes (/my/orders, /my/invoices, etc.) call
portal.portal_searchbar with breadcrumbs_searchbar=True, which made
portal.portal_layout suppress its outer breadcrumb container — the
breadcrumb then rendered inside the searchbar nav, which lives inside
our shell's <main> and showed up in the right column. We can't edit
the stock route handlers, so override portal.portal_layout in
fp_portal_shell to ignore breadcrumbs_searchbar (still respect
no_breadcrumbs and my_details). CSS-hide the now-duplicate inline
breadcrumb inside .o_portal_navbar so we don't show two trails.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
t-field requires a record.field_name access pattern. open_balance is a
Python float (returned by _fp_account_summary_open_balance), not a
recordset attribute, so QWeb threw AssertionError at render time and
the page 500'd. Format the value in the controller via tools.formatLang
and render it as a plain string with t-out instead.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Many FP templates slap mt-3/mt-4 onto their root content div (dashboard,
configurator wizard steps, etc.) which still pushed the right column's
content ~16px below the sidebar's top edge even after pt-3 was zeroed
in e50631c. Scope a margin-top: 0 to .o_fp_portal_main #wrap > .container's
first child — strips whichever utility class the template happens to use
without touching siblings or styles below.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Odoo's portal_layout wraps page content in <div class="container pt-3 pb-5">.
The pt-3 (1rem) was pushing the right column's first visible content ~16px
below the sidebar card's top edge, so the two column corners looked
misaligned. Zero out the top padding on that inner container, scoped via
.o_fp_portal_main #wrap > .container so it only applies inside our shell.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three coordinated portal-chrome fixes:
1. Drop `breadcrumbs_searchbar=True` from the four list templates
(quote_requests, jobs, deliveries, certifications). They were
suppressing Odoo's outer breadcrumb container, so the breadcrumb
rendered inside portal.portal_searchbar in the right column on
those pages. With the flag off, the outer container fires on
every /my/* page (consistent with the dashboard, configurator,
and detail pages). The portal_searchbar's else-branch now renders
the page title in a Bootstrap navbar — the title still shows,
just no longer doubled up as breadcrumb chrome.
2. Breadcrumb history pass in fp_portal_breadcrumbs.xml:
- fp_jobs / fp_portal_job: rename label from "Parts Portal" to
"Work Orders" so the breadcrumb matches the sidebar item.
- fp_purchase_orders / fp_invoices: drop the dead stanzas. Both
page_names are unreachable since Task 7 turned those routes
into redirects.
- fp_account_summary: add the missing entry so the new page has
a trail.
3. Drop `align-items: start` on .o_fp_portal_shell and add
min-height: 100% + min-width: 0 on .o_fp_portal_main. The right
column now stretches to match the sidebar's height on short
pages, so layouts look uniform. min-width: 0 lets wide table
children scroll horizontally instead of forcing the grid track
to grow.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Every FP portal route built `values = {...}` from scratch and called
`request.render(...)` directly, bypassing `_prepare_portal_layout_values`.
Our new `fp_sidebar_items` and `fp_partner_display_name` keys live in
that hook, so the sidebar template's `t-foreach` was a no-op on every
custom page (`/my/home`, `/my/jobs`, `/my/account_summary`, etc.) — the
sidebar rendered with the "My Account" fallback header and only the
Sign Out footer link visible.
Fix: each FP render now does
values = self._prepare_portal_layout_values()
values.update({...route-specific values...})
This puts the layout values in first (so `fp_sidebar_items` and
`fp_partner_display_name` always present), and the route's own
update wins on `page_name` and other collisions.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The inline 'onchange=\"window.location.href = this.value\"' attribute on
the sort <select> is the only inline-JS handler in the project's QWeb
templates. Under a strict Content-Security-Policy (script-src 'self')
the handler silently fails, leaving the sort dropdown dead. Replace
with a tiny vanilla-JS file (fp_portal_account_summary.js) that attaches
the listener via class selector .o_fp_sort_select inside the Account
Summary page.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Tabs: Invoices / Credit Memos / Statements (V1 placeholder).
Page header carries the Open Balance pill. Per-tab filter pills
(Open/Closed/All), search box (name OR ref), sort dropdown
(newest/oldest/largest/smallest), 10-per-page pager.
Empty states: 'No results for X' for failed searches, 'No records
in this tab' for empty result sets, and the dedicated Statements
'coming soon' card. Statements tab hides the filter/search/sort
strip — nothing to filter yet.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New /my/account_summary route. Splits posted account.move into
Invoices (out_invoice) / Credit Memos (out_refund) / Statements
(V1 placeholder). Open Balance helper sums amount_residual across
open invoices for the partner's commercial tree.
Search filters name OR ref (customer PO). Sort options: date desc/asc,
amount desc/asc. Filter pills: open / closed / all.
Tests cover the tab partitioning, the open-balance sum, and the
search behaviour. Helpers use commercial_partner.env so they work
in both HTTP context and unit tests without requiring request.env.
Test scaffolding uses fp_from_so_invoice=True context flag and
invoice_payment_term_id to satisfy the fusion_plating_jobs and
fusion_plating_invoicing create/post gates.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- /my/fp_invoices -> /my/account_summary
- /my/purchase_orders -> /my/orders (Odoo default)
- /my/quote_requests/new (GET) -> /my/configurator/new
(POST handler preserved for back-compat with the existing RFQ form
button; will be removed after the form is fully retired)
Thin templates deleted: portal_my_fp_invoices, portal_my_purchase_orders.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fp_portal_shell.xml was already registered in Task 3 commit
(d17cada). This commit adds the two missing asset entries:
fp_portal_sidebar.scss in web.assets_frontend, after
fp_portal_dashboard.scss; fp_portal_sidebar.js after fp_rfq_form.js.
Version bumps 19.0.3.7.0 -> 19.0.4.0.0 (sidebar is a chrome change,
minor bump).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drives the sidebar from a single Python data structure
(_FP_SIDEBAR_LAYOUT). Active state resolved by page_name lookup OR
URL-prefix match (so Odoo default pages like /my/orders and
/my/account light up correctly). _prepare_portal_layout_values
extends super() so existing counter injection (fp_quote_request_count
etc.) keeps firing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Direct entry['url'] / entry['label'] would 500 the portal page if a
future helper emits an item dict missing a key. Use .get('url', '#')
and .get('label', '') so a malformed entry degrades silently instead
of taking the page down. Helper data is currently trusted (defined
in _FP_SIDEBAR_LAYOUT class constant) but defensive iteration is
cheap and prevents regression bugs from cascading.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fp_portal_shell wraps every /my/* page (FP custom + Odoo default)
in a sticky-sidebar shell with no per-template edits. Sidebar markup
is a separate fp_portal_sidebar template that reads fp_sidebar_items
+ fp_partner_display_name from the page context.
Approach D ($0 re-emit) used instead of plan's unbalanced-xpath approach:
position="replace" on //div[@id='wrap'] with $0 inside <main> causes
Odoo's Python inheritance engine to re-emit the original #wrap node
(verified in tools/template_inheritance.py lines 162-169). Every
xpath block is well-formed XML.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Backdrop display:block isn't media-scoped in fp_portal_sidebar.scss
(intentional — JS owns the drawer lifecycle). Without a resize
listener, opening the drawer at <=768px and resizing the browser
to >768px leaves the semi-opaque backdrop visible on desktop while
the sidebar visually snaps back to its sticky rail. Resize handler
calls toggleOpen(false) when crossing the breakpoint with .o_fp_open
still set.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
20 lines, no framework. Toggles .o_fp_open on sidebar + backdrop.
Backdrop click closes drawer; navigating a sidebar link on mobile
auto-closes. No-ops gracefully when sidebar isn't on the page
(logged-out, 500 pages, etc.).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Grouped sections via .o_fp_sidebar_section_label, active item gets
mint gradient fill + brand-teal left bar. Below 768px the sidebar
collapses to a fixed slide-in drawer (.o_fp_open class), with
.o_fp_portal_hamburger button + .o_fp_portal_backdrop as siblings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Captures everything the next Claude session needs to pick up cold:
- Live module versions on entech (portal 19.0.3.7.0, jobs/reports
versions, all 5 tests green)
- What shipped this session (24+ commits, summarised by area)
- Sub-A (IA + sidebar) brainstorm decisions locked, spec written,
plan ready to execute (11 tasks, 4 phases)
- What's deferred (sub-B multi-user, sub-C search, drafts, real
statements, RMA portal, top-recurring-parts) and WHY — so next
session doesn't re-litigate
- Gotchas hit + fixed this session that aren't obvious from code
- Deploy recipe (file copy + module upgrade + cache bust) used 20+
times this session
CLAUDE.md's Recent Session Handoff section now points to the new
handoff doc; the previous handoff is kept as 'superseded but kept
for context' below it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Portal users have read access to fp.portal.job but NOT to fp.job.
The new job-card macro traverses job.x_fc_job_id -> fp.job to surface
part info, sale_order, ship-to address — that raised AccessError for
real customers (admins were fine due to inherited groups).
Adding .sudo() to the three Job queries in home(), portal_my_jobs(),
and the certifications panel mirror lookup. Domain still filters to
the customer's commercial partner tree, so sudo doesn't widen
visibility — it just lets the template walk past the portal-job
boundary to the privileged backend models.
Same pattern is already used in the same file for sale.order,
account.move, and stock.picking queries.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1. Configurator step 2/3 500 fix: fp.coating.config was retired
(Sub-11) but the controller still queried it -> KeyError. Swapped
to fusion.plating.process.type (the real coating taxonomy on entech:
Hard Chrome, EN Low Phos, Type I Anodize, etc). Step 2 template
dropped dead refs (coat.process_type_id / spec_reference / thickness_*
/ certification_level), now shows code + process_family + description.
Pricing helper relaxed: filters out rules keyed to the dead model
and silently returns {'available': False} -> template shows 'Quote
will be priced by EN Plating' instead of fake numbers.
2. Configurator step 1: manual measurements hidden per customer
feedback. Length/Width/Height/Surface Area are kept as hidden 0s so
the rest of the flow doesn't error; backend trimesh still auto-calcs
surface area silently when STL is uploaded. Single file input split
into two: separate Drawing (PDF) + 3D Model (STL/STP/STEP/IGES)
uploads so customer can send both. Multi-upload session shape:
attachment_ids list. Submit handler re-keys ALL uploads onto the
new quote_request.
3. Job card upgraded: new fp_portal_job_card macro shared by dashboard
+ jobs list. Renders wrap div containing main anchor (whole card
clickable -> detail page) + sibling actions footer (4 doc download
quick-buttons: SO / WO / CoC / Packing + Repeat Order form).
Forms-inside-anchor is invalid HTML so the footer lives as a
sibling, not a child. Card now shows part name+number and ship-to
address pulled inline from job.x_fc_job_id.sale_order_id chain.
Same data also added to detail-page hero for consistency.
Version bump: 19.0.3.6.0 -> 19.0.3.7.0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Regression I introduced when adding the WO Detail group: the
groups.insert(2, wo_group) ran BEFORE the SPECIFICATIONS / QUALITY /
SHIPPING appends, so groups[2] shifted from 'quality' to 'work_order'
mid-helper. Result: the CoC got appended to the work_order group's
docs and shipping doc went into quality. Test caught it.
Restructured to declare the 5-group list up front in display order
and use stable indices throughout (0=from_you, 1=specs, 2=work_order,
3=quality, 4=shipping). Added a code comment warning future editors
that reordering means updating every groups[N] reference.
Test updated to expect 5 groups, asserting both 'work_order' and
'quality' keys are present + pending state in each.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1. From-You group now surfaces ANY ir.attachment attached to the
linked sale.order (sudo'd) so customer-uploaded PO + drawings
appear automatically. Each shows file name + upload date + size,
downloads via /web/content/<id>?download=true. Falls through to
the Sales Order Confirmation entry as before.
2. New 'Work Order' document group between Specifications and Quality,
surfacing the EN Plating WO Detail PDF via new route
/my/jobs/<id>/wo_detail. Sudo'd render of report_fp_job_wo_detail_
template so the template can read backend fp.job + recipe nodes.
Placeholder rendered when there's no linked backend job yet.
3. Hover underline gone: Bootstrap Reboot puts
text-decoration: underline on a:hover for every anchor, which read
as buggy on our flat chips / pill buttons / dashboard cards. Added
a catch-all selector list in fp_portal_buttons.scss that pins
text-decoration: none across hover/focus/active for every brand
element. Hover signal lives in color + shadow only.
Version bump: 19.0.3.5.0 -> 19.0.3.6.0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Original macro put the 5 labels in a separate flex container below the
stepper with flex:1 each. That distributes them at 10%/30%/50%/70%/90%
(centred in 1/5 slots) while the circles distribute at 0%/25%/50%/75%/
100% (edges via space-between + line-flex). Result: labels visibly off
from their circles, getting worse the wider the row.
Restructured the macro so each circle + its label live inside a single
.o_fp_step_unit. The label is absolute-positioned at top:100% / left:50%
with translateX(-50%), so its horizontal centre always pins to the
circle's centre regardless of text width. Wider labels ('Inspected')
overflow equally to both sides instead of pushing the column.
Bumped stepper margin-bottom to 2.4rem so the absolutely-positioned
labels have clearance below. Dropped the now-unused .o_fp_step_labels
container rule.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Regression from the pulse-animation commit: the @media (prefers-
reduced-motion) block had crept up and swallowed the .o_fp_step_line
rule, so the connector lines only got flex:1 when the user had
reduce-motion enabled. Everywhere else they had zero width and the
circles clustered on the left of the row with no visible gaps.
Moved .o_fp_step_line back inside the parent .o_fp_stepper { } where
it belongs. Added a comment so the next person doesn't make the same
mistake when editing the surrounding rules.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1. Pulse animation on the active step indicator:
- New @keyframes fp-pulse-teal / fp-pulse-amber in stepper.scss
- Applied to .o_fp_step_active / _warn and .o_fp_timeline_active
.o_fp_timeline_dot so dashboard stepper + detail-page timeline
breathe in sync. 1.8s ease-in-out, ring grows 4px -> 9px and
fades 20% -> 6% opacity. Two color variants so QC (warn) keeps
its amber meaning.
- prefers-reduced-motion: reduce kills the animation for users
who opted out.
2. Repeat Order button on /my/jobs/<id> detail page:
- New POST /my/jobs/<id>/repeat route that creates a draft
fusion.plating.quote.request seeded with the user's contact +
the job's quantity, posts a chatter link back to the original
job, redirects to the new RFQ for review/submit.
- Button placed in the detail footer next to 'Back to all jobs',
CSRF-protected via the form's csrf_token hidden field.
3. Dashboard expanded from 3 secondary panels to 5 (Recent Quote
Requests + Recent Purchase Orders added) so every previously-
designed customer page is reachable from /my/home.
- Auto-fit grid: 3+2 / 2+2+1 / single column depending on width.
- Every panel header gets a 'View all ->' link to its list page
(Quote Requests / POs / Certs / Deliveries / Invoices).
- Empty-state for Quote Requests gets an inline 'Get a quote ->'
CTA so first-time customers know where to start.
Version bump: 19.0.3.4.0 -> 19.0.3.5.0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two changes to _fp_get_stage_timeline:
1. Format: 'May 16, 2026 \xb7 9:14 AM' (full year + space + uppercase
AM/PM) instead of 'may 16 \xb7 9:14a'. Matches the mockup the
user approved. Date-only render kicks in when the timestamp has
no time component (backfilled/interpolated midnight values), so
we don't show fake '12:00 AM' next to a date we only know to the
day.
2. Linear interpolation: records that pre-date Task 16's per-stage
Datetime hook had empty middle-stage timestamps. The new fallback
spreads done stages evenly between received_at (or received_date)
and now() so old records show a plausible progression instead of
gap-toothed empty rows. Records created post-hook hit the real
captured values and never reach the interpolation branch.
Helper imports datetime + time at module level since we need
datetime.combine for Date->Datetime conversion in the fallback chain.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Records created before Task 16 (per-stage Datetime fields + write
snapshot hook) have NULL for received_at/shipped_at/etc. SQL backfill
copies received_date -> received_at; this commit adds a runtime
fallback so if any record slips through (manual edits, future
imports) the timeline still surfaces what's available.
Also render date-only ('May 16, 2026') when the timestamp has no
time component, so backfilled-from-Date records don't show the
misleading 'may 16 · 12:00a' fake time.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The signature footer ('Customer Acceptance (Signature / Date)' +
'Authorized Representative') is not part of EN Plating's intended
customer-facing quote/SO PDF flow. Removed from both portrait and
landscape variants of report_fp_sale_portrait/landscape.
Invoice report (report_fp_invoice.xml) had no such block - nothing
to remove there. Verified by grep across fusion_plating_reports.
Version bump: fusion_plating_reports 19.0.11.14.0 -> 19.0.11.15.0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The FP sale report template (report_fp_sale_portrait) walks into
fp.part.catalog records, which portal users don't have ACL on -
they'd hit 'You are not allowed to access Fusion Plating - Part
Catalog' when rendering. Standard /report/pdf/ route runs as the
authed user, so the template traversal fails.
Mirror the portal_download_coc pattern: gate on _document_check_access
for the portal job (customer can only ever reach their own data),
then render the report via ir.actions.report.sudo()._render_qweb_pdf
so the QWeb template traversal bypasses ACL. Return the PDF as an
attachment with a friendly filename.
Updates _fp_group_documents to point the From-You SO Confirmation
link at this new route instead of /report/pdf/ directly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Standard sale.report_saleorder hit the sale_pdf_quote_builder
header/footer merge bug (CLAUDE.md MEMORY.md gotcha) and produced
garbled PDFs on FP-customised sale orders. Switching to
fusion_plating_reports.report_fp_sale_portrait which is the
customer-facing FP template and bypasses the merge gate. Added
?download=true so the browser saves the PDF instead of trying to
embed it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
_fp_create_portal_job hardcoded state='in_progress'. Now uses the
same _FP_JOB_STATE_TO_PORTAL_STATE map as write(), so a portal job
created for an already-confirmed (but not yet started) fp.job lands
in 'received' instead of jumping to 'in_progress'. Falls back to
'received' for unmapped states.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1. /my now serves the FP dashboard (stock Odoo home was leaking
through because parent route declared ['/my', '/my/home'] but my
override only listed /my/home).
2. Button padding bumped to .5rem 1rem + font 1rem so o_fp_btn matches
Odoo's standard Bootstrap button rhythm. Ghost button drops its
custom padding override.
3. .o_fp_job_card on /my/home + /my/jobs is now an <a> wrapping the
whole card area — full row is the click target, not just the WO
number. Inner <a> on job.name dropped to avoid nested anchors;
focus-visible outline added for keyboard nav.
4. fp.job.write() now mirrors state -> fp.portal.job.state via new
_FP_JOB_STATE_TO_PORTAL_STATE map (confirmed->received,
in_progress->in_progress, done->ready_to_ship). Fixes the bug where
completed backend jobs left the portal stuck on 'in_progress'.
'on_hold' and 'cancelled' intentionally not mirrored — manager
choice what to surface.
5. Sales Order Confirmation now surfaces in the 'From You' group on
the job detail page, pulled via job.x_fc_job_id.sale_order_id ->
/report/pdf/sale.report_saleorder/<id>. Falls back to the upload
placeholder when no SO is linked.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
All btn-primary -> o_fp_btn_primary, btn-outline-secondary ->
o_fp_btn_secondary, large CTAs get o_fp_btn_lg modifier. Status
badges (text-bg-secondary/warning/info) left untouched - they're
auto-calculated chips not workflow states.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two-column grid: vertical timeline (5 stages with per-stage timestamps)
on the left, grouped document panel (4 categories) on the right. Hero
header carries WO ref + part / qty / ETA / tracking facts.
Controller adds stage_timeline, doc_groups, and timeline_spine_pct
to the render context. Spine fill = done + half-credit for the
active stage (so the spine visually leads the eye to where the work
is happening).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drops the old 3-segment progress bar in favour of the dashboard's
5-step circle-and-line stepper for consistency. Uses the same
state_to_idx mapping so all 6 fp.portal.job states (including
'complete') render correctly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
V1 surfaces only the fields directly on fp.portal.job (CoC + packing
list). Other 2 groups (From You, Specifications) render placeholder
rows. V2 will wire in sale.order linking for full doc surfacing.
Also adds _fp_size_label helper for friendly file-size strings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Builds a 5-entry list (label, status, started_at, time_label, notes)
ordered by stage. Labels match the dashboard stepper exactly
(Received/Inspected/Plating/QC/Shipped) so the two surfaces tell
the same story. Inspected and Plating share in_progress_started_at
since state in_progress means both transitions happened.
Time labels use lowercase am/pm matching the mockup typography.
'complete' state correctly shows all 5 stages as done (caught by
new test).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds received_at, in_progress_started_at, qc_started_at,
ready_to_ship_at, shipped_at - snapshotted on state change via
write() override using super().write() to avoid recursion. Required
for the vertical-timeline rendering on the job detail page (Phase 3).
Idempotent: re-transitioning to a state already-stamped does not
overwrite the original timestamp.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds 4 Phase 2 SCSS partials (badges/cards/stepper/dashboard) plus
the macros XML data file. Macros load before any template that
t-calls them per Odoo's strict-sequential XML loader.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Welcome strip -> 4-tile KPI row (In-Flight Jobs is the hero) ->
Active Work Orders section with 3 most-recent V2 cards ->
3-panel secondary strip (Certs / Packing Slips / Invoices).
Uses the new badge/stepper/doc-chip macros.
Also fixes a stepper state->step mapping bug that would have
shown Inspected as active when state=in_progress (should be
Plating active). New state_to_idx dict handles all 6 fp.portal.job
states correctly, including 'complete' (all 5 stages done).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds active_job_count, awaiting_review_count, ready_to_ship_count
to the dashboard context. Tests verify partition is correct across
the fp.portal.job and fp.quote.request state machines.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Macros take dict args so callers never reach into the underlying
records — keeps templates testable + makes the stepper reusable
on dashboard cards AND detail-page if needed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Tokens partial loaded first; buttons SCSS loaded next; legacy
catch-all stays last. Per CLAUDE.md rule 8 every SCSS file is a
separate entry (no @import allowed in Odoo 19 custom SCSS).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Five button variants under .o_fp_btn_* classes that don't fight
Bootstrap. Primary uses the brand teal gradient with mint-tinted
shadow; danger uses the red gradient. Focus/hover/active states
included.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
EN Plating teal palette + gradient/shadow/radius/spacing/typography
tokens. Single source of truth for the customer portal redesign.
Tokens load first in web.assets_frontend so downstream SCSS sees them.
Refs spec: docs/superpowers/specs/2026-05-17-portal-dashboard-redesign-design.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Spec covers the brainstormed design: jobs-forward layout, V2 stepper
with timestamps, EN Plating teal/gradient palette, 4 doc categories.
Plan decomposes implementation into 4 independently-deployable phases
(tokens+buttons -> dashboard -> jobs detail -> cosmetic sweep) with
27 tasks total.
Also adds .gitignore so .superpowers/ brainstorm artifacts stay
untracked.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The custom dashboard at fusion_plating_portal was rendering a 6-card
view at /my/home, but a method-name mismatch left the parent
portal.CustomerPortal.home() route active instead. Rename the
override to home() so Python MRO does the override naturally, and
add CLAUDE.md Critical Rule 16 documenting the gotcha so future
controller-override work doesn't trip on it.
Version bump: 19.0.2.2.0 -> 19.0.2.3.0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
macOS keystroke injection from a CLI-launched Python hits multiple
TCC permission walls (Accessibility AND Automation, both attaching
to identities macOS often can't resolve cleanly). After bouncing
through Quartz, AppleScript, and pyautogui fallbacks, none of them
worked reliably in our test environment.
Switch to a proper IPC channel instead of pretending to be a
keyboard.
Daemon (wedge.py):
- Adds a ThreadingHTTPServer on 127.0.0.1:8765 exposing /events
- SSE stream pushes each detected UID as one event
- 30s keep-alive comments to keep idle connections open
- CORS: Access-Control-Allow-Origin: * (kiosk page may be on any
client-domain HTTPS origin; SSE source is always localhost)
- Keystroke injection kept as best-effort fallback for non-SSE
clients
Kiosk JS (fusion_clock_nfc_kiosk.js):
- Adds startWedgeSseListener() that opens EventSource to
http://localhost:8765/events on setup
- On message: same handleTap()/_onEnrollTap() flow as Web NFC + HID
- EventSource auto-reconnects; first error is logged then silenced
- http://localhost is a "potentially trustworthy origin" so this
works from https:// pages without mixed-content blocking
Result: ACR122U + wedge.py daemon now drives the kiosk with zero
macOS permission prompts and no focused-window dependency. Same
input plumbing as Web NFC and HID — penalty/photo/activity log
fire identically.
Bump fusion_clock to 19.0.3.3.0.
pyautogui's Quartz-based keystroke path often fails on newer macOS
because the Python CLI binary doesn't auto-surface in System Settings
> Accessibility. User reported the daemon detected taps fine but
keystrokes never landed in any window.
Switch to AppleScript / System Events on macOS. Permission attaches
to whatever terminal/app launched the Python process (Terminal.app,
iTerm, etc.) — a familiar named app the user can grant Accessibility
to in one click. Combined keystroke + Return in a single osascript
call to keep latency ~100ms per tap.
Fall back to pyautogui if osascript fails (handles edge cases) and
on non-macOS platforms.
ACR122U is a 13.56 MHz PC/SC (CCID) reader, not HID. Browsers can't
talk to PC/SC devices directly, so the kiosk JS can't see ACR122U
taps the way it sees a USB-HID reader.
This daemon bridges the gap:
- Polls the ACR122U via pyscard
- Reads UID via the standard ACS GET_UID APDU (FF CA 00 00 00)
- Types UID + Enter into the focused window using pyautogui
- Debounces re-reads of the same card (2s window)
Output format matches FusionClockNfcKiosk._normalize_uid() expectations:
colon-separated uppercase hex (04:10:5B:CA:FD:22:90 + Enter).
The kiosk JS already has a keyboard-wedge listener (v19.0.3.2.0+),
so no server-side or kiosk-side changes needed — wedge.py's
keystrokes route through the same handleTap() path as a USB-HID
reader, preserving photo verification + penalty + activity log.
Setup docs include macOS, Windows, Linux instructions plus
launchd/Task Scheduler/systemd snippets for running as a service.
Strategic value: with this, ACR122U deployments support UA-Pockets
(13.56 MHz DESFire EV3) for single-card door+clock setups in the
premium tier of the standard product kit. The 125 kHz EM4100 USB-C
HID reader remains the default tier.
The NFC kiosk previously required Web NFC, which is Android-Chrome-only.
This blocked desktop testing and locked us to a single hardware path.
Add a keyboard-wedge listener that captures keystrokes from USB HID NFC
readers (the standard Sycreader/Yanzeo class). The listener buffers hex
chars + separators, flushes on Enter (or 600ms idle as fallback for
readers without a terminator), and routes the UID through the same
handleTap()/_onEnrollTap() codepath as Web NFC. Photo verification,
penalty calc, and activity logging all fire identically.
Make the setup button tolerant: try Web NFC, but treat its absence as
non-fatal. USB HID always activates. Only hard-fail when photoRequired
is True AND the camera is unavailable.
Result: same kiosk page now works on Android Chrome (Web NFC), desktop
Chrome with a USB reader, or both at once.
Bump manifest to 19.0.3.2.0.
Wizard was deployed without an entry in security/ir.model.access.csv,
so ANY user (including managers) got a permission error when opening
the menu. The model is registered but has no group access rules,
so Odoo's ORM blocks read/create on it.
Grant full CRUD on fusion.clock.nfc.enrollment.wizard to
group_fusion_clock_manager (the same group the menu is gated to).
Bump manifest to 19.0.3.1.1.
The Enroll NFC Card menu item references action_fusion_clock_nfc_enrollment_wizard,
which is defined in wizard/clock_nfc_enrollment_views.xml. With the wizard file
listed AFTER clock_menus.xml in the manifest, the menu load failed with
"External ID not found in the system" on first upgrade.
Move the wizard view above clock_menus.xml so the action XMLID exists by the
time the menu references it.
Verified on odoo-entech: fusion_clock upgraded cleanly to 19.0.3.1.0, all
wizard XMLIDs registered.
Adds a tap-driven enrollment workflow so managers can pair NFC/RFID
cards to employees using a USB HID reader at their desk:
- New wizard model fusion.clock.nfc.enrollment.wizard with auto-focused
Card UID field, employee picker, and reassignment warning if the
card is already held by someone else.
- Two actions: 'Enroll Card' (single) and 'Enroll & Next' (bulk).
- Menu entry under Fusion Clock root, manager-gated.
- Exposes x_fclk_nfc_card_uid on the Employee form Clock Settings
section (next to Kiosk PIN) so it can be inspected/edited directly.
- Bumps manifest to 19.0.3.1.0 for asset cache bust.
Wizard reuses FusionClockNfcKiosk._normalize_uid so stored format
matches what the kiosk /tap endpoint looks up later. Reassignment
clears the UID from the previous holder and logs both events to the
activity log under 'card_enrollment'.
Per client direction: every order is a thickness RANGE (e.g.
"0.0005-0.0008 mils" or "5-10 mils"), never a single value. The
old picker model (fp.recipe.thickness with a single 'value' Float)
was modelling the wrong concept and overcrowding the order entry
UI. Replaced with one free-text Char field that auto-fills from
last-used or part default.
DELETED entirely:
- fp.recipe.thickness model (file + view + ACL + manifest entry)
- recipe.thickness_option_ids One2many (the picker source)
- "Thickness Options" inline list on the recipe form
- sale.order.line.x_fc_thickness_id (M2O picker)
- account.move.line.x_fc_thickness_id
- fp.delivery.x_fc_thickness_id
- fp.direct.order.line.thickness_id
ADDED:
- sale.order.line.x_fc_thickness_range (Char) — operator types range
- account.move.line.x_fc_thickness_range — for invoice rendering
- fp.delivery.x_fc_thickness_range — for packing slip
- fp.direct.order.line.thickness_range — for the wizard
- fp.part.catalog.x_fc_default_thickness_range — part default
AUTO-FILL CHAIN (sale.order.line + wizard line):
1. Operator already typed → keep
2. Most recent SO line for (this part, this customer) with a
non-empty thickness_range → copy that
3. part.x_fc_default_thickness_range → copy
4. Blank — operator types
Implemented as both an @api.onchange (interactive) AND a
create() override (programmatic — wizard, sale_mrp bridge,
imports). Same logic in both paths.
WIZARD push-to-defaults: when "Save as Default" toggle is ticked
on a wizard line, persist the line's thickness_range to
part.x_fc_default_thickness_range so future first-customer orders
get a sensible starting point.
REPORTS: customer_line_header.xml + report_fp_wo_sticker.xml now
print the Char range as-typed (no display_name lookup needed).
KEPT (admin documentation only — doesn't affect order entry):
- recipe.thickness_min, thickness_max, thickness_uom on the recipe
root: documents the recipe's CAPABILITY range. No UI gate; just
for spec authors to record what the chemistry can produce.
JOB GROUPING: fp.job auto-create groups SO lines by (recipe, part,
spec, thickness, serial). Updated to key on the thickness_range
Char (stripped) instead of the deleted thickness_id integer.
DB cleanup: --update=base ran on the upgrade, dropping the
fp_recipe_thickness table + the four x_fc_thickness_id columns.
Existing data was already nulled in earlier dev work.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The _compute_display_name method on fusion.plating.customer.spec was
missing its @api.depends decorator. Without it, Odoo doesn't know
when to fire the compute, so display_name stayed NULL on:
- All seeded specs (created via XML data import)
- Any spec created later (the field was never recomputed)
Symptom: Specification dropdown on the SO line showed "Unnamed" for
every option, making spec selection useless.
Fix:
- @api.depends('code', 'revision', 'name') on _compute_display_name
- Imported `api` (was only `fields, models`)
Companion entech-side action: forced recompute on the 15 existing
specs via `env.add_to_compute(specs._fields['display_name'], specs)`
so the stored column was backfilled. New specs created via UI will
trigger the compute automatically going forward.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Audit of all 86 data XML files in the fusion_plating module set
turned up 3 more files that lacked noupdate=1 protection — every
module upgrade would re-import them and silently overwrite user
customisations. Following the ENP-ALUM-BASIC recovery (a68bf2e),
locked these too:
1. fusion_tasks/data/ir_cron_data.xml — 4 ir.cron records
(technician travel times, push notifications, late-arrival
checks, location cleanup). Users may disable / re-schedule.
2. fusion_plating_shopfloor/data/fp_cron_data.xml — 1 ir.cron
(Bake Window state updater). Same reasoning.
3. fusion_plating_bridge_maintenance/data/fp_maintenance_stage_data.xml
— 3 maintenance.stage records (kanban columns: New / Active /
Completed). Admin may rename, reorder, or add new stages.
Companion entech-side action (executed via SQL during the fix
session): 11 ir.model.data rows for these records were updated to
noupdate=true so the next module upgrade respects the new flag.
Files left explicitly noupdate=0 — verified safe:
- fusion_plating/data/fp_landing_data.xml — 1 ir.actions.server
(system action, code-defined; re-import is harmless)
- fusion_plating_reports/data/fp_hide_default_reports.xml —
re-asserts deletion of default Odoo report bindings; intentional
to re-run on every upgrade
Final audit confirmed 0 user-editable noupdate=false records remain.
ir.model.inherit + report.paperformat rows still noupdate=false but
those are system metadata (Odoo manages) and Odoo's standard
paperformat pattern, both safe.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CRITICAL BUG: 5 of 6 seeded recipe files had <data noupdate="0">
which caused EVERY module upgrade to re-import the recipe and
overwrite any user customisations to the base recipe (renamed
steps, added child nodes, custom prompts on seeded steps).
Files fixed (now noupdate="1"):
- fp_recipe_enp_alum_basic.xml
- fp_recipe_enp_steel_basic.xml
- fp_recipe_enp_sp.xml
- fp_recipe_anodize.xml
- fp_recipe_chem_conversion.xml
(fp_recipe_general_processing.xml was already correctly noupdate=1.)
Companion entech-side action (not in this commit, executed via SQL
during the fix session): 200 ir.model.data rows for the affected
process_node + process_node_input records were updated to
noupdate=true so the next module upgrade will skip them entirely
and respect the user's current state.
Recovery for users whose base recipe edits were already lost:
the variants (part-cloned recipes that share the recipe name)
were untouched because they have no XML xmlid match. The
customisations are preserved in the variants and can be lifted
back to the base recipe via the simple/tree editor.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Specifications menu (urgent — workflow blocker for estimators):
- Moved from Configuration → Quality & Documents (manager-only) up
to Plating → Quality (sequence 70). Now visible to estimator,
supervisor, and manager.
- Renamed "Customer Specs" → "Specifications" — the seeded library
includes industry standards (AMS, MIL, ASTM, BAC) not just
customer-private specs.
- Action display name updated: "Customer Specifications" → "Specifications".
- Added action.help HTML so the empty-state placeholder explains
the Specifications library purpose to first-time users.
- Old xmlid (menu_fp_config_customer_spec) preserved so existing
links / breadcrumbs / search references continue to resolve.
Other clarifying renames:
- Safety: "JHSC" / "JHSC Meetings" → "H&S Committee (JHSC)" /
"H&S Committee Meetings" — acronym was opaque to non-Canadian
H&S folks.
- Operations: "Move Log" → "Parts & Rack Move Log" — generic name
could be confused with chatter messages or stock moves.
- Configuration → Recipes & Steps: "Workflow States" →
"Job Workflow Stages" — generic name; clarifies these are job
state milestones (passed-stage tracking), not generic workflow.
- Compliance → General: child folder "Configuration" → "Reference
Data" — three levels of "Configuration" nesting (Plating>Config
vs Plating>Compliance>General>Config) was confusing.
No model / data changes. Pure menu metadata.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After Phase E removed Coating Config + Treatments + Customer Price List
+ Coating Thickness from the Configurator submenu, only 3 admin items
remained — not enough to justify a top-level menu just for an
estimator.
Re-homed:
- Pricing Rules → Configuration → Pricing & Billing
(sequence 40, joins Invoice Strategy
Defaults + Account Holds)
- Materials → Configuration → Materials & Tanks
(sequence 40, joins Bath Parameters,
Replenishment Rules, Chemicals,
Rack Tags, Calibration Equipment)
- Line Description Templates → Configuration → Quality & Documents
(sequence 90, joins Notification
Templates — same "templates" pattern)
All three keep estimator visibility (group_fp_estimator) plus manager
access. Top-level menu count under "Plating" drops from 9 visible to 8.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase E removed the coating-rollup loop but left a stale `has_cost_data`
reference in the percent computation. NameError on every SO list /
form load.
Margin is "not available" until recipe-level cost data exists
(backlog item). Set all three margin fields to 0 / False explicitly
so no stale references remain.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reports updated to print Specification (with revision via display_name):
- report_fp_sale.xml — header sections show "SPECIFICATION" instead
of "COATING CONFIG", reads doc.x_fc_customer_spec_id (added on
sale.order via quality inherit, computed from line.customer_spec_id)
- report_fp_wo_sticker.xml — propagates _spec alongside _coating
- fusion_plating_reports/report_fp_job_traveller.xml — header row
now shows Specification (falls back to coating)
- fusion_plating_jobs/report_fp_job_traveller.xml — same fall-back
- fusion_plating_jobs/report_fp_job_sticker.xml — _spec added
sale.order.x_fc_customer_spec_id added as a stored compute on
sale.order (in quality) so reports can render order-level spec.
Mirrors the line's first spec; updates on line edit.
Tablet payload (shopfloor_controller.py):
- spec_label added to the job payload dict
- defensive 'customer_spec_id' in job._fields check (shopfloor doesn't
depend on quality — circular if added)
Portal: deferred (same circular-dep issue, more substantial UI rewrite
needed; Phase E backlog item).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pricing:
- Quality inherit on fp.pricing.rule adds customer_spec_id + recipe_id
- Quality inherit on fp.quote.configurator adds customer_spec_id field
+ extends _find_matching_rule with priority chain:
spec (+8) > recipe (+6) > coating (+4) > material (+2) > cert (+1)
- View inherit surfaces both new pickers on the rule form
Quality points:
- fp.quality.point now has customer_spec_ids + recipe_ids M2M filters
- Matcher (_matches + _find_matching) accepts new args
- Hook overrides on SO confirm + job confirm/done + step finish
pass spec/recipe context through to the matcher
- View surfaces both new M2M widgets
Job:
- jobs/sale_order.py wires x_fc_customer_spec_id from SO line to
fp.job.customer_spec_id on action_confirm
Cert:
- Quality inherit on fp.certificate adds customer_spec_id field +
create() override auto-fills spec_reference from spec.code+revision
Resolution priority: explicit spec_reference > cert.customer_spec_id
> SO line spec (with print_on_cert) > legacy coating fallback
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Spec-side picker (x_fc_customer_spec_id / customer_spec_id) added on:
- sale.order.line (via quality inherit — onchange autofill, create()
fallback to part default, _prepare_invoice_line carry)
- account.move.line (via quality inherit — invoice rendering)
- fp.part.catalog (via quality inherit — x_fc_default_customer_spec_id)
- fp.direct.order.line (via quality inherit — wizard picker + autofill)
- fp.direct.order.wizard (action_create_order post-creates spec on SO line)
Thickness picker switched to fp.recipe.thickness (replaces coating-scoped):
- sale.order.line.x_fc_thickness_id comodel + domain rewired to recipe
- account.move.line + fp.delivery same
- fp.direct.order.line.thickness_id same
View inherits in quality add Specification picker next to legacy
Primary Treatment column on:
- SO form line tree
- part catalog Default Treatments block
- direct-order wizard line tree + drawer
Wizard files (fp.contract.review.client.email.wizard) pulled from
entech into the repo — they were ahead of the repo. Quality __init__
now imports wizards/.
Legacy x_fc_coating_config_id + treatment_ids remain visible during
transition; Phase E removes them.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per client review: NADCAP-qualified recipes need manager-only edit
permission. Word-doc external approval workflow stays outside ERP;
this is the in-app enforcement.
- New field fp.process.node.is_locked (recipe root)
- write() override blocks non-manager edits when recipe root is_locked
Lock checks via recipe_root_id so child ops/steps are also protected
Manager bypass via group + env.su (sudo) bypass for system jobs
- Amber "LOCKED — Manager Edit Only" ribbon at top of recipe form
- Toggle on Specification & Bake page under "Change Control (NADCAP)"
- Spec doc updated with Decision 6.5 + backlog from client review:
approvals list, doc control auto-sync, oven recorder sync, SOP
word-doc workflow, final-inspection signoff on cert
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add fp.recipe.thickness model (replaces fp.coating.thickness, scoped to recipe root)
- Add spec metadata + bake-relief fields to fusion.plating.process.node (recipe root):
phosphorus_level, thickness_min/max/uom, thickness_option_ids,
requires_bake_relief + bake_window_hours/temperature/duration
- Add recipe_ids M2M + print_on_cert to fusion.plating.customer.spec
- Add applicable_spec_ids reverse M2M as inherit in fusion_plating_quality
(avoids circular dep — core can't reference customer.spec which lives in quality)
- Surface new fields on recipe form ("Specification & Bake" notebook page)
- Surface recipe linkage on customer spec form
Pure additive. Foundation for Phases B-E.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.