JS: single FpQualityDashboard component + BannerCard / BannerItem /
SectionCard / SectionRow sibling sub-components in the same file.
Fetches /fp/quality/dashboard/snapshot, 60s poll, deep-link
?tab=certificates scrolls to section-cert via scrollIntoView.
XML: outer wrapper + banner + 6 sections (t-foreach over
state.snapshot.sections). Each section has id='section-<type>' so
the deep-link target works. SectionRow has overdue-conditional
class for red subtitle highlight.
SCSS: local tokens for urgent/good/section-head with light+dark via
$o-webclient-color-scheme branch. 135deg gradients matching the
plant kanban polish. Mobile breakpoint at 900px collapses banner
grid to 1 col and stacks row Open button.
OLD TABS array, selectTab, openTab, totalOpen, totalOverdue all
deleted. Old template's tab tiles + per-tab panels deleted. Existing
per-model kanbans untouched.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Covers: missing-field critical-customer check returns empty without
crashing; computed_at is a valid ISO timestamp; every section ships
a non-empty open_kanban_xmlid in module.xmlid format.
(missing-model test from the plan omitted — patching env.__contains__
was unsafe; the in-self.env guard is already exercised by Tasks 2-4
in production behavior. The other 3 defensive tests still cover the
missing-field path, which is the more common scenario.)
Phase 1 backend complete.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
_fetch_banner_candidates collects (overdue) OR (critical-customer +
open) records per type. _critical_customer_ids reuses partner.x_fc_rush
and partner.x_fc_vip flags when defined (gracefully no-ops when
absent). _critical_badge returns RUSH/VIP/AEROSPACE/AS9100 label
when the banner reason is critical-customer (no badge when overdue).
_build_banner ranks: overdue first by oldest, then critical-customer
by oldest, takes top 6, reports total_matching.
build() now collects banner candidates from every section in one
pass + invokes _build_banner once.
Tests cover overdue hold pickup, 6-cap with overflow count, and
all_clear when DB is empty.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
_fetch_section_items pulls top-5 open records per type, ranked
overdue-first by oldest create_date. _build_item shapes each row
with id/name/customer/subtitle/urgency/open_action. _resolve_partner
defensively walks partner_id -> job_id.partner_id -> ncr_id.partner_id
per type. _build_subtitle generates the human-readable second line.
Tests cover empty list, 5-cap on 8-record set, and required item
keys (id/name/customer/subtitle/urgency/open_action).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces /counts with /snapshot. Helper class FpQualityDashboardSnapshot
returns response with correct shape — banner placeholder + per-type
sections with open/overdue counts (reuses old counts endpoint
thresholds). Items + critical-customer banner come in Tasks 3-5.
Per CLAUDE.md Rule 13m, Model.sudo() on cross-module reads. Per
Rule 24 the in-self.env check guards missing-model paths.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Tests for empty-DB all-clear, canonical section order, and required
keys on each section. All fail until Task 2 lands the snapshot helper.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User goal: 'all quality related updates at glance, all the flagged
tasks need to show right here so the manager can quickly follow up
and complete the task'. Current dashboard is a tab-router (6 numeric
tiles + click-to-drill) — flagged tasks aren't visible without
navigation.
Design (Hybrid layout, approved):
- Red 'Needs Attention Today' banner on top (up to 6 items, 2x3 grid)
showing items that are overdue OR from critical customers
(x_fc_rush / x_fc_vip / aerospace). Green 'all caught up' when zero.
- Per-type sections below in QM-urgency order: Certs / Holds / NCRs /
RMAs / CAPAs / Checks. Each shows top 5 items inline + Open all
link to the existing kanban.
- Single 'Open ->' button per row -> opens record form via act_window.
No one-click action shortcuts (cert form is where Fischerscope +
sign-off prereqs are validated).
- Drop the existing 'Quality Overview' header strip entirely.
- 60s poll cadence preserved.
- ?tab=certificates deep-link from awaiting-cert notification email
preserved as scrollIntoView on the certs section.
Backend: replace /fp/quality/dashboard/counts with /snapshot. New
helper class FpQualityDashboardSnapshot builds banner + 6 sections in
one response. Cross-module reads sudo'd per Rule 13m; missing fields
gracefully degrade per Rule 13j defensive pattern.
Frontend: rewrite the OWL component. BannerCard + 6 SectionCards as
sub-components in the same JS file (not reused elsewhere yet).
Existing per-model kanbans untouched.
Self-review fixed 4 issues:
- _critical_customer_domain made per-type (was contradictory)
- OVERDUE_THRESHOLDS gained explicit use_due_date flag (CAPA branch)
- Template requirement called out: id='section-<type>' on each card
for the deep-link scrollIntoView to work
- doAction call shape disambiguated for xmlid vs full dict
Spec: docs/superpowers/specs/2026-05-25-quality-dashboard-redesign-design.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User feedback after first deploy: 7 KPI tiles wrapped to second line
(grid was repeat(5, 1fr) but I had added 2 new ones), and the
controls felt cramped.
Layout fix:
- .kpi-strip grid: repeat(5, 1fr) → repeat(8, 1fr) so the row stays
one line and there's room for the new Awaiting QC tile.
Missing KPI added:
- Awaiting QC — fp.job.card_state='awaiting_qc' count. Operators
couldn't see when QC was blocking job close from the KPI strip
(only visible inside the column). Server-side count + filter
clause + matching filter chip.
Visual polish (all light + dark via existing token system):
- KPI tiles: padding 6→10px, value font 20→26px, label font 9→10px,
subtle 135deg linear-gradient bg per kind (urgent/warn/good/qc),
hover lifts the tile with translateY + shadow.
- Filter chips: padding 4/12→7/16px, font 11→13px, gradient bg,
active state has gradient blue + shadow.
- Search input: padding 5/10→9/14px, font 12→14px, focus ring.
- Toolbar buttons (Station/All Plant/Manager/Scan QR/Hand Off):
padding 5/10→8/14px, font 12→14px, gradients, hover lift.
Dark mode handled automatically — all gradients reference
$plant-* tokens which already have @if $o-webclient-color-scheme ==
dark global overrides in _plant_tokens.scss.
Version bump fusion_plating_shopfloor 19.0.34.0.0 → 19.0.34.1.0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three bugs caught + fixed during entech battle test:
1. _fp_check_finish_gates calling button_mark_done triggered the
step-completion gate prematurely (step still in_progress at
pre-super time). Pass fp_skip_step_gate=True alongside
fp_check_gates_only — we know the operator is about to finish
the last open step.
2. _fp_schedule_cert_activity used env.get('fp.notification.template')
for presence check. env.get returns an EMPTY recordset (falsy),
not None — 'if not Template: return' silently exited and no
activity was ever scheduled. Switch to 'in self.env' check
pattern + explicit indexing. CLAUDE.md Rule 24.
3. _fp_check_advance_after_cert_issue + _fp_check_regress_after_cert_void
used 'state != issued' as outstanding-cert count. This made
voided certs count as outstanding forever, so void+re-issue
cycles never re-advanced. Switch to per-type coverage check:
each required cert TYPE needs at least one issued cert.
Regress mirrors: only fire if a type loses all issued certs.
CLAUDE.md gains Rule 24 (env.get falsy empty recordset trap).
Rule 25 (mail.template parse-time validation) renumbered.
Battle test ALL PASS on entech admin DB:
10/10 steps green — auto-advance, kanban placement, activity
schedule + auto-resolve, ACL guard, cert issue advance, void
regress, re-issue advance, manual ship.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Entech deploy of 5a039ae3 hit:
ParseError: Failed to render inline_template template
AttributeError('fp.job' object has no attribute 'display_wo_name')
Root cause: mail.template data files are parse-time validated by
Odoo (template rendered against sample object). fusion_plating_notifications
loads BEFORE fusion_plating_jobs in dep order, so jobs-module fields
(display_wo_name, part_catalog_id) aren't on the Python class yet
even though the DB columns exist from previous installs.
Fix: strip display_wo_name → name and remove the Part row.
Recipe / qty_done / partner_id stay (all in fusion_plating core).
Logged as CLAUDE.md Rule #24 — same trap will bite anyone else
adding cross-module mail templates. Includes structural alternatives
for callers that really need downstream fields.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
10-step battle test covering: auto-advance on last step finish,
kanban placement, QM activity, ACL guard, cert issue advance,
activity auto-resolve, cert void regress, re-issue, manual ship.
Tolerant of partial state — branches around the awaiting_cert path
when partner doesn't require certs (uses awaiting_ship path instead),
SKIPs subsequent steps when prerequisites fail, rolls back at end so
the DB stays clean.
Run on entech via odoo-shell after deploy.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Idempotent post-migrate that moves mid-flight in_progress jobs whose
recipe steps are all terminal into the appropriate new state:
- draft cert exists → awaiting_cert
- no cert required → awaiting_ship
done jobs left alone (historically completed, already shipped).
Card_state + mini_timeline_json recomputed for affected rows so the
plant kanban renders correctly on first page load.
Version bump 19.0.10.31.0 → 19.0.11.0.0 triggers the migration on -u.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
TRIGGER_EVENTS extended with three new events:
- cert_awaiting_issuance — fires on in_progress → awaiting_cert
- cert_voided_re_notify — fires on awaiting_ship → awaiting_cert
regress (cert voided post-issue)
- job_shipped — fires on button_mark_shipped
_dispatch routes cert events through new internal-recipient resolver
(QM/Manager/Owner via all_group_ids, transitive per Rule 13l)
instead of the partner-based stream lookup. Other events unchanged.
Mail templates (fp_cert_authority_templates.xml): two new
mail.template records bound to fp.job. Amber accent bar for awaiting,
red accent bar for void-re-issue. Deep-link to
/odoo/action-...?tab=certificates so QM lands on the right tab.
Activity type (fp_activity_types_data.xml): mail.activity.type
activity_type_issue_coc — bound to fp.job, 1-day delay, certificate
icon.
fp.job helpers:
_fp_schedule_cert_activity: round-robin by oldest login_date,
idempotent on existing open activity, soft-fails if helpers
are missing.
_fp_resolve_cert_activities: auto-resolves on awaiting_ship,
soft-fails on per-activity exceptions.
Manifest bumps:
fusion_plating_notifications 19.0.6.6.1 → 19.0.7.0.0
fusion_plating_jobs: data list gains fp_activity_types_data.xml
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Counts endpoint: certificates block — open=draft, overdue=draft+>24h.
Falls back to {open:0, overdue:0} when fp.certificate isn't installed.
JS: TABS array gains the 6th entry. Existing data-driven OWL template
auto-renders both the header tile and the body panel. Tab opens the
fp.certificate kanban grouped by state, filtered to draft by default.
Deep-link: setup() reads action.context.params.tab. The
cert_awaiting_issuance notification email links to
/odoo/action-fp_quality_dashboard?tab=certificates and lands the QM
on the right tab automatically.
Template: 'Open across all 5' → 'Open across all <tabs.length>' so
it stays correct if more tabs are added later.
Manifest: fusion_plating_quality 19.0.6.6.6 → 19.0.7.0.0
(fusion_plating_certificates already in depends — no change needed).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Controller (plant_kanban.py):
- Widen domain: state IN (confirmed, in_progress, awaiting_cert,
awaiting_ship). Done jobs still drop off.
- _resolve_card_area: state=awaiting_cert → 'inspection' column,
state=awaiting_ship → 'shipping' column. State drives column
regardless of recipe shape.
- _state_chip: 🏷️ Awaiting CoC (amber) + 📦 Ready to ship (green).
- _SORT_PRIORITY: awaiting_cert=3.5, awaiting_ship=8.5.
- KPI dict: awaiting_cert + awaiting_ship counts.
- Filter clauses for the two new chips.
Model (fp_job.py):
- _compute_card_state handles new states in BOTH branches: the
no-active-step early return (where awaiting_cert/ship cards
land — all steps terminal) AND the per-step branch (defensive).
- _compute_mini_timeline_json: awaiting_cert paints inspection
dot 'current'; awaiting_ship paints shipping dot 'current'.
All earlier dots show 'done'.
SCSS (_plant_tokens.scss + _plant_card.scss):
- New tokens for amber (cert) + green (ship), light + dark variants
via the existing $o-webclient-color-scheme compile-time branch.
- .state-awaiting_cert / .state-awaiting_ship modifier classes
match the existing border-left pattern.
XML (plant_kanban.xml):
- Two new KPI tiles + two new filter chips wired to the state
filter clauses.
Manifest: fusion_plating_shopfloor 19.0.33.2.0 → 19.0.34.0.0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
action_issue gated to Manager/QM/Owner via Python AccessError +
view-level groups= on the Issue button (two-layer enforcement).
Manager bypass via fp_skip_cert_authority_gate=True context flag
with chatter audit.
action_issue post-callback calls job._fp_check_advance_after_cert_issue
so the job auto-advances awaiting_cert → awaiting_ship when every
required cert is issued.
write({'state':'voided'}) override calls
job._fp_check_regress_after_cert_void so a previously-issued cert
being voided slides the job back to awaiting_cert and re-notifies
the QM.
x_fc_age_hours non-stored Float drives the Quality Dashboard age
chip + overdue filter.
Version bump 19.0.7.9.3 → 19.0.8.0.0 (spec said 19.0.6.0.0 but
current is already higher; bumped to next major instead).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
button_mark_shipped: manual transition awaiting_ship → done. Does
not re-run the bake/qty/QC gates — those passed at the in_progress
→ awaiting_cert/ship transition. Just the 'yes, shipped' stamp.
Milestone cascade (_compute_next_milestone_action) extended to
recognize the two new states:
- awaiting_cert → 'issue_certs' button
- awaiting_ship → 'mark_shipped' button
Legacy state='done' branch preserved for historical jobs.
action_advance_next_milestone now dispatches 'mark_shipped' via
_action_mark_shipped_dispatch which routes:
awaiting_ship → button_mark_shipped (new path)
done + active delivery → _action_mark_active_delivery_delivered
(legacy, unchanged)
View: 'Mark Shipped' milestone button gated on Manager/Owner groups.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pre-super: when finishing the last open step on an in_progress job,
run the bake/qty/QC gates from button_mark_done so failures surface
as UserError on the click (per spec D12). Without this the
auto-advance would silently fail with no error path.
Post-super: trigger _fp_check_advance_post_shop so the state
auto-advances cleanly (in_progress → awaiting_cert / awaiting_ship).
Added _fp_check_finish_gates helper on fp.job and a
fp_check_gates_only context flag honored by button_mark_done so the
gate logic is single-sourced (DRY).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
_fp_check_advance_post_shop: in_progress + all steps terminal →
awaiting_cert (cert required) or awaiting_ship. Auto-spawns cert
+ delivery and fires notifications. Idempotent. Does NOT raise —
gate failures bubble up via fp.job.step.button_finish (Task 4).
_fp_check_advance_after_cert_issue: awaiting_cert → awaiting_ship
when every required cert is state=issued.
_fp_check_regress_after_cert_void: awaiting_ship → awaiting_cert
when a previously-issued cert is voided. Re-notifies QM.
hasattr guards on _fp_schedule_cert_activity + _fp_resolve_cert_activities
keep this safe during incremental rollout — those land in Task 20.
Test scaffolding added covering helper existence + idempotency.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per spec docs/superpowers/specs/2026-05-25-post-shop-cert-shipping-job-states-design.md.
Selection extension only; transitions wired in subsequent tasks.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Trigger: WO-30058 (SO-30058) finished all recipe steps on entech and
disappeared from the Shop Floor kanban with a draft CoC and no QM
notification. Operators reported jobs feeling lost; risk that a job
could leave the building without paperwork.
Design (Approach A, approved):
- Two new fp.job.state values between in_progress and done:
awaiting_cert + awaiting_ship
- Auto-advance on last step finish; auto-advance on cert issue
- Plant kanban widens domain, renders the two states in the existing
Final inspection / Shipping columns
- 6th tab 'Certificates' on Quality Dashboard with kanban + filters
- ACL gate on fp.certificate.action_issue restricted to
Manager / QM / Owner (transitive via all_group_ids)
- Email + mail.activity notification to QM authority group
- Migration script backfills mid-flight jobs
Shipping label printing, BoL, carrier dispatch are explicitly
out of scope; awaiting_ship is a parking column with a manual
Mark Shipped button.
Self-review pass found and fixed:
- round-robin field ambiguity (last_activity_at vs login_date)
- unstated behavior for button_mark_done gates (now in step.finish)
- placeholder version inlined (19.0.11.0.0)
- dead reference replaced with inline body
Spec: docs/superpowers/specs/2026-05-25-post-shop-cert-shipping-job-states-design.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two layout polish fixes after persona-walk feedback on the new Plant
Kanban surface (`fp_plant_kanban`).
1. Columns now run full board height with visible borders
Was: `.col` had `background: $plant-bg` (= page bg, invisible) and
no border, so only the header card (`.o_fp_col_header`) drew any
outline. Empty columns (BAKING / DE-RACKING / SHIPPING) looked
unbounded — operators couldn't tell where one column ended and
another began.
Now: `.col` is the bordered white card (Trello / Asana style),
stretches full height via grid + flex. `.o_fp_col_header` drops
its standalone border / radius / background and is just a bottom-
divider band inside the column card.
2. Horizontal scrollbar pinned to viewport bottom
Was: `.o_fp_plant_kanban` was `min-height: 100vh` (block flow) +
`.board` had `min-height: 520px; overflow-x: auto`. Scrollbar
showed at the bottom of the .board element (~520px from top of
board), floating mid-page below the empty columns.
Now: parent is `height: 100vh; display: flex; flex-direction:
column`. Header is `flex: 0 0 auto`; `.board` is `flex: 1 1 auto;
min-height: 0` so it fills all remaining vertical and its
scrollbar sits at the viewport bottom.
`.col-scroll` switched from `max-height: calc(100vh - 260px)` to
`flex: 1 1 auto; min-height: 0` so it expands inside the now-full-
height column instead of being capped at a magic number.
Version: fusion_plating_shopfloor 19.0.33.1.13.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Four fixes shipped together — all surfaced during tablet UX walkthrough
on entech.
1. sale.order ACL on step completion
Technicians hit "Access Denied... sale.order" when starting/finishing
any step. _fp_check_receiving_gate + the serial-promotion helpers +
_fp_resolve_contract_review_part read step.job_id.sale_order_id (and
sale_order_line_ids) without sudo. Per Rule 13m, denormalized cross-
module reads in tablet controllers must sudo() the source recordset.
2. Workspace stuck on "Loading Job Workspace…" after Hand Off + relogin
Action params aren't URL-encoded, so the workspace remounts with
jobId=null. refresh() exited early, state.data stayed null, "Loading"
shown indefinitely. onMounted now redirects to the plant kanban
when jobId is null or the initial load returns no data.
3. 4-hour timer offset on active steps
workspace_controller used fp_format() to serialize date_started —
which converts naive UTC to user tz wall time first. JS then
appended 'Z' and parsed as UTC, so timer was offset by the user's
tz (4h on EDT). Switched to fp_isoformat_utc() (proper +00:00 ISO)
and dropped the Z-append in formatActiveStepElapsed +
isActiveStepOvertime.
4. Lock-screen clock follows FP regional setting
tablet_lock.js used d.getHours() / d.toLocaleDateString() — browser
tz. Now /fp/tablet/tiles returns tz_name (fp_user_tz resolution:
user.tz → company.x_fc_default_tz → UTC) and the formatters use
Intl.DateTimeFormat with the explicit timeZone option. plant_overview
now consumes server_time (already fp_format'd) instead of toLocaleTime
String. Same chain Odoo backend uses, so PDF / view / tablet all
agree on what time it is.
Versions: fusion_plating_jobs 19.0.10.30.0,
fusion_plating_shopfloor 19.0.33.1.12.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per user request: technicians on the tablet should only see Discuss,
To-do, Plating, AI, Maintenance, Time Off. Every other top-level app
menu (Calendar, Contacts, CRM, Sales, Dashboards, RC, Faxes, Field
Service, Fusion Clock, Invoicing, Accounting, Project, Timesheets,
Planning, Shipping, Website, Purchase, Inventory, Sign, HR, Payroll,
Attendances, Recruitment, Expenses, IoT, Link Tracker, Apps) is now
restricted to a new group_fp_office_user.
Architecture:
- New group_fp_office_user (security/fp_menu_visibility.xml) — a
marker group that controls back-office menu visibility.
- Owner / Manager / Quality Manager / Shop Manager / Sales Rep all
imply office_user via implied_ids — they see everything they did
before.
- Pure Technicians do NOT imply office_user — they see only the
tablet-friendly menus.
- A "!technician" filter would have hit managers too (because Manager
→ ... → Technician via implication), so office_user is the inverse
pattern that gets the right scoping.
Implementation:
- post_init_hook + migrations/19.0.21.4.0/post-migrate.py both call
_fp_apply_office_user_menu_visibility(env) which iterates a curated
list of menu xmlids and sets group_ids = [office_user] on each.
- Uses env.ref(..., raise_if_not_found=False) so menus from
uninstalled modules silently skip — no hard depends added.
- ir.ui.menu uses `group_ids` in Odoo 19 (was groups_id pre-18 — same
rename pattern as res.users; CLAUDE.md Rule 13c).
- Settings / Apps / Tests left untouched (already admin-restricted).
- Some menus (Field Service) end up with office_user OR their original
group — that's correct behavior: Plating Techs have neither so still
don't see them; explicit Field Technicians keep access.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds [-] / [+] buttons around every numeric input in the Record Inputs
dialog (single-value, dual-entry, and pass_fail+range branches). Tap
to increment / decrement by the recipe-author-derived step size
(stepFor() already computes this from target_min/target_max precision,
falling back to input-type defaults).
- Decrement clamps at 0 (typical qty/time/temp on a plating floor
doesn't go negative; if needed, operator can still tap the input
and type a negative value)
- Increment uses _stepRound() to avoid floating-point fuzz on decimals
- Center-aligned monospace-ish input between the buttons for clarity
- inputmode='decimal' (or 'numeric' for time fields) hint so when the
operator does tap the input, the iPad shows a number keypad instead
of the full keyboard
Touches single-value, dual-entry (min/max), and pass_fail+range. Other
multi-field widgets (multi-point thickness, bath chemistry panel) still
use plain inputs — separate request if they need steppers too.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Native browser confirm popups look out of place in the tablet UI.
Mark Counted is already a deliberate prior step, so requiring a
second confirmation on Close Receiving was just friction. If a
receiver hits Close prematurely, action_reset_to_counted on
fp.receiving from the back office is the recovery path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two bugs:
1. Gate silently passed when step.recipe_node_id was NULL — happened
to every WO-30057 step after this morning's clone delete (the FK
ON DELETE SET NULL wiped the link). _fp_missing_required_step_inputs
returned an empty recordset when node was None, so the gate had
nothing to fail on and button_finish succeeded with zero audit.
Fix: _fp_check_step_inputs_complete now treats NULL recipe_node_id
as an explicit "no recipe link" hard block. Operator can't finish;
manager bypass posts chatter audit.
2. No tablet UI for the manager bypass. The gate's bypass was a
Python context flag — invisible from the JS layer, so managers
were stuck behind the same hard error as operators.
Fix: new /fp/workspace/finish_step endpoint returns structured
errors (gate type, missing_prompts list, bypass_available bool).
Server-side enforces manager group when bypass=True (can't trust
the client). New FpFinishBlockDialog OWL modal renders:
- Non-manager: Cancel + Record Inputs
- Manager: Cancel + Record Inputs + ⚠ Bypass & Finish (audit)
JobWorkspace.onFinishStep routes plain finishes through the new
endpoint; signature-required steps still go through /fp/workspace/sign_off
(separate gate). Added is_manager to /fp/workspace/load payload so
the JS knows which dialog variant to render.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pure client-side tick — 1s setInterval bumps state.tickNow which the
template reads via formatActiveStepElapsed(step). No RPC per tick.
Reads step.date_started_iso (UTC) from the existing payload, parses
to ms, displays elapsed since.
- Green pill (#d1fae5 bg, monospace tabular-nums) on the ACTIVE badge
- Flips red (#fee2e2 + pulse animation) when elapsed > 1.5x
duration_expected — visual cue for the operator that the step is
running long against the recipe target
Cleanup interval on onWillUnmount alongside the existing 15s refresh
interval.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the receiver workflow to the Job Workspace tablet view (was the
gap behind WO-30057 sitting in Receiving with no way to advance).
Receivers no longer need to go to the backend form.
Workspace card (renders above the step list when fp.receiving in
state draft/counted on the linked SO):
- Draft state: numeric box-count input + per-line received_qty /
condition picker (good/damaged/mixed) + Damage Log panel + Mark
Counted button. Autosaves on input blur.
- Counted state: read-only summary (boxes, parts, who/when) +
Damage Log still editable + Close Receiving button.
- Closed: card disappears, recipe takes over.
New FpDamageDialog OWL modal:
- Severity pill picker (Cosmetic / Functional / Rejected) with
color-coded active state
- Required description textarea
- Action Required pill picker (None / Notify / Return / As-Is)
- Photo capture: both "Take Photo" (input capture="environment"
triggers tablet camera) AND "Upload" (file picker fallback).
Multi-photo with preview grid + per-photo remove.
5 new endpoints on workspace_controller.py:
- receiving_save_lines (autosave box_count_in + per-line qty/cond)
- receiving_mark_counted (wraps action_mark_counted)
- receiving_close (wraps action_close)
- damage_create (creates fp.receiving.damage + attaches base64 photos)
- damage_delete (removes a damage row)
No model changes — wraps existing fp.receiving actions and damage
CRUD. C3 (outbound shipping carrier/label) is a separate spec.
Spec: in-conversation brainstorm (C1+C2) following the 2026-05-24
workspace step actions spec; no standalone doc since scope is small.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Fix: in the Job Workspace tablet view, the Start button was buried
inside a parent t-if that required the step to already be in_progress
or blocked. So ready/paused steps showed no buttons at all -
operators couldn't advance the WO from this screen (the reason the
user couldn't complete anything on WO-30057).
Template restructure (job_workspace.xml):
- Always-visible line 1 (icon + step# + name + ACTIVE/PAUSED badge + meta)
- Non-terminal detail panel (chips + instructions + opt-out + GateViz)
visible on every non-done step so operator reads ahead
- Action row dispatched per-kind via getStepActions() helper
Per-kind action dispatcher (job_workspace.js):
- in_progress -> Record Inputs, Pause, Finish (or Finish & Sign Off)
- paused -> Resume, Record Inputs, Finish
- contract_review (ready) -> Open QA-005 Form
- gating (ready) -> Mark Passed (1-click start+finish)
- requires_rack_assignment -> Start (Assign Rack) - opens FpRackPartsDialog
- else (ready) -> Start
5 new handlers: onPauseStep / onResumeStep / onMarkPassed /
onOpenContractReview / onStartWithRack. Pause and Resume use ORM RPC
(button_pause/button_resume) since no HTTP endpoint exists.
New model method (fp.job.step.action_mark_gating_passed):
- 1-click pass for gating steps - does button_start + button_finish
in one transaction, posts chatter "Gate X marked passed by Y"
- Raises UserError if called on a non-gating step (defensive)
- Bypasses S21 required-inputs gate (gating steps have no inputs)
Controller: workspace_controller.py adds requires_rack_assignment to
the step payload so the JS dispatcher can route correctly.
Spec: docs/superpowers/specs/2026-05-24-workspace-step-actions-design.md
Sub-B (Record Inputs tablet polish: inputmode/prefill/date pickers/
signature pad/camera) is brainstormed but deferred.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Root causes documented:
1. Recipe 3620 ENP-ALUM-BASIC had duplicate sequences (Contract
Review + Masking both at seq 10; Incoming Inspection + Racking
both at seq 20). Clones inherited the ambiguity and resolved by
id ordering, putting Masking before Incoming Inspection — which
meant new jobs went straight to Plating column after the
contract-review auto-complete.
2. 24 per-part clone recipes accumulated, all carrying the broken
ordering.
3. ~10 kind=other stragglers across the base recipes (Blasting,
Adhesion Test Coupon, Strip Process, Chemical Conversion etc.)
4. Recipe duplication had no kind safety net.
Implementation shipped in commits referenced from the plan.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two follow-up fixes caught during the entech deploy of recipe cleanup:
1. RESOLVER_KIND_TO_ACTIVE_KIND was missing a self-pass entry for
'wet_process'. The new aliases added in 19.0.21.3.0 (Chemical
Conversion, Trivalent Chromate Conversion, Strip Process - AL,
Plug The Threaded Holes via mask) directly return 'wet_process'
from the resolver — without the passthrough they didn't translate
to any active kind and stayed as 'other'. Added 'wet_process':
'wet_process' so the migration's Phase 2 backfill catches them.
2. Migration 19.0.10.26.0 Phase 3 was using batch unlink
(clone_recipes.unlink()) which tripped a PostgreSQL FK cascade
ordering bug on entech ("insert or update on parent_id violates
FK ..." during the CASCADE chain). Rewrote Phase 3 to delete one
clone at a time with SAVEPOINT per clone — slower but immune to
the batching bug, and one failed clone doesn't roll back the
whole transaction.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Migration 19.0.10.26.0/post-migrate.py runs in 5 phases:
1. Resequence recipe 3620 ENP-ALUM-BASIC ops to fix the duplicate-
sequence bug (Contract Review=10, Incoming Inspection=20,
Masking=30, Racking=40, then the rest). Also delete the empty
duplicate ENP-Alum Line sub_process (id 4056).
2. Backfill kind on all kind=other nodes via the extended resolver
from fusion_plating 19.0.21.3.0
3. Delete all per-part clone recipes (name contains em-dash)
4. Recompute fp.job.step.area_kind on all steps
5. Recompute fp.job.active_step_id + card_state on in-flight jobs
Plant kanban: no_parts cards now always land in the Receiving column
regardless of active_step area_kind. The receiver works Receiving;
that's where the card belongs when parts haven't arrived.
Spec: docs/superpowers/specs/2026-05-24-recipe-cleanup-design.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Resolver (fp_resolve_step_kind) extensions:
- New aliases: blasting/bead blast/media blast variants, adhesion
testing, corrosion testing, lab testing, strip process, chemical
conversion, trivalent chromate, plug the threaded holes, air dry,
desmut, soak clean, cleaner, nickel strike/strip
- Parenthetical suffix stripping - "Masking (If Required)" resolves
through "masking", "Incoming Inspection (Standard)" through
"incoming inspection", "Trivalent Chromate Conversion (A-14 / A)"
through "trivalent chromate conversion"
- New RESOLVER_KIND_TO_ACTIVE_KIND map translates the resolver's
vocabulary (cleaning/electroclean/etch/rinse/strike/dry/wbf_test
-> wet_process) so the resolver output lands on active kinds only
Auto-classify hook on fusion.plating.process.node:
- _fp_autoclassify_kind() upgrades kind_id when current is 'other'
AND name resolves via the resolver. Idempotent - never overrides
a non-'other' kind. Skip via context flag fp_skip_kind_autoclassify
- Wired into create() and write() (only fires when name or kind_id
changed on write)
- Side-effects: recipe duplication via copy() auto-corrects newly
copied nodes; Simple/Tree editor authoring auto-classifies as soon
as the name is saved
Spec: docs/superpowers/specs/2026-05-24-recipe-cleanup-design.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Required because fp.job.card_state (stored) has @api.depends including
active_step_id.area_kind. When step.area_kind changes, Odoo's trigger
chain searches fp.job by active_step_id — non-stored fields can't be
queried in WHERE clauses, raising ValueError("Cannot convert ... to
SQL because it is not stored").
Caught during entech deploy of 19.0.10.25.0/post-migrate.py Phase 3
(steps._compute_area_kind() failed on first run). store=True makes
the column searchable and the trigger chain works.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- fp.step.kind.area_kind: drop tracking=True (model doesn't inherit
mail.thread; tracking was a no-op emitting a startup warning).
- Migration 19.0.10.25.0: anchor the De-Masking ILIKE so it doesn't
wildcard-match "Ready For De-Masking" (which the earlier "Ready %"
rule already routes to gating). Also drop the cur_code='mask' filter
so the 4 De-Masking nodes still classified as 'other' get picked up
on fresh re-runs too.
Direct SQL applied the 4-row fix on entech (post-migrate doesn't
re-run for already-applied versions); this commit keeps fresh
installs and any future re-runs consistent.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Fix the Shop Floor plant kanban so cards land in the right column:
- fp.job._compute_active_step_id walks priority chain
(in_progress > paused > ready > pending), not just in_progress
- fp.job._compute_card_state edge case respects job.state='done'
(no more bogus 'contract_review' label on done jobs)
- fp.job.step._compute_area_kind reads kind.area_kind directly;
legacy _STEP_KIND_TO_AREA dict removed (50+ lines deleted)
- /fp/landing/plant_kanban filters out done/cancelled jobs from
the live board
Migration 19.0.10.25.0 backfills template metadata (codes,
descriptions, icons, kind_id) on 30 unfinished library templates
and repoints recipe nodes for 6 unambiguous name patterns
(Blasting -> blast, Ready For X -> gating, De-Masking -> demask,
Scheduling -> gating, Nickel Strip -> wet_process,
Pre-Meas/Check Sulfamate -> inspect).
Battle test bt_s24_between_steps.py covers between-step routing,
paused step lifecycle, and done-job board filter.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add required area_kind Selection to fp.step.kind so each kind
self-declares which plant-view column its steps belong in. Replaces
the hardcoded _STEP_KIND_TO_AREA dict (removed in fp_job_step.py
in the follow-up commit).
- New `blast` kind for the Blasting column (sequence=35)
- 26 existing kind records seeded with area_kind in XML
- Pre-migrate 19.0.21.2.0 seeds existing rows BEFORE NOT NULL hits
the schema; also activates derack/demask/gating that were
deactivated in 19.0.20.6.0 but are needed for the full taxonomy
- Step Kind form + list views surface area_kind (badge + chip)
- Step Kind search adds Group By Shop Floor Column
- Simple Editor kind picker shows "Masking — Masking column"
suffix so authors see the routing at pick time
- Add Hot Water Porosity Test (A-15) + Final Inspection / Packaging
templates (used by 7+3 recipe nodes that previously had no
library entry)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Odoo 19's Session.authenticate(env, credential) takes an Environment as
the first arg, not a db-name string. Passing request.db triggered
TypeError: 'str' object is not callable on the internal
env(user=None, su=False) reset.
Fixes the "Odoo Server Error" dialog operators saw when trying to PIN
unlock from the tablet. Same fix applies to lock_session (which was
silently masked by its broad except Exception).
Bumps fusion_plating_shopfloor to 19.0.33.1.2.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pure code change (no DB schema), but bumping the patch version
keeps repo manifest aligned with the deployed state so the next
-u doesn't no-op due to version match.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The kiosk_login in /fp/tablet/lock_session was hardcoded to the
data XML's original value ('fp_tablet_kiosk@enplating.local'). The
data record is noupdate='1', so admins can (and on entech, did)
rename the kiosk user on the form for memorability — the rename
persists through -u, but the hardcoded string in the controller
silently breaks the re-auth-as-kiosk path.
Fix: resolve the kiosk login dynamically via env.ref of the xmlid
'fusion_plating_shopfloor.user_fp_tablet_kiosk'. Robust against any
future rename. CLAUDE.md updated to make 'identify by xmlid, never
by login string' an explicit convention for the tablet flow.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Tablet PIN session redesign Phase G removed all tablet_tech_id
plumbing. CLAUDE.md still documented the old session-pool + kwarg
flow which would mislead future-Claude. Updated to describe the
new per-tech-session attribution + kiosk re-auth flow, plus the
gotcha about keeping ir.config_parameter['fp.tablet.kiosk_password']
in sync with the actual user-record password.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Records the legacy-tablet-flow-removed state. Triggers -u so the
module's installed version reflects the post-cleanup code (the
ir_module_module row shows 19.0.33.1.0 after deploy).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Frontend cleanup completing Phase G of the tablet PIN session
redesign:
- tablet_lock.js: removed sessionMode branching (no legacy path).
unlock() always calls /fp/tablet/unlock_session + reloads.
handOff() always calls tabletSessionManager.lockBack('manual').
isLocked uses currentUid vs kioskUid exclusively. _checkIdle
still drives the warning UI via activity_tracker; the actual
lock RPC is owned by tablet_session_manager.
- fp_rpc.js: simplified to a thin async pass-through around @web/core
network rpc. tech_store-based tablet_tech_id injection is gone
(the session uid IS the tech).
- tech_store.js: DELETED (replaced by per-session backend attribution
+ tablet_session_manager for lock state). Removed from manifest.
- Wrapper components (shopfloor_landing, job_workspace,
manager_dashboard, plant_kanban): swapped useService('fp_shopfloor_tech_store')
for useService('fp_tablet_session_manager'); techStore.lock() ->
tabletSessionManager.lockBack('manual'). plant_kanban's defensive
try/catch on the tech_store lookup is no longer needed.
- tablet_lock.xml: Hand-Off button no longer gated on sessionMode;
always rendered.
- Tests: removed legacy TestTabletUnlock class from test_tablet_pin.py
(covered the deleted /fp/tablet/unlock route). Dropped session_mode
assertion from test_tiles_bootstrap_fields.py (the return key is
gone post-Phase-G). kiosk_uid + current_uid assertions retained.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Session-swap is now the only flow. Legacy /fp/tablet/unlock endpoint
deleted. _tablet_audit.py (env_for_tablet_tech helper) deleted with
its last caller gone. /fp/tablet/ping no longer takes current_tech_id
(session uid IS the tech). /fp/tablet/tiles drops tablet_session_mode
return key (kiosk_uid + current_uid retained for OWL isLocked logic).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>