8 distinct bugs caught + fixed while testing the live admin DB on entech
after the migration was approved. Each surfaced a real Odoo 19 gotcha
now codified in CLAUDE.md (rules 13b-13l).
Picker architecture:
- res.users.x_fc_plating_landing_action_id and res.company.x_fc_default_landing_action_id
now Many2one('ir.actions.actions') instead of ('ir.actions.act_window'),
so the picker accepts BOTH window actions (Sale Orders / Quotations /
Process Recipes) AND client actions (Manager Desk / Plant Kanban /
Quality Dashboard). Picker went from 3 entries to 6.
- x_fc_pickable_landing field moved from the two subclasses to the
ir.actions.actions base. Single source of truth.
- _render_resolved on the base dispatches to the correct subclass by
action type.
Non-admin Preferences access:
- Added ACL grant: group_fp_technician (and all higher roles via
implication) get read on ir.actions.actions. Without this, opening
Preferences raised AccessError on the picker domain evaluation.
- Removed the accessible_landing_action_ids Many2many compute (failed
for non-admins because field assignment requires write access on
the comodel relation, even with sudo'd search). Picker now shows all
6 entries to all users; resolver falls through gracefully if the
user picks an action they can't reach.
- res.users SELF_WRITEABLE_FIELDS / SELF_READABLE_FIELDS extended via
@property + super() (NOT class attribute — Odoo 19 changed the
pattern). Non-admin users can now save the Preferences dialog with
plating fields without hitting the standard write ACL.
Migration workflow:
- res.groups.users -> .user_ids (Odoo 19 rename; deprecated alias
removed). Was crashing _fp_notify_owners and _cron_purge_expired.
- user.message_post -> user.partner_id.message_post (res.users uses
_inherits delegation which doesn't expose mail.thread methods).
Was crashing the Owner approval click.
Tablet lock screen:
- /fp/tablet/tiles points at group_fp_technician instead of the old
group_fusion_plating_operator. Post-migration nobody holds the old
group directly (only via implication), so res.groups.user_ids on
the old xmlid returned empty — 'No operators configured' shown
even with PIN set.
- PIN pad dots dark mode: empty dot now dark gray (#424245), filled
dot now pure white. Previous version had both at light shades so
user couldn't see PIN entry progress.
- Lock-screen logo frame dark mode: near-opaque white plate
(rgba 0.95) so company logos designed for light backgrounds
render correctly. Previous 0.08 alpha let the dark page bleed
through.
Pre-deploy collision fix (already committed before deploy but
documented here for completeness):
- pre-migrate.py to rename old configurator's 'Shop Manager' group
display name before new fp_security_v2.xml loads the new
group_fp_shop_manager_v2 with the same display name (avoids
res_groups_name_uniq violation).
Module versions bumped:
fusion_plating: 19.0.21.1.0 -> 19.0.21.1.2
fusion_plating_shopfloor: 19.0.32.0.4 -> 19.0.32.0.6
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 fixes discovered during the live deploy to entech LXC 111:
1. pre-migrate.py to rename old configurator's 'Shop Manager' group BEFORE
new core 'Shop Manager v2' XML loads (cross-module name collision on
res_groups_name_uniq).
2. res_company_views.xml: dropped ref() inside <field domain=> attribute
(Odoo 19 view validator interprets it as a field name).
3. sale_order_views.xml: replaced 3 separate xpaths for amount_total /
amount_untaxed / amount_tax with a single xpath on tax_totals widget
(Odoo 19 sale.view_order_form uses one widget instead of separate fields).
4. fp_cert_security.xml: certificate_type field, not cert_type. FAIR is a
separate model so the rule only restricts cert_type='nadcap_cert' now.
5. fp_certificate_views.xml + fp_capa_views.xml + fp_customer_spec_views.xml:
stripped user_has_groups() from invisible= / readonly= attrs (Odoo 19
view validator interprets as field name). Model-layer ACLs and ir.rules
already enforce the same restrictions.
Also fixed res.groups.users -> user_ids in fp_migration.py (Odoo 19 rename,
caught when manually invoking _fp_notify_owners post-deploy).
CLAUDE.md updated with 4 new rules (13e cross-module name collisions,
13f ref() in domain, 13g tax_totals widget, 13h user_has_groups in attrs).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pre-deploy fixes for Phase 1 permissions overhaul branch (catches by
final-reviewer subagent + main session).
CRITICAL FIXES:
C1: groups_id -> group_ids (Odoo 19 field rename). Affected ~30 sites
across 4 model files, 1 view, 7 test files. Documented project
gotcha (feedback_odoo19_groups_id_renamed.md) that the implementer
subagents missed because they don't see user memory.
C2: action_fp_resolve_plating_landing server action now calls
env['ir.actions.act_window'].sudo()._fp_resolve_landing_for_current_user()
instead of the old inline priority chain. Phase E's role-based
dispatch was previously dead code.
C3: New migrations/19.0.21.1.0/post-migrate.py triggers
_fp_post_init_role_migration(env) on -u. post_init_hook only fires
on INSTALL in Odoo 19, not UPGRADE -- so Phase H's preview creation
wouldn't have auto-fired on entech without this script. Module
version bumped to 19.0.21.1.0 to match the migration directory.
C4: Team kanban template rewritten for Odoo 19 (<t t-name='card'> with
semantic <aside>/<main>) instead of legacy <t t-name='kanban-box'>.
Previous template threw 'Missing card template' at render.
IMPORTANT FIXES:
I1: SO state=sent Confirm button (id='action_confirm') now also gated
to group_fp_sales_manager. Previously only the state=draft button
was gated; Sales Reps could send-and-confirm via the secondary path.
I2: Designated Officials picker domain uses all_group_ids (transitive)
instead of group_ids (explicit only). Owner users now correctly
appear as eligible CGP DO candidates via the implied_ids chain.
I3: test_menu_visibility.py compliance hub xmlid corrected to
fusion_plating.menu_fp_compliance_hub (was
fusion_plating_compliance.menu_fp_compliance_hub which doesn't exist
-- the hub menu is defined in core's fp_menu.xml). Tests were
silently skipTest-ing.
I4: _inverse_plating_role chatter audit reads old role from DB via SQL
(bypasses cache) so 'old -> new' displays actual values, and
short-circuits no-op writes.
I5: _FP_ROLE_MAPPING_RULES reordered: cgp_designated_official fires
BEFORE admin/uid_1_or_2 so admin+DO users keep the capability_delta
marker that triggers res.company.x_fc_cgp_designated_official_id
auto-set during migration.
I6: _cron_purge_expired_migrations skips groups with active users
instead of unlink-ing unconditionally. Defense against rollback
safety being bypassed by manual role assignments post-migration.
CLAUDE.md updated with 3 new durable rules (13b kanban card template,
13c group_ids vs all_group_ids, 13d post_init_hook only on install).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase H of permissions overhaul (LAST subagent phase).
New models:
- fp.migration.preview (state: pending/approved/cancelled/rolled_back)
- fp.migration.preview.line (one per active internal user)
On -u, post_init_hook creates a preview in 'pending' state, walks all
active non-share users through the 12-rule mapping predicate chain
(first match wins, highest precedence first), and schedules a
mail.activity on every Owner.
Mapping table (per spec Section 5):
uid 1/2 / Administrator -> owner
CGP DO (existing) -> owner + res.company DO field set
CGP Officer -> quality_manager
Manager / Shop Mgr (old) -> manager
Accounting -> manager
Estimator-without-Manager -> sales_rep (flagged: loses confirm)
Supervisor / Receiving -> shop_manager
Operator -> technician
catchall -> 'no'
Owner clicks 'Approve & Run' on the preview form -> sudo write removes
old plating groups, adds new role's group, posts Markup chatter audit.
Optionally sets res.company.x_fc_cgp_designated_official_id for the DO.
30-day rollback window via JSON snapshot of groups_id per line. Daily
cron (Fusion Plating: Purge Expired Role Migrations) clears snapshots
+ unlinks old [DEPRECATED] groups after 30 days.
ACL: fp.migration.preview + .line both Owner-only (CRUD).
Menu: Plating > Configuration > Role Migrations (Owner-only).
Tests cover: only-Owner-can-approve, approve advances state, cancel
blocks after approval, rollback restores groups_id, Estimator warning
flagged, uid 2 maps to owner, rollback blocked after 30 days.
Per CLAUDE.md: ir.cron uses only Odoo-19-valid fields (no numbercall,
no doall). Post-init hook is idempotent — won't double-create previews
or re-fire if all users already migrated.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase G of permissions overhaul.
G2: sale.order.action_confirm now requires group_fp_sales_manager
(spec Section 2.B). Sales Reps can save drafts but cannot move SOs
to 'sale' state. UserError raised with clear message if attempted.
G3: Fixed audit-finding-11 typo bug in 2 files. The original code
checked has_group('fusion_plating.group_fusion_plating_administrator'),
an xmlid that has NEVER existed - so the gate always returned False
and only the Manager-side check actually fired. Fixed both:
- fusion_plating_invoicing/models/res_partner.py:34
- fusion_plating_configurator/wizard/fp_direct_order_wizard.py:467
Both now check has_group('fusion_plating.group_fp_manager') which
transitively includes Owner via implied_ids.
G4: Swept all Python has_group() calls to reference new group xmlids.
Backward-compat keeps old refs working today (Phase A's implied_ids),
but the sweep ensures correctness after the 30-day rollback window
deletes old groups. Replacements:
group_fusion_plating_operator -> group_fp_technician
group_fusion_plating_supervisor -> group_fp_shop_manager_v2
group_fusion_plating_manager -> group_fp_manager
group_fusion_plating_admin -> group_fp_owner
group_fusion_plating_cgp_officer -> group_fp_quality_manager
group_fusion_plating_cgp_designated_official -> group_fp_owner
group_fp_estimator -> group_fp_sales_rep
group_fp_accounting -> group_fp_manager
group_fp_receiving -> group_fp_shop_manager_v2
group_fp_shop_manager (legacy) -> group_fp_manager
G1: test_sales_manager_gate.py covers the new confirm gate (SR
blocked, SMg allowed, Manager allowed via diamond implication).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase F of permissions overhaul.
Adds res.users.x_fc_plating_role Selection field (8 options matching
the role hierarchy). Compute reads highest plating group from
groups_id (precedence: owner > QM > manager > sales_manager >
shop_manager > sales_rep > technician). Inverse uses sudo().write()
to clear all plating-role groups (additive-by-default m2m (3, id))
then adds the chosen one, and posts a Markup-wrapped chatter audit
naming the actor.
New Owner-only menu: Plating > Configuration > Team. Standard
res.users kanban grouped by x_fc_plating_role with records_draggable
for drag-and-drop role changes. Domain hides shared/portal users
and archived users.
res.company gains two Designated Official fields:
- x_fc_cgp_designated_official_id (CGP DO per Defence Production Act §22)
- x_fc_nadcap_authority_user_id (Nadcap signer)
Both tracking=True for audit. View-level domain restricts picker to
Owner or Quality Manager users via [(ref('...'), ref('...'))] xmlid
domains. New 'Plating Designated Officials' page on res.company form,
Owner-only visibility.
Tests in test_team_page.py cover compute/inverse/chatter/menu.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase E of permissions overhaul. The landing resolver now dispatches
based on the user's highest role (per spec Section 3):
Owner -> Manager Desk
Quality Mgr -> Quality Dashboard
Manager -> Manager Desk
Sales Manager -> Sale Orders
Shop Manager -> Plant Kanban (v2 layout) or Workstation (legacy)
Sales Rep -> Quotations
Technician -> Plant Kanban / Workstation
User override (x_fc_plating_landing_action_id) still wins; company
default and hardcoded Sale Orders are fallbacks. Layout-flag-aware via
ir.config_parameter['fusion_plating_shopfloor.layout'] (v2 vs legacy).
x_fc_pickable_landing field added to BOTH ir.actions.act_window AND
ir.actions.client (Manager Desk / Plant Kanban / Quality Dashboard
are client actions). Resolver helper polymorphically calls
_render_resolved() on either model.
Tagged 3 of 4 new actions pickable: Manager Desk, Plant Kanban,
Quality Dashboard. (action_fp_shopfloor_landing doesn't exist as an
XML record — it's a JS component name only; legacy layout falls
through to company default gracefully via raise_if_not_found=False.)
Tightened picklist domain to filter by user accessibility (Technician
no longer sees Manager Desk in the dropdown). New compute field
res.users.accessible_landing_action_ids runs check_access_rights on
each pickable action.
Tests in fusion_plating/tests/test_landing_resolver.py.
CLAUDE.md updated with two durable rules:
- x_fc_pickable_landing lives on BOTH act_window and actions.client
- Role-based dispatch precedence and helper API
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase D Task D5 of permissions overhaul. Adds explicit groups= to
form-level elements so non-matching roles don't even SEE the buttons
they can't use:
- SO Confirm button → group_fp_sales_manager (Sales Rep sees the SO
in draft but no Confirm button — matches model-level gate from Phase G)
- SO pricing fields (price_unit/subtotal/total/untaxed/tax) →
group_fp_sales_rep (Technician/Shop Manager don't see pricing if
they navigate to an SO)
- Partner Account Hold tab → group_fp_manager (was the fold-in
group_fp_accounting; the audit-finding-11 _administrator typo lives
in res_partner.py and is Phase G's fix)
- CAPA Close + all state-transition buttons → group_fp_quality_manager;
edit fields use readonly="not user_has_groups(...)" so Manager
retains read+comment per spec section 2.C
- Audit Start/Findings/Close buttons → group_fp_quality_manager
- AVL Approve/Suspend/Reinstate/Remove → group_fp_quality_manager
(model uses Suspend+Remove instead of spec's literal 'Disqualify';
both surfaces gated, semantics match)
- Customer Spec edit fields → readonly for non-QM (Manager keeps
read access per spec; only inputs lock)
- FAIR Approve/Reject buttons → group_fp_quality_manager (Submit-
for-Review and Reset stay open to whoever created the FAIR)
- Certificate Issue button — Strategy B chosen: single button hidden
when cert_type=nadcap_cert AND user is not QM. Cleaner than splitting
into two buttons; no separate action_sign exists on fp.certificate
(Issue is the sign+publish action). FAIR lives in its own model;
fp.certificate only has nadcap_cert as a special type. The ir.rule
from Phase C enforces model-level writes independently.
- CGP form buttons (7 view files: ai, controlled_good, psa,
receipt_shipment, registration, security_incident, visitor) →
group_fp_quality_manager on every action button
Defense in depth: ir.rules and ACLs (from Phases B + C) already
restrict model access. These view gates are the UI layer that
matches.
Concerns:
- Spec line 192 names 'sale.order view — x_fc_account_hold_override'
but no such field exists in the codebase. Closest practical match
was the partner-side Account Hold management tab, which already had
a group= attribute. Re-gated there; no SO-side field to gate.
- AVL model has no action_disqualify per spec; uses suspend+remove.
Both gated to QM.
- fp.certificate has no action_sign (only action_issue). FAIR's
approve/reject covers the FAIR side; nadcap-cert Issue covers the
cert side via Strategy B.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implementer concern from D1-D4 dispatch: plan template referenced
menu_fp_sales_root / menu_fp_shopfloor_root but actual xmlids drop
the _root suffix. Tests were silently skipping.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase D Tasks D1-D4 of permissions overhaul. Adds explicit groups=
attributes to:
- 9 top-level Plating menus (matrix per spec Section 2.E)
- Quality submenus: Audits, Customer Specs, AVL → QM-only
- Compliance hub child submenus (CGP, General, Safety, Aerospace,
Nuclear) → QM-only
- Operations submenus: Maintenance, Move Log, Labor History → Shop
Manager+; Replenishment Suggestions → Manager+
Replaces fragile inheritance + action-ACL-based visibility with
explicit per-menu gates. Now every role's menu tree is deterministic.
Also adds fusion_plating/tests/test_menu_visibility.py — per-role
matrix tests using ir.ui.menu.search_count with the test user.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implementer-flagged concern: plan template referenced fp.approved.vendor.list
but actual model id is fusion.plating.avl. Tests were silently skipping
instead of exercising the AVL split.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase C of permissions overhaul (spec Section 2.C).
Manager keeps reactive Quality (NCR/Hold/Check/Cert/RMA — already gated
via Phase B sweep). QM gains exclusive write/create/unlink on strategic
Quality records:
- fusion.plating.capa: Manager → read-only (1,0,0,0); QM → full
- fusion.plating.audit: same split (if model present)
- fp.approved.vendor.list: same split (if model present)
- fusion.plating.customer.spec: same split
- Doc Control models: same split
Plus FAIR/Nadcap cert restriction via two new ir.rule records on
fp.certificate:
- Manager: write/create/unlink on certs where cert_type NOT in
('fair', 'nadcap')
- QM: write/create/unlink on all certs (overrides via OR within group)
- Read access unchanged for both (perm_read=False on the rules)
Tests in fusion_plating/tests/test_quality_split.py verify each side
of the split. Models that may not exist on all DBs (audit, AVL) use
skipTest gracefully.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Phase B plan (commit 8eb2c2de) listed 12 modules to sweep, but the
codebase has 13 more modules with ACL CSVs referencing the old role
group xmlids. Backward-compat (Phase A's implied_ids chains) keeps
these working today, but the old groups will be deleted after the
30-day rollback window — so the sweep must cover ALL modules with
plating-group ACL refs to avoid post-rollback breakage.
Sweeps: batch, bridge_documents, bridge_maintenance, bridge_mrp
(uninstalled but file present), bridge_quality (planned removal),
bridge_sign, compliance, culture (retired), kpi, logistics,
notifications, portal, reports.
Pattern matches the original sweep:
group_fusion_plating_operator → group_fp_technician
group_fusion_plating_supervisor → group_fp_shop_manager_v2
group_fusion_plating_manager → group_fp_manager
group_fusion_plating_admin → group_fp_owner
group_fp_accounting → group_fp_manager
group_fp_receiving → group_fp_shop_manager_v2
group_fp_estimator → group_fp_sales_rep
group_fp_shop_manager (legacy) → group_fp_manager
cgp_officer → group_fp_quality_manager
cgp_designated_official → group_fp_owner
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase B of permissions overhaul. Mechanical text replacement across
11 ir.model.access.csv files:
- group_fusion_plating_operator -> fusion_plating.group_fp_technician
- group_fusion_plating_supervisor -> fusion_plating.group_fp_shop_manager_v2
- group_fusion_plating_manager -> fusion_plating.group_fp_manager
- group_fusion_plating_admin -> fusion_plating.group_fp_owner
- group_fp_estimator (configurator)-> fusion_plating.group_fp_sales_rep
- group_fp_accounting -> fusion_plating.group_fp_manager
- group_fp_receiving -> fusion_plating.group_fp_shop_manager_v2
- group_fp_shop_manager (legacy) -> fusion_plating.group_fp_manager
- group_fusion_plating_cgp_officer -> fusion_plating.group_fp_quality_manager
- group_fusion_plating_cgp_designated_official -> fusion_plating.group_fp_owner
Backward-compat: old group xmlids still resolve (Phase A's implied_ids
chains keep old ACLs working for users still holding old groups).
This sweep ensures future-state correctness: when old groups are deleted
after the 30-day rollback window, ACLs continue resolving via the new
group xmlids.
Also adds fusion_plating/tests/test_acl_migration.py with sample-based
per-role access checks. The 2 CAPA tests are expected to fail until
Phase C implements the Manager/QM quality split.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Group-structure tests for Phase 1 permissions overhaul. Covers:
- All 7 new res.groups records present (8th role "No" is implicit)
- Owner transitively implies base.group_system + every old group
- Manager forms the diamond (implies both Shop Manager and Sales Manager)
- Sales and Shop branches remain orthogonal at the leaf (Tech != Sales Rep)
- uid 1/2 auto-assigned to Owner
- Sequence numbers unique (renders dropdown predictably)
- New groups imply old for backward-compat (30-day rollback safety)
- Cross-module backward-compat chain works (e.g., Owner -> CGP DO)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous commit (a53b0326) added implied_ids in fp_security_v2.xml
that referenced 5 xmlids from downstream modules (configurator/receiving/
invoicing/cgp). Since fusion_plating is the BASE module and loads first
at fresh install, those refs raised External-ID-not-found at install.
Fix: relocate the 5 cross-module implications into each downstream module's
own security file via additive (4, ref()) writes to the core group's
implied_ids. Odoo's XML data loader treats these as additive updates so
they stack cleanly across install + -u cycles.
Also: drop redundant <data noupdate="0"> wrapper in fp_security_v2.xml
to match sibling fp_security.xml's bare <odoo> shape.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase A of permissions overhaul (see docs/superpowers/specs/2026-05-23-*).
New groups (technician/sales_rep/shop_manager_v2/sales_manager/manager/
quality_manager/owner) defined in fp_security_v2.xml with implied_ids
chains that include old groups for backward-compat during 30-day rollback
window. Old groups display as [DEPRECATED] in user form.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Spec describes consolidation of 12 res.groups into 8 roles (No / Technician /
Sales Rep / Shop Manager / Sales Manager / Manager / Quality Manager / Owner),
role-based landing-page defaults, Owner-only Team management page, and
dry-run + Owner-approval migration workflow.
Plan breaks the work into 9 phases (A through I), ~40 TDD tasks, with
explicit file lists and entech deploy commands.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two fixes from live testing of the 2026-05-24 redesign:
1. Job Workspace Back button routed to deprecated component.
onBack() hardcoded tag: 'fp_shopfloor_landing' so tapping a card on
the new plant kanban -> opening the workspace -> clicking Back
dropped the user into the OLD per-step kanban (the legacy OWL
component the data-record redirects don't reach because doAction
bypasses the data layer).
Fix: change the hardcoded tag to 'fp_plant_kanban'. Grep
confirmed it's the only such reference in JS.
2. Logo frame shape — wider, shorter, logo bigger.
140x140 square -> 280x110 rectangle. Better fit for horizontal
company logos (mark + name + tagline laid out left-to-right).
Uniform 18px padding on all sides so the image breathes evenly.
Image area is ~244x74 (vs old ~104x104), so a typical horizontal
logo renders ~50% wider. border-radius 28->22 for the flatter
rect; letter-mark placeholder font 52->44 to fit the shorter
frame.
Also augmented CLAUDE.md 'Legacy-action redirect' rule with a new
'grep JS for hardcoded doAction' clause — the XML-record redirect
trick only covers ir.actions.client data; OWL components with inline
this.action.doAction({tag: ...}) calls bypass the data layer entirely
and need a separate sweep.
Asset cache cleared (3 stale attachments).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User feedback after live testing: the 84px logo frame felt too small
and the image inside used only a fraction of the frame area.
Bumped the frame to 140px (1.67x) — image scales with the container
via the existing max-width/max-height: 100% rule. Proportional
adjustments to padding (14→18), border-radius (20→28), margin-bottom
(12→16), and the letter-mark placeholder font (32→52).
SCSS-only change. Asset cache cleared (3 stale attachments).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
LS-T2..T6 of the tablet lock-screen redesign (LS-T1 backend shipped
separately in c6137100).
Files:
- _tablet_lock_tokens.scss (new — design tokens, dark/light branches
via $o-webclient-color-scheme, registered
first in manifest per project rule 8)
- tablet_lock.scss (full rewrite — gradient bg, glassmorphic
tiles, 4 entrance keyframes, hover lift,
click press, clocked-in pulse,
prefers-reduced-motion gate)
- tablet_lock.xml (extended — logo + clock + prompt blocks
wrapping the existing tile loop; tile
inner shape updated for avatar gradient,
has_photo conditional, is_clocked_in
modifier)
- tablet_lock.js (extended — state.clockText / dateText /
company, setInterval(60s) clock tick,
_formatTime / _formatDate / tileStyle /
avatarClass helpers per project rule 20)
- __manifest__.py (19.0.31.0.0 -> 19.0.32.0.0, registered
new tokens SCSS BEFORE tablet_lock.scss)
Verified live on entech:
- Module upgrade clean, registry loaded in 15.5s
- 6 stale asset attachments cleared
- Helpers in tablet_controller.py emit company payload + initials +
gradients correctly (Garry Singh -> GS, EN Tech -> ET, uid=5 ->
pink gradient)
- res.company.logo present (has_logo: True)
- All animations gated by prefers-reduced-motion per spec §6
CLAUDE.md updated with new Critical Rule 22 about Odoo 19 HTML fields
auto-wrapping plain-string writes — caught during Task 1 testing when
the original 'tagline equality' test failed because res.company.report
_header is an HTML field that wraps writes with <p> tags.
Closes the 6-task plan in
docs/superpowers/plans/2026-05-24-tablet-lock-screen-redesign-plan.md
Spec: docs/superpowers/specs/2026-05-24-tablet-lock-screen-redesign-design.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
LS-T1 of the tablet lock-screen redesign.
Adds 3 module-level helpers in tablet_controller.py:
_initials_from(name) — first/last initials for letter-mark fallback
_avatar_gradient_for(uid) — deterministic per-user color (8 gradients)
_lock_company_payload(env) — company name + tagline + logo URL block
Endpoint /fp/tablet/tiles now returns:
{ok, company:{id,name,tagline,logo_url,has_logo,initials},
tiles:[{user_id, name, initials, avatar_url, has_photo,
avatar_gradient, is_clocked_in, has_pin}, ...]}
Tagline reuses res.company.report_header (the existing invoice-letterhead
field) — no new model field. Falls back to 'Shop Floor Terminal' when
empty.
10 tests pass (initials edge cases, gradient determinism, payload shape).
The 'tagline matches input string' assertion was intentionally NOT added
— see new CLAUDE.md Critical Rule 22 about Odoo 19 HTML field
auto-wrapping that makes such an equality test brittle.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Hybrid Industrial Bold + Premium Glassmorphism direction approved
during brainstorming. Adds company branding (logo from
res.company.logo with letter-mark fallback), real-time clock, tighter
3-column tile grid for ~10-15 operator small shops, dual dark/light
mode via compile-time $o-webclient-color-scheme branch, 7-animation
catalogue gated by prefers-reduced-motion.
Backend touch: extend /fp/tablet/tiles payload with company block +
per-tile initials/avatar_gradient/has_photo. Two small helper
functions in tablet_controller. No DB migration.
Frontend touch: new _tablet_lock_tokens.scss (loads first), full
rewrite of tablet_lock.scss, extend XML + JS for clock + company.
Mockup: .superpowers/brainstorm/1983-1779585812/content/lock-final.html
(in-repo since the brainstorm session used --project-dir).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User feedback after live testing: cards were too cramped on the 9-column
board. Restoring the Variant C mockup proportions and letting the board
scroll horizontally on smaller viewports (user explicitly accepted
side-scrolling).
Changes:
- .board grid: repeat(9, 1fr) → repeat(9, minmax(300px, 1fr))
plus overflow-x: auto. Each column ~300px so the card has room to
breathe. ~6 columns visible on 1920px desktop, ~4 on 1366px tablet,
smooth horizontal scroll for the rest.
- .col-scroll: gap 4→8, max-height eased so cards aren't packed.
- .o_fp_plant_card: padding 8/10→12, gap 4→6, base font 11→12.
- card-wo: 13→16 (matches mockup header size).
- card-step: 12→14.
- chips: padding 1/6→2/8, font 10→11, radius 10→12.
- mini-timeline blocks: 8→16px tall (current step 11→22px), labels
8→9px. Matches the mockup's punchy timeline strip.
- progress bar: max-width 60→100, height 3→4.
- operator pill / icon-row: bumped to match card scale.
No backend changes. SCSS-only. Asset cache cleared (3 attachments).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The plant-view rollout left two legacy ir.actions.client data records
still claiming tag='fp_shopfloor_landing':
- action_fp_plant_overview (Plant Overview)
- action_fp_shopfloor_tablet (Shop Floor — Tablet Station)
The landing-action resolver dispatched the new view correctly when the
user clicked the Plating root menu, but bookmarks / breadcrumbs /
QR-scan landings / direct URLs still routed through these legacy
actions and loaded the per-step kanban (OWL component is still
registered for back-compat).
Flipping their tag to fp_plant_kanban means every entry point now
opens the new view. The legacy fp_shopfloor_landing OWL component
stays registered (no code removed) but no XMLID points at it
anymore — safe to delete in a future cleanup.
Also documented this as a durable convention in CLAUDE.md under
'Legacy-action redirect (general rule for OWL component swaps)'.
Verified on entech:
- action 1129 (Shop Floor) tag: fp_shopfloor_landing → fp_plant_kanban
- action 1133 (Plant Overview) tag: fp_shopfloor_landing → fp_plant_kanban
- 3 stale asset bundles cleared
- Module re-upgraded clean, registry rebuilt in 15.7s
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PV-Phase5 of the plant-view redesign. Final phase — flips the default
of x_fc_shopfloor_layout from 'legacy' to 'v2' and updates CLAUDE.md
with the new architecture rule.
Verified on entech:
- HTTP 200 on /web/login
- Shopfloor module loads cleanly with all 19 new frontend files
- /fp/landing/plant_kanban returns the assembled payload with 9
columns + denormalized cards
- Card state distribution: 22 contract_review + 8 no_parts + 1 running
(sample data only — dev system)
- Asset bundle re-compiled (9 stale attachments cleared)
- ir.config_parameter['fusion_plating_shopfloor.layout'] = 'v2' set
To switch back to legacy: Settings → Fusion Plating → Shop Floor
Layout, or UPDATE ir_config_parameter SET value='legacy' WHERE
key='fusion_plating_shopfloor.layout'.
CLAUDE.md gets a new ~80-line section documenting:
- Why the redesign (per-step kanban produced duplicate cards)
- 9-column layout + step-kind → area mapping (spec D3, D4, D5)
- 13-state catalog + precedence dispatch in _compute_card_state
- Backend single-endpoint payload shape (/fp/landing/plant_kanban)
- Frontend OWL component tree + critical implementation gotchas
(rule 20 OWL scope, rule 8 SCSS @import, dark-mode compile-time)
- How to switch back to legacy
Closes the 20-task plan in
docs/superpowers/plans/2026-05-23-shopfloor-plant-view-plan.md
Spec: docs/superpowers/specs/2026-05-23-shopfloor-plant-view-design.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PV-Phase4 of the plant-view redesign. 19 new files implementing the
6-component OWL tree plus design tokens.
Components (each = JS + XML + SCSS triple):
- FpMiniTimeline — 9-step bar consuming mini_timeline_json
- FpPlantCard — Variant C card; 13 state-* CSS classes; tap
opens fp_job_workspace
- FpColumnHeader — column label + count badge + 'You're here'
badge when paired
- FpKpiTile — clickable KPI button with urgent/warn/good
variants and active state
- FpFilterChip — toggleable chip
- FpPlantKanban — top-level orchestrator: 10s polling, mode
toggle, search + 6 filter chips, board with
9 fixed columns, localStorage filter persistence
SCSS:
- _plant_tokens.scss (loads first, exposes $plant-* vars to every
later file — required because Odoo 19 forbids @import in custom
SCSS, manifest order IS the concat order)
- Dark mode via $o-webclient-color-scheme compile-time branch
Manifest registers all assets in dependency order: tokens → component
SCSS → component XML → leaf JS → top-level JS. Mirrors the existing
project pattern.
Critical patterns honored:
- Project rule 20 (no String/Number/parseInt in OWL templates):
all coercion in JS, string literals in foreach arrays.
- No t-out without markup() (none in this batch — all card text is
pre-formatted by the controller).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PV-Phase3 of the plant-view redesign.
- /fp/landing/plant_kanban JSONRPC endpoint returns {kpis, columns,
cards} in one payload. One card per fp.job; cards denormalized so
the OWL component doesn't fan out RPCs. Server-side filter handling
for All / Mine / Running / Blocked / Overdue / FAIR. Within-column
sort by (overdue, _SORT_PRIORITY[card_state], due_date).
- fusion_plating_shopfloor.action_fp_plant_kanban client action
registered alongside the existing fp_shopfloor_landing action.
- fp_landing_data.xml resolver extended to read the layout flag and
dispatch to v2 when x_fc_shopfloor_layout='v2' (default still legacy).
Card payload (23 fields): WO, customer, PN+rev, qty, PO, recipe, spec,
tags, current step + work centre, state chip, mini_timeline, operator,
icons (signoff / bake / tracking / etc.), progress.
State-chip mapping per spec §6.1 — one map keyed by card_state with
running-time elapsed, idle-hours, and operator-name interpolation.
Verified live — card payload sample on WO-30036 (contract_review state)
produces all expected keys + 9-element mini_timeline.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PV-Phase2 of the plant-view redesign.
Implements the 13-state classifier on fp.job:
- card_state Char field, stored + indexed for fast filtering
- _compute_card_state with explicit precedence dispatch matching
spec §6.2 / §9.3 exactly (no_parts → on_hold → awaiting_signoff
→ awaiting_qc → bake_due → predecessor_locked → idle_warning →
done → contract_review → running/_mine → ready/_mine)
Six precedence helpers, each isolated for testability:
_fp_inbound_not_received, _fp_has_open_hold, _fp_has_pending_qc,
_fp_bake_window_due_soon, _fp_is_mine + _fp_has_unfinished_predecessors
on fp.job.step.
mini_timeline_json compute: 9-element array (one per column) with
state in {done, current, upcoming} and an optional 'variant' on the
current marker keyed to card_state for renderer color mapping.
Verified live:
- 14 jobs in contract_review (no active step yet)
- 8 in no_parts (confirmed + draft fp.receiving)
- 1 running (WO-30051 with Pre-Measurements at Plating column)
- mini_timeline JSON renders the full 9-area structure with the
plating slot marked current+variant=running.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Consolidated commit of session work already deployed to entech and
verified via the deep audit + the persona walk:
S22 — Signoff gate (fp.job.step.requires_signoff was 100% unenforced,
42/42 done steps had NULL signoff_user_id). Three-piece fix:
_fp_autosign_if_required (captures finisher on button_finish),
_fp_check_signoff_complete (raises UserError if NULL after autosign),
action_signoff (explicit supervisor pre-sign). Bypass:
fp_skip_signoff_gate=True.
S23 — Transition-form gate (same dormant-field shape as S22, caught
preventively before recipe authors flipped requires_transition_form
on). Model helpers on fp.job.step.move + controller gate in
move_controller (parts commit) + pre-reject in rack commit.
F7 — Chatter standardization: _fp_create_qc_check_if_needed,
_fp_fire_notification, _fp_create_delivery silent failures now also
post to job chatter instead of only logging to file.
UI fixes:
- Critical Rule 20 documented + applied: OWL templates only expose
Math as a global. Calling String(d) inside t-on-click throws
'v2 is not a function'. Fixed pin_pad.xml (string array instead of
number array with String() coercion). Also swept parseInt/
parseFloat in recipe_tree_editor + simple_recipe_editor.
- Notes panel HTML escape fix: chatter messages off /fp/workspace/load
were rendered via t-out, escaping the HTML. Wrap with markup() in
job_workspace.js refresh() before assigning to state.
Versions:
fusion_plating 19.0.20.8.0 → 19.0.20.9.0
fusion_plating_jobs 19.0.10.20.0 → 19.0.10.23.0
fusion_plating_shopfloor 19.0.30.2.0 → 19.0.30.5.0
All deployed to entech (LXC 111) and verified live.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the Phase 6.3 fpRpc wrapper to the web.assets_backend bundle.
Placed before its consumers so the `import { fpRpc } from "./services/fp_rpc"`
calls in job_workspace, shopfloor_landing, manager_dashboard, and
hold_composer resolve.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
JobWorkspace, ShopfloorLanding, ManagerDashboard, and the embedded
FpHoldComposer now call fpRpc() for write-path endpoints (start/finish
step, hold create, sign-off, milestone advance, work-centre move,
assign-worker, assign-tank, manager takeover). fpRpc auto-injects
tablet_tech_id from the tech_store so the server can rebind env via
env_for_tablet_tech() and credit the right user.
Read-path RPCs (workspace/load, landing/kanban, manager/overview,
manager/funnel, manager/approval_inbox, manager/at_risk, shopfloor/scan)
stay as plain rpc() — no audit benefit, no need for the extra plumbing.
Also wires tablet_tech_id into /fp/shopfloor/plant_overview/move_card
which I missed in P6.3.3 — surfaced when grepping JS for write callers.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
10 endpoints in shopfloor_controller (log_chemistry, start_bake, end_bake,
start_wo, stop_wo, bump_qty_done, bump_qty_scrapped, log_thickness_reading,
quality_hold, mark_gate) and 3 in manager_controller (assign_worker,
assign_tank, take_over) now accept a `tablet_tech_id` kwarg. Each rebinds
env via env_for_tablet_tech() so writes carry the correct uid even when
the OS session belongs to the persistent tablet user.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
hold, sign_off, advance_milestone each accept tablet_tech_id and
rebind env via env_for_tablet_tech. Writes (Hold.create, button_finish,
action_advance_next_milestone) now carry the tech-of-record's uid.
load endpoint is read-only and untouched.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Client-side fpRpc() is a drop-in for rpc() that automatically injects
tablet_tech_id from the tech_store into every action call. Read-only
endpoints can keep using plain rpc().
Server-side env_for_tablet_tech(env, tablet_tech_id) returns an env
scoped via with_user() when the id is a valid active user; otherwise
returns the original env unchanged. Controllers call this at the top
of action methods so all subsequent writes carry the right uid.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Registers fp_tablet_pin_setup as an ir.actions.client tag. Triggered
from res.users preferences via action_open_tablet_pin_setup (added
to res_users.py in P6.1.1). Three-stage flow:
loading → check if user has existing PIN via search_count
old → enter current PIN (skipped if first-time)
new → choose new PIN
confirm → enter new PIN again
done → success toast + auto-close 1.5s later
Each stage reuses FpPinPad with a different onSubmit + title. On
mismatch / server error, resets to the first stage with a notification.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three OWL client actions all wrap their root in <FpTabletLock>:
ShopfloorLanding wraps o_fp_landing
JobWorkspace wraps o_fp_ws
ManagerDashboard wraps o_fp_manager
Each adds FpTabletLock to static components, imports tech_store, and
gains a handOff() method that calls techStore.lock(). The Hand-Off
button (yellow, lock icon) lands next to the scan/QR controls in each
header — pressing it instantly returns the tablet to the tile grid
without waiting for the idle timer.
Component composition (per spec §6.5):
FpTabletLock
if isLocked → tile grid + FpPinPad
else → existing client action (via <t t-slot="default"/>) + FpIdleWarning
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Top-level wrapper that renders lock screen (tile grid + PIN pad) when
no tech is signed in, and renders <t t-slot="default"/> otherwise.
Drives the auto-lock countdown via the activity_tracker service +
sends a /fp/tablet/ping heartbeat every 60s while a tech is signed in.
Tiles fetch from /fp/tablet/tiles using the localStorage station id
(set by ShopfloorLanding on QR pair / station picker selection).
State machine for the lock screen body:
loadingTiles → tiles list → tile tapped → PinPad → unlock RPC
↑
onPinCancel → back to tiles
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Fixed-position yellow-border overlay + countdown toast shown during
the last N (default 30) seconds before auto-lock. Pure props-driven —
secondsRemaining is the only input; parent (FpTabletLock) decides
when to mount and unmount. Box-shadow pulse animation runs CSS-only
so OWL doesn't need to re-render every tick.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reusable 4-digit PIN pad. Auto-submits on the 4th digit via the
onSubmit prop. On wrong PIN, shake animation + dots clear + error
banner (caller controls the message via the returned {ok:false, error}).
Used by FpTabletLock (unlock flow) and FpPinSetup (set/change flow).
Dark-mode SCSS branch follows the same $o-webclient-color-scheme
pattern as the rest of the shopfloor components.
Also registers tech_store + activity_tracker services in the asset
bundle (assets/web.assets_backend) before the pin_pad files, since
the pin_pad/tablet_lock components consume them.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two registry-level services:
tech_store Shared reactive state holding currentTechId after a
successful PIN unlock. Other components subscribe via
useService("fp_shopfloor_tech_store") and read
currentTechId to inject into action RPCs. setTech(id, name)
on unlock; lock() on auto-lock / Hand-Off.
activity_tracker Document-level event tracker for pointerdown / touchstart
/ keydown / visibilitychange. Mouse-move alone deliberately
EXCLUDED — a tool resting on a tablet would otherwise keep
the session alive indefinitely. Public API:
bump(), getSecondsUntilLock(), getWarnThresholdSec()
Reads thresholds from ir.config_parameter at start +
every 5 min (so manager edits propagate within a shift).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two view inheritances on res.users:
(a) Preferences form — adds a 'Tablet PIN' group with a 'Set / Change
Tablet PIN' button that triggers action_open_tablet_pin_setup → the
fp_tablet_pin_setup OWL client action (Phase 6.2). Shows PIN Last
Set as read-only context.
(b) Standard res.users form — header button 'Reset Tablet PIN' visible
only to the fusion_plating manager group; hidden when no PIN is set
(via the set_date invisible field reference). Confirms before clearing.
Calls the clear_tablet_pin method from the model.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds two fields to fusion.plating.shopfloor.station:
- x_fc_authorised_user_ids (Many2many → res.users): restricts the
tablet lock-screen tile grid to a specific roster per station.
Empty = all operator-group users shown.
- x_fc_idle_lock_minutes (Integer, nullable): per-station override
for the auto-lock idle threshold; null = use system parameter.
Plus data/fp_tablet_config_data.xml registers four ir.config_parameter
defaults (noupdate=1 — manager can override via Settings → Technical
→ Parameters):
fp.shopfloor.tablet_idle_lock_minutes = 5
fp.shopfloor.tablet_pin_fail_threshold = 5
fp.shopfloor.tablet_pin_fail_lockout_minutes = 5
fp.shopfloor.tablet_warn_seconds_before_lock = 30
Form view surfaces both new fields in a dedicated 'Tablet PIN Gate'
group.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>