Commit Graph

1198 Commits

Author SHA1 Message Date
gsinghpal
2285c9def1 docs(plating): spec + plan for Shop Floor live-step + library cleanup
Spec documents the 4 code defects + structural vocabulary mismatch
between fp.step.kind taxonomy and the legacy _STEP_KIND_TO_AREA dict,
plus the 30 library templates missing metadata. Plan breaks the work
into 15 bite-sized tasks across 2 phases.

Implementation shipped in:
- c75d2bde (Odoo 19 session.authenticate signature fix — separate)
- 7b90f210 (Phase 1: fusion_plating)
- b06d28e7 (Phase 2: jobs + shopfloor)
- 6afc9e3c (follow-up tracking + pattern anchor)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 17:13:58 -04:00
gsinghpal
6afc9e3c0d fix(fusion_plating): tracking warning + De-Masking pattern anchor
- 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>
2026-05-24 17:13:42 -04:00
gsinghpal
b06d28e7f6 feat(jobs+shopfloor): live-step priority chain + done-job filter
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>
2026-05-24 17:06:53 -04:00
gsinghpal
7b90f210b9 feat(fusion_plating): kind.area_kind drives Shop Floor column routing
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>
2026-05-24 17:02:02 -04:00
gsinghpal
c75d2bde5a fix(shopfloor): session.authenticate signature update for Odoo 19
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>
2026-05-24 16:56:05 -04:00
gsinghpal
9e6b88f60e chore(shopfloor): bump to 19.0.33.1.1 for lock_session kiosk-xmlid fix
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>
2026-05-24 15:27:30 -04:00
gsinghpal
dc6afdd021 fix(shopfloor): lock_session resolves kiosk login via xmlid
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>
2026-05-24 15:26:46 -04:00
gsinghpal
978cd5953e docs(plating): update Shop-floor attribution section for Phase G
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>
2026-05-24 15:00:37 -04:00
gsinghpal
b869c31ba3 chore(shopfloor): bump to 19.0.33.1.0 after Phase G cleanup
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>
2026-05-24 14:38:03 -04:00
gsinghpal
67fc22237b cleanup(shopfloor): session_swap is the only tablet flow
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>
2026-05-24 14:36:12 -04:00
gsinghpal
d9f2983ea7 cleanup(shopfloor): remove legacy /fp/tablet/unlock + _tablet_audit helper
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>
2026-05-24 14:31:21 -04:00
gsinghpal
3120612e35 cleanup(shopfloor): strip tablet_tech_id from 17 endpoints
Session swap makes attribution automatic via request.env.user — the
tablet_tech_id plumbing is dead code after the kiosk + per-tech-session
architecture lands. Removed kwarg from 3 endpoints in
manager_controller, 11 in shopfloor_controller, 3 in
workspace_controller. _tablet_audit.env_for_tablet_tech import gone
from all 3 files.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 14:29:51 -04:00
gsinghpal
2a93ece4ba feat(shopfloor): per-user 7-day tablet event smart button
Owner-only smart button on res.users form. Click opens the audit log
filtered to that user (both user_id and attempted_user_id, so
failed unlock attempts against a tile show up too).

Compute is non-stored: search_count on the audit model per user on
demand. Sudo'd because the audit model has Owner-only ACL — the
compute fires for the form-viewing user (Owner) who would see the
results anyway via the menu.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 13:34:45 -04:00
gsinghpal
b26fa13044 feat(shopfloor): audit log list+form views, Owner-only menu
Plating > Configuration > Tablet Audit Log. Read-only list with
decoration (green=unlock, red=failed, warning=ceiling/force,
muted=manual/idle). Form shows full forensic detail incl. ip/ua.
Owner-only via groups=fusion_plating.group_fp_owner on the menu.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 13:34:00 -04:00
gsinghpal
7ff46af192 fix(shopfloor): Phase D review findings — defensive cleanups + bootstrap test
Important I1: tablet_session_manager.beginSession() now calls
_removeListeners() (and clears any pending _tickHandle) defensively
at start. Prevents DOM listener leak on dev hot-reload or any path
that re-bootstraps without a clean endSession() first.

Important I2: tablet_lock._checkIdle() early-returns in session_swap
mode. The tablet_session_manager owns idle tracking there (5s poll,
calls /fp/tablet/lock_session directly). Was previously dormant by
accident because session_swap never populates the legacy techStore;
explicit guard makes the decoupling intentional.

Minor M5: session_swap unlock success now resets selectedTileUserId
before window.location.reload(), matching the legacy path''s
cleanup pattern. Cosmetic before reload kicks in.

Minor M9: New test_tiles_bootstrap_fields with 3 HttpCase tests
asserting /fp/tablet/tiles returns tablet_session_mode, kiosk_uid,
and current_uid. The OWL lock screen branches on all three — a
contract regression would silently break session_swap.

Minor M10: Added inline comment near _sessionModeCache declaration
in fp_rpc.js explaining the page-reload-invalidates-cache lifecycle.

Deferred (for future polish, NOT in this commit):
- I3 (_getSessionMode ACL gap for tech users — functionally correct,
  just suboptimal; cache fallback to ''legacy'' kicks in)
- M6 (wrapper component Hand-Off buttons no-op in session_swap)
- M7 (hardcoded idle/ceiling thresholds — server-configurable later)
- M8 (timer divergence vs activity_tracker — unify later)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 13:30:29 -04:00
gsinghpal
6d4b6284ad feat(shopfloor): fpRpc skips tablet_tech_id injection in session_swap mode
When tablet_session_mode='session_swap', the server attributes every
write via request.env.user — there's no need to pass tablet_tech_id
in the RPC params. Caches the mode lookup at module level so we don't
round-trip on every RPC.

Legacy mode unchanged — fpRpc still injects tablet_tech_id from
techStore.currentTechId.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 13:18:33 -04:00
gsinghpal
d8456fb9a3 feat(shopfloor): tablet_lock branches on tablet_session_mode
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>
2026-05-24 13:17:53 -04:00
gsinghpal
b41d9629e1 feat(shopfloor): tablet_session_manager OWL service
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>
2026-05-24 13:15:28 -04:00
gsinghpal
10477a7c8f feat(shopfloor): /fp/tablet/tiles returns session_mode + kiosk_uid
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>
2026-05-24 13:14:41 -04:00
gsinghpal
8f6302b446 fix(shopfloor): Phase C review findings — lock_session closes unlock event + cron test
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>
2026-05-24 13:08:30 -04:00
gsinghpal
87e924d1d8 test(shopfloor): HTTP tests for unlock_session + lock_session
5 tests covering correct/wrong PIN, audit event writes, manual/idle
lock reasons. Uses HttpCase to actually drive the JSONRPC endpoint
end-to-end with session cookie handling.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 12:55:19 -04:00
gsinghpal
7fab01e5cb feat(shopfloor): force-lock cron for stale tablet sessions
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>
2026-05-24 12:54:44 -04:00
gsinghpal
4911088dc1 feat(shopfloor): /fp/tablet/lock_session destroys tech session
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>
2026-05-24 12:54:08 -04:00
gsinghpal
086ff512b6 feat(shopfloor): /fp/tablet/unlock_session mints real Odoo session
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>
2026-05-24 12:53:36 -04:00
gsinghpal
96e33834bd feat(shopfloor): _tablet_session_audit helper for audit-log writes
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>
2026-05-24 12:53:03 -04:00
gsinghpal
765b095035 fix(shopfloor): Phase B review findings — C1/I1/I2/I3/M1
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>
2026-05-24 12:47:26 -04:00
gsinghpal
358b90516b test(shopfloor): fp_tablet_pin auth manager handles all cases
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>
2026-05-24 12:30:56 -04:00
gsinghpal
dd0dc26232 feat(shopfloor): fp_tablet_pin custom auth manager
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>
2026-05-24 12:30:24 -04:00
gsinghpal
1dea752a29 test(shopfloor): fp.tablet.session.event is append-only
Owner reads. Technician cannot read. Owner cannot write or unlink.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 12:29:52 -04:00
gsinghpal
9f3edd60ae feat(shopfloor): fp.tablet.session.event append-only audit log
Captures unlock / failed_unlock / manual_lock / idle_lock /
ceiling_lock / force_lock / admin_reset events with session hash,
ip, user-agent, duration, failure reason, acting uid.

Read-only ACL granted to Owner in Phase A; no write/unlink anywhere.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 12:29:22 -04:00
gsinghpal
0b92294586 fix(shopfloor-sec): narrow kiosk ir.config_parameter scope + doc accuracy
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>
2026-05-24 12:22:40 -04:00
gsinghpal
a52ef29a84 test(shopfloor): kiosk user ACL has near-zero access
7 tests covering allowed reads (res.users, ir.config_parameter)
and forbidden everything else (fp.job, sale.order, fp.certificate,
fp.part.catalog, res.users write).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 12:06:52 -04:00
gsinghpal
97deb93ee7 feat(shopfloor): post-migrate hook for kiosk password init
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>
2026-05-24 12:06:22 -04:00
gsinghpal
b67186a25b feat(shopfloor): create fp_tablet_kiosk user
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>
2026-05-24 12:06:00 -04:00
gsinghpal
258782e3c3 feat(shopfloor-sec): kiosk ACL — read res.users + ir.config_parameter
Owner gets read on fp.tablet.session.event (audit log).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 12:05:42 -04:00
gsinghpal
acc95d8ee0 feat(shopfloor-sec): group_fp_tablet_kiosk for tablet kiosk session
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 12:05:14 -04:00
gsinghpal
e9b82fbe9d chore(shopfloor): bump to 19.0.33.0.0 for tablet PIN session redesign
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 12:04:55 -04:00
gsinghpal
c3bcb4b99d docs(plating): tablet PIN session redesign implementation plan
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>
2026-05-24 11:53:23 -04:00
gsinghpal
cfaf4657ce docs(plating): tablet PIN session redesign spec
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>
2026-05-24 11:42:00 -04:00
gsinghpal
7966f8d505 fix(shopfloor): tablet tiles domain uses group_ids (Odoo 19 rename)
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>
2026-05-24 10:48:29 -04:00
gsinghpal
839a7f0abc fix(shopfloor): tablet tile grid includes shop-branch role holders
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>
2026-05-24 10:47:01 -04:00
gsinghpal
0f751d82cc feat(shopfloor): add Record Inputs button to Job Workspace step row
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>
2026-05-24 10:34:09 -04:00
gsinghpal
aa8161f764 fix(shopfloor): sudo job recordset in /fp/workspace/load (rule 13m)
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>
2026-05-24 10:28:58 -04:00
gsinghpal
31740b3949 fix(shopfloor): sudo cross-module reads in Plant Kanban _render_card
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>
2026-05-24 10:19:39 -04:00
gsinghpal
e99cf20887 style(shopfloor): tablet lock clock 24h -> 12h with AM/PM
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>
2026-05-24 10:16:17 -04:00
gsinghpal
cc5542833f style(shopfloor): tablet lock screen tile grid 3 -> 5 columns
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>
2026-05-24 10:13:52 -04:00
gsinghpal
0568d8ae87 fix(plating-perms): widen settings landing field to ir.actions.actions
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>
2026-05-24 10:11:40 -04:00
gsinghpal
c2180d3691 Merge: Fusion Plating Permissions Overhaul Phase 1
Consolidates 12 res.groups into 8 clean roles:
  Owner -> Quality Manager -> Manager -> [Shop Manager, Sales Manager]
       -> [Technician, Sales Rep], plus implicit 'No' (no plating group).

Phase A — 7 new res.groups with implied_ids chains + backward-compat;
old groups marked [DEPRECATED] and queued for 30-day cron purge.
Phase B — mechanical ACL sweep across 24 ir.model.access.csv files.
Phase C — Manager/QM quality permission split + FAIR/Nadcap ir.rule.
Phase D — 3-layer menu/submenu/field/button visibility hardening.
Phase E — role-based landing-page dispatch (Owner -> Manager Desk,
QM -> Quality Dashboard, Sales Rep -> Quotations, Tech -> Plant
Kanban, etc.) + picker domain over ir.actions.actions so window AND
client actions are both pickable.
Phase F — Owner-only Plating > Configuration > Team kanban for
drag-and-drop role assignment, plus Designated Officials (CGP DO +
Nadcap Authority) fields on res.company.
Phase G — Sales Manager + required to confirm SO; fixed the
audit-finding-11 _administrator typo that had made the account-hold
bypass dead code; swept all Python has_group() refs to new xmlids.
Phase H — dry-run + Owner-approval migration workflow with
fp.migration.preview model, mail.activity notification, 30-day
rollback window, daily purge cron.
Phase 9 — final-reviewer fixes (groups_id->group_ids, server-action
wiring, migrations/19.0.21.1.0/post-migrate.py for -u dispatch,
Odoo 19 kanban card template, FAIR/Nadcap cert_type field name,
user_has_groups removed from invisible attrs).
Phase I — pre-deploy backup, entech deploy (5 cascade fixes
discovered live), Owner approval of migration #1 (25 users
migrated cleanly), post-approval SQL verification, sample login
tests, deprecated-group picker cleanup (Option A SQL UPDATE),
and 11 post-deploy bug fixes (picker model swap to ir.actions.actions,
ACL grant for ir.actions.actions read to plating users, SELF_WRITEABLE_FIELDS
extension for non-admin Preferences save, res.users.message_post ->
partner_id.message_post, tablet lock screen group ref swap,
PIN-pad dark-mode dot contrast, lock-screen logo plate dark mode).

Spec: docs/superpowers/specs/2026-05-23-permissions-overhaul-design.md
Plan: docs/superpowers/plans/2026-05-24-permissions-overhaul-phase1-plan.md
CLAUDE.md rules added: 13b-13l (Odoo 19 gotchas surfaced during build/deploy)

Live state on entech: 25 users migrated, 30-day rollback open
until 2026-06-23, deprecated groups hidden from picker.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 10:05:25 -04:00
gsinghpal
42036c23ab fix(plating-perms): Phase I post-deploy fixes (live entech test catches)
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>
2026-05-24 10:02:32 -04:00
gsinghpal
7bcbcb4008 fix(plating-perms): deploy-time cascade fixes from entech I3
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>
2026-05-24 09:07:13 -04:00