When ir.config_parameter[fp.shopfloor.tablet_session_mode]='session_swap',
PIN submit calls /fp/tablet/unlock_session and reloads the page; the
new session manager service kicks in on next mount. handOff() calls
lockBack('manual') which destroys the tech session server-side and
re-auths as kiosk.
Legacy mode unchanged — same /fp/tablet/unlock + techStore flow.
The feature flag, kiosk_uid, and current_uid arrive via the existing
/fp/tablet/tiles bootstrap response (Task D0).
Adds a tablet_lock-owned Hand-Off button visible only in session_swap
mode (in legacy mode wrapper components own their own buttons that hit
techStore.lock(); session_swap renders our own button so the manual
hand-off goes through lockBack() + page reload).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Tracks idle + ceiling timers for an unlocked tech session. Fires
/fp/tablet/lock_session when either trips, then reloads the page so
the browser re-bootstraps under the fresh kiosk session.
Defaults: 10min idle, 8hr ceiling, 5s tick interval. Listens for
click/touchstart/keydown/mousemove as activity signals.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
OWL lock screen needs to know (a) the active session mode (legacy or
session_swap) to branch between endpoints, and (b) the kiosk uid to
determine 'is the current browser session the kiosk?' Both come from
the bootstrap response so no extra round-trips on every render.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Important 1: lock_session now closes the original unlock event's
session_ended_at via the same parameterized-SQL bypass pattern used
by the force-lock cron. Without this, every Hand-Off click became
a duplicate force_lock event 8 hours later (cron saw the unlock still
open and re-processed).
Important 2: test_unlock_lock_session_endpoints setUp now
unconditionally overrides the kiosk password (was gated on
'if not get_param(...)' which broke on entech where the post-migrate
hook already generated a random password — tests failed against the
real value). HttpCase rolls back per test so no persistence.
Minor 4: _cron_force_lock_stale_sessions now routes the force_lock
create through write_event helper for consistency (single audit-write
path; helper captures acting_uid/ip/ua uniformly).
Minor 5: Hoisted local imports inside method bodies to top-of-file
in tablet_controller.py (AccessDenied, _tablet_session_audit) and
fp_tablet_session_event.py (timedelta, write_event).
Minor 6: New test_force_lock_cron.py with 3 tests: stale session
emits force_lock + closes original; recent session unaffected;
already-closed session not re-processed. Would have caught
Important 1 if it had existed during Phase C review.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Every 5 minutes, find active unlock events past 8-hour ceiling and
mark them force-locked. SQL bypass of the model's read-only ACL is
the only path that can update existing rows (no Python write() works
because the model override blocks even sudo writes without the
explicit fp_tablet_audit_admin_write context flag).
Ceiling configurable via ir.config_parameter[fp.tablet.session_ceiling_hours].
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Writes lock event (manual/idle/ceiling) with duration computed from
the open unlock event. Then logout + re-authenticate as kiosk via
the password stored in ir.config_parameter['fp.tablet.kiosk_password'].
Falls back to 'needs_kiosk_relogin' if the kiosk password is missing
(sysadmin must log in manually). Logs every event for forensic
review.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PIN verify -> request.session.authenticate(type=fp_tablet_pin) -> new
session sid, cookie swap, audit event written. Failed attempts also
written to audit log (failed_unlock, failure_reason=wrong_pin or
locked_out or no_pin_set or user_inactive).
OLD /fp/tablet/unlock stays alive during the 1-week overlap window
per spec Section 5.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Single source for sha256(session sid), ua trim, ip/acting_uid capture
from request. Used by unlock_session, lock_session, and force-lock cron.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
C1: Add placeholder fp_tablet_cron.xml + fp_tablet_session_event_views.xml
so the module is installable now (real content lands in Phase C task C4
and Phase E task E1 respectively).
I1: test_tablet_pin_auth_manager now passes {} (not self.env) as the
env arg to _check_credentials — matches what request.session.authenticate
provides and what the base implementation expects.
I2: Auth manager role check now uses user_sudo.all_group_ids (transitive)
instead of group_ids (direct) per CLAUDE.md rules 13l + 23. Owner users
who hold Owner directly still match all 5 shop-branch xmlids via the
implication chain.
I3: fp.tablet.session.event gains Python-layer write() + unlink()
overrides that always raise AccessError unless the explicit
fp_tablet_audit_admin_write / fp_tablet_audit_admin_purge context flag
is set. Closes the gap between the model's append-only docstring and
its actual enforcement (ACL-only previously).
M1: Hoisted 'from odoo.exceptions import AccessDenied' to top-of-file
imports next to existing UserError import.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
8 tests: correct/wrong/missing PIN, missing/unknown login, inactive
user, no shop-branch role, and pass-through of other credential types.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Validates PIN hash + shop-branch role membership when the credential
type is fp_tablet_pin. Goes through Odoo's standard _check_credentials
chain so future 2FA / IP-gate modules layer cleanly on top.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Code-review findings on Phase A (Tablet PIN Session Redesign):
I1: Security XML comment now honestly describes the kiosk as Internal
User + explicit reads, not 'near-zero ACL'. base.group_user is kept
(required for auth='user' HTTP routes to function) but the comment
no longer overstates how locked-down the kiosk is.
I2: New ir.rule scopes the kiosk's ir.config_parameter read to keys
matching 'fp.tablet.%' or 'fp.shopfloor.%'. Combined with the
existing model-level read ACL, kiosk can no longer enumerate
third-party secrets (e.g. fusion_tasks.vapid_private_key) or
arbitrary API keys stored in ICP.
I3: post-migrate docstring now advises sysadmins to unlink the
plaintext ICP password row after kiosk tablets are paired, to
minimise plaintext-in-backups risk. Rotation procedure documented.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Generates a random kiosk password on first deploy, stores in
ir.config_parameter for sysadmin retrieval. Idempotent — re-runs
on subsequent -u leave the password alone.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Kiosk holds the tablet session when no tech is PIN-unlocked.
Password is auto-generated by the post-migrate hook (Task A5).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
7 phases (A-G), ~25 tasks. Phase A-E build the new auth flow,
audit model, endpoints, OWL service, and audit UI. Phase F is the
entech rollout (manual, inline by main session per hybrid pattern).
Phase G is the post-overlap cleanup (rip out tablet_tech_id,
delete legacy endpoint, archive shopfloor service user).
Bakes in 7 known gotchas from the permissions overhaul (rules
13c, 13i, 13k, 13m, 13d, AUDIT-1, always-push-to-main) so the
implementer doesn't repeat them.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Real per-tech Odoo sessions on PIN unlock (not just attribution).
Closes the audit-trail gap from Phase 1 permissions overhaul: today
the tablet runs as a persistent 'shopfloor service' user and the PIN
is just an OWL overlay — every action is attributed to whoever the
session user is, not the tech who tapped their tile.
Locked decisions:
1. Real per-tech sessions (impersonation, cookie swap)
2. Idle timeout 10min + manual lock + 8hr hard ceiling
3. Dedicated kiosk user (fp_tablet_kiosk, near-zero ACL)
4. No manager override — Mgr/Owner PIN in as themselves
5. Two-step deploy with 1-week overlap; OLD endpoint removed after
successful rollout
Audit: fp.tablet.session.event append-only log captures unlock /
manual_lock / idle_lock / ceiling_lock / force_lock / failed_unlock
/ admin_reset events with ip, ua, session hash, duration.
Effort: ~4 dev days + 1 week observation. Plan via writing-plans
skill next.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Same mistake as the original implementer wave — used the deprecated
groups_id field name on res.users in the search domain. Odoo 19 raises
ValueError: Invalid field res.users.groups_id. Should be group_ids.
CLAUDE.md rule 13l example also fixed so future-Claude doesn't copy
the bug from the documentation.
Module version: 19.0.32.0.12 -> 19.0.32.0.13
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously only direct Technicians appeared on the lock-screen tile
grid because env.ref('group_fp_technician').user_ids returns DIRECT
members only — Odoo's implication chain (Owner -> ... -> Technician)
is read-time only, not stored in res_groups_users_rel.
Search res.users with ('groups_id', 'in', shop_branch_ids) where
shop_branch_ids covers all 5 shop-branch role groups (Technician,
Shop Manager v2, Manager, Quality Manager, Owner). Sales branch
intentionally excluded — they don't operate the tablet.
Verified on entech: 18 technicians + 1 shop_manager + 2 managers
+ 1 quality_manager + 2 owners = 24 tiles (was 18).
CLAUDE.md rule 13l corrected — previous version wrongly claimed
res.groups.user_ids surfaced implied members. Now documents the
search-based query as the canonical 'enumerate role X or higher'
pattern.
Module version: 19.0.32.0.11 -> 19.0.32.0.12
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Operators trying to Finish a step with required step_input prompts
got the S21 gate error telling them to 'Click Record Inputs on the
step row' — but the workspace UI never exposed that button. Only the
job-form view had it.
Adds a 'Record Inputs' secondary button next to Finish/Finish & Sign
Off when the step is active. Click opens the fp_record_inputs_dialog
(via action_open_input_wizard on fp.job.step). On dialog close the
workspace refreshes so the step's progress chip updates.
Module version: 19.0.32.0.10 -> 19.0.32.0.11
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Same pattern as plant_kanban — workspace payload denormalizes
cross-module fields Technician can't read directly (sale.order,
fp.part.catalog, customer_spec, etc.). job.sudo() at the top so
the whole render path is sudo'd.
Job Workspace was stuck on 'Loading...' with a server-error toast
because the route returned {ok:false, error:'...'} (27-byte response)
when the first cross-module field access AccessError'd.
Module version: 19.0.32.0.9 -> 19.0.32.0.10
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Post-migration, Technicians (now group_fp_technician) have read on
fp.job but NOT on sale.order / fp.part.catalog / fusion.plating.customer.spec.
The kanban render path tries to access job.sale_order_id.x_fc_po_number
and AccessErrors silently — kanban returns empty, user sees blank
'Shop Floor' page.
Fix: `job = job.sudo()` at the top of _render_card. The output is
denormalized display data, no security concerns; ACL gating is still
enforced by the caller's access to fp.job (which Technician does have).
CLAUDE.md rule 13m documents the broader pattern: any dashboard /
tablet / kanban controller surfacing cross-module data to low-priv
roles needs this sudo at the helper top.
Module version: 19.0.32.0.8 -> 19.0.32.0.9
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Operators read phone-style clocks; 24-hour was off-norm for North
American shop. Hour no longer zero-padded (1:05 PM, not 01:05 PM)
to match the iPhone/Android idiom.
Module version: 19.0.32.0.7 -> 19.0.32.0.8
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wider tablets fit 5 tiles per row comfortably; 3 was too sparse with
a 20-person operator roster (forced a long vertical scroll). Bumped
.o_fp_lock_tiles max-width from 480px to 800px so the tiles don't
stretch wide at 5 columns.
Module version: 19.0.32.0.6 -> 19.0.32.0.7
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
res.config.settings.x_fc_default_landing_action_id is related= to
res.company.x_fc_default_landing_action_id, which was widened from
ir.actions.act_window to ir.actions.actions in the Phase I post-deploy
fixes (so the picker accepts both window AND client actions). The
settings field's comodel was left at the old type and tripped on
opening Settings: 'Wrong value for ...: ir.actions.actions()' when
the related compute tried to write the client-action value into the
narrower settings field.
Module version: 19.0.21.1.2 -> 19.0.21.1.3
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>