Commit Graph

1224 Commits

Author SHA1 Message Date
gsinghpal
aab6b9275b feat(fp.job): migration 19.0.11.0.0 — backfill new states (Task 21)
Idempotent post-migrate that moves mid-flight in_progress jobs whose
recipe steps are all terminal into the appropriate new state:
  - draft cert exists → awaiting_cert
  - no cert required  → awaiting_ship
done jobs left alone (historically completed, already shipped).

Card_state + mini_timeline_json recomputed for affected rows so the
plant kanban renders correctly on first page load.

Version bump 19.0.10.31.0 → 19.0.11.0.0 triggers the migration on -u.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 09:53:57 -04:00
gsinghpal
26a1086623 feat(notifications): cert authority events + QM activity (Tasks 17-20)
TRIGGER_EVENTS extended with three new events:
  - cert_awaiting_issuance — fires on in_progress → awaiting_cert
  - cert_voided_re_notify  — fires on awaiting_ship → awaiting_cert
                             regress (cert voided post-issue)
  - job_shipped            — fires on button_mark_shipped

_dispatch routes cert events through new internal-recipient resolver
(QM/Manager/Owner via all_group_ids, transitive per Rule 13l)
instead of the partner-based stream lookup. Other events unchanged.

Mail templates (fp_cert_authority_templates.xml): two new
mail.template records bound to fp.job. Amber accent bar for awaiting,
red accent bar for void-re-issue. Deep-link to
/odoo/action-...?tab=certificates so QM lands on the right tab.

Activity type (fp_activity_types_data.xml): mail.activity.type
activity_type_issue_coc — bound to fp.job, 1-day delay, certificate
icon.

fp.job helpers:
  _fp_schedule_cert_activity: round-robin by oldest login_date,
    idempotent on existing open activity, soft-fails if helpers
    are missing.
  _fp_resolve_cert_activities: auto-resolves on awaiting_ship,
    soft-fails on per-activity exceptions.

Manifest bumps:
  fusion_plating_notifications 19.0.6.6.1 → 19.0.7.0.0
  fusion_plating_jobs: data list gains fp_activity_types_data.xml

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 09:53:09 -04:00
gsinghpal
c00831a72a feat(quality_dashboard): sixth 'Certificates' tab (Tasks 14-16)
Counts endpoint: certificates block — open=draft, overdue=draft+>24h.
Falls back to {open:0, overdue:0} when fp.certificate isn't installed.

JS: TABS array gains the 6th entry. Existing data-driven OWL template
auto-renders both the header tile and the body panel. Tab opens the
fp.certificate kanban grouped by state, filtered to draft by default.

Deep-link: setup() reads action.context.params.tab. The
cert_awaiting_issuance notification email links to
/odoo/action-fp_quality_dashboard?tab=certificates and lands the QM
on the right tab automatically.

Template: 'Open across all 5' → 'Open across all <tabs.length>' so
it stays correct if more tabs are added later.

Manifest: fusion_plating_quality 19.0.6.6.6 → 19.0.7.0.0
(fusion_plating_certificates already in depends — no change needed).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 09:49:10 -04:00
gsinghpal
3a120dd400 feat(plant_kanban): post-shop states visible on board (Tasks 9-13)
Controller (plant_kanban.py):
  - Widen domain: state IN (confirmed, in_progress, awaiting_cert,
    awaiting_ship). Done jobs still drop off.
  - _resolve_card_area: state=awaiting_cert → 'inspection' column,
    state=awaiting_ship → 'shipping' column. State drives column
    regardless of recipe shape.
  - _state_chip: 🏷️ Awaiting CoC (amber) + 📦 Ready to ship (green).
  - _SORT_PRIORITY: awaiting_cert=3.5, awaiting_ship=8.5.
  - KPI dict: awaiting_cert + awaiting_ship counts.
  - Filter clauses for the two new chips.

Model (fp_job.py):
  - _compute_card_state handles new states in BOTH branches: the
    no-active-step early return (where awaiting_cert/ship cards
    land — all steps terminal) AND the per-step branch (defensive).
  - _compute_mini_timeline_json: awaiting_cert paints inspection
    dot 'current'; awaiting_ship paints shipping dot 'current'.
    All earlier dots show 'done'.

SCSS (_plant_tokens.scss + _plant_card.scss):
  - New tokens for amber (cert) + green (ship), light + dark variants
    via the existing $o-webclient-color-scheme compile-time branch.
  - .state-awaiting_cert / .state-awaiting_ship modifier classes
    match the existing border-left pattern.

XML (plant_kanban.xml):
  - Two new KPI tiles + two new filter chips wired to the state
    filter clauses.

Manifest: fusion_plating_shopfloor 19.0.33.2.0 → 19.0.34.0.0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 09:47:17 -04:00
gsinghpal
4dc0a7cca5 feat(fp.certificate): ACL guard + state hooks + age field (Tasks 6-8)
action_issue gated to Manager/QM/Owner via Python AccessError +
view-level groups= on the Issue button (two-layer enforcement).
Manager bypass via fp_skip_cert_authority_gate=True context flag
with chatter audit.

action_issue post-callback calls job._fp_check_advance_after_cert_issue
so the job auto-advances awaiting_cert → awaiting_ship when every
required cert is issued.

write({'state':'voided'}) override calls
job._fp_check_regress_after_cert_void so a previously-issued cert
being voided slides the job back to awaiting_cert and re-notifies
the QM.

x_fc_age_hours non-stored Float drives the Quality Dashboard age
chip + overdue filter.

Version bump 19.0.7.9.3 → 19.0.8.0.0 (spec said 19.0.6.0.0 but
current is already higher; bumped to next major instead).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 09:43:08 -04:00
gsinghpal
4930a89970 feat(fp.job): button_mark_shipped + milestone cascade integration (Task 5)
button_mark_shipped: manual transition awaiting_ship → done. Does
not re-run the bake/qty/QC gates — those passed at the in_progress
→ awaiting_cert/ship transition. Just the 'yes, shipped' stamp.

Milestone cascade (_compute_next_milestone_action) extended to
recognize the two new states:
  - awaiting_cert → 'issue_certs' button
  - awaiting_ship → 'mark_shipped' button
Legacy state='done' branch preserved for historical jobs.

action_advance_next_milestone now dispatches 'mark_shipped' via
_action_mark_shipped_dispatch which routes:
  awaiting_ship → button_mark_shipped (new path)
  done + active delivery → _action_mark_active_delivery_delivered
    (legacy, unchanged)

View: 'Mark Shipped' milestone button gated on Manager/Owner groups.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 09:26:03 -04:00
gsinghpal
72f0f182a6 feat(fp.job.step): wrap button_finish with gate + advance (Task 4)
Pre-super: when finishing the last open step on an in_progress job,
run the bake/qty/QC gates from button_mark_done so failures surface
as UserError on the click (per spec D12). Without this the
auto-advance would silently fail with no error path.

Post-super: trigger _fp_check_advance_post_shop so the state
auto-advances cleanly (in_progress → awaiting_cert / awaiting_ship).

Added _fp_check_finish_gates helper on fp.job and a
fp_check_gates_only context flag honored by button_mark_done so the
gate logic is single-sourced (DRY).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 09:22:33 -04:00
gsinghpal
5173554281 feat(fp.job): post-shop transition helpers (Tasks 2+3)
_fp_check_advance_post_shop: in_progress + all steps terminal →
  awaiting_cert (cert required) or awaiting_ship. Auto-spawns cert
  + delivery and fires notifications. Idempotent. Does NOT raise —
  gate failures bubble up via fp.job.step.button_finish (Task 4).
_fp_check_advance_after_cert_issue: awaiting_cert → awaiting_ship
  when every required cert is state=issued.
_fp_check_regress_after_cert_void: awaiting_ship → awaiting_cert
  when a previously-issued cert is voided. Re-notifies QM.

hasattr guards on _fp_schedule_cert_activity + _fp_resolve_cert_activities
keep this safe during incremental rollout — those land in Task 20.

Test scaffolding added covering helper existence + idempotency.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 09:21:22 -04:00
gsinghpal
c2b693c97e feat(fp.job): extend state with awaiting_cert + awaiting_ship
Per spec docs/superpowers/specs/2026-05-25-post-shop-cert-shipping-job-states-design.md.
Selection extension only; transitions wired in subsequent tasks.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 09:20:12 -04:00
gsinghpal
051094813e docs(plan): post-shop cert + shipping job states implementation plan
23 tasks across 8 phases:
  Phase 1 — State machine foundation (extend Selection, advance helpers)
  Phase 2 — Step-level gating hooks (button_finish gates + advance)
  Phase 3 — Certificate-side hooks + ACL (action_issue guard + cert
            void regress + x_fc_age_hours + view groups gating)
  Phase 4 — Plant Kanban visibility (domain, _resolve_card_area,
            chips, SCSS, KPI tiles + filter chips, mini-timeline)
  Phase 5 — Quality Dashboard sixth tab (Certificates)
  Phase 6 — Notification + Activity (events, resolver, templates,
            mail.activity schedule + auto-resolve)
  Phase 7 — Migration 19.0.11.0.0 — backfill mid-flight jobs
  Phase 8 — Battle test + entech deploy

Implements: docs/superpowers/specs/2026-05-25-post-shop-cert-shipping-job-states-design.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 09:15:27 -04:00
gsinghpal
edf3f95854 docs(brainstorm): post-shop cert + shipping job states spec
Trigger: WO-30058 (SO-30058) finished all recipe steps on entech and
disappeared from the Shop Floor kanban with a draft CoC and no QM
notification. Operators reported jobs feeling lost; risk that a job
could leave the building without paperwork.

Design (Approach A, approved):
- Two new fp.job.state values between in_progress and done:
  awaiting_cert + awaiting_ship
- Auto-advance on last step finish; auto-advance on cert issue
- Plant kanban widens domain, renders the two states in the existing
  Final inspection / Shipping columns
- 6th tab 'Certificates' on Quality Dashboard with kanban + filters
- ACL gate on fp.certificate.action_issue restricted to
  Manager / QM / Owner (transitive via all_group_ids)
- Email + mail.activity notification to QM authority group
- Migration script backfills mid-flight jobs

Shipping label printing, BoL, carrier dispatch are explicitly
out of scope; awaiting_ship is a parking column with a manual
Mark Shipped button.

Self-review pass found and fixed:
- round-robin field ambiguity (last_activity_at vs login_date)
- unstated behavior for button_mark_done gates (now in step.finish)
- placeholder version inlined (19.0.11.0.0)
- dead reference replaced with inline body

Spec: docs/superpowers/specs/2026-05-25-post-shop-cert-shipping-job-states-design.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 09:03:31 -04:00
gsinghpal
80887d6098 changes 2026-05-25 08:17:29 -04:00
gsinghpal
5d5964a327 fix(plant-kanban): full-height bordered columns + viewport-pinned scrollbar
Two layout polish fixes after persona-walk feedback on the new Plant
Kanban surface (`fp_plant_kanban`).

1. Columns now run full board height with visible borders
   Was: `.col` had `background: $plant-bg` (= page bg, invisible) and
   no border, so only the header card (`.o_fp_col_header`) drew any
   outline. Empty columns (BAKING / DE-RACKING / SHIPPING) looked
   unbounded — operators couldn't tell where one column ended and
   another began.

   Now: `.col` is the bordered white card (Trello / Asana style),
   stretches full height via grid + flex. `.o_fp_col_header` drops
   its standalone border / radius / background and is just a bottom-
   divider band inside the column card.

2. Horizontal scrollbar pinned to viewport bottom
   Was: `.o_fp_plant_kanban` was `min-height: 100vh` (block flow) +
   `.board` had `min-height: 520px; overflow-x: auto`. Scrollbar
   showed at the bottom of the .board element (~520px from top of
   board), floating mid-page below the empty columns.

   Now: parent is `height: 100vh; display: flex; flex-direction:
   column`. Header is `flex: 0 0 auto`; `.board` is `flex: 1 1 auto;
   min-height: 0` so it fills all remaining vertical and its
   scrollbar sits at the viewport bottom.

`.col-scroll` switched from `max-height: calc(100vh - 260px)` to
`flex: 1 1 auto; min-height: 0` so it expands inside the now-full-
height column instead of being capped at a magic number.

Version: fusion_plating_shopfloor 19.0.33.1.13.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 20:40:41 -04:00
gsinghpal
80f80fb707 fix(tablet): ACL, loading hang, timer offset + FP-tz clock
Four fixes shipped together — all surfaced during tablet UX walkthrough
on entech.

1. sale.order ACL on step completion
   Technicians hit "Access Denied... sale.order" when starting/finishing
   any step. _fp_check_receiving_gate + the serial-promotion helpers +
   _fp_resolve_contract_review_part read step.job_id.sale_order_id (and
   sale_order_line_ids) without sudo. Per Rule 13m, denormalized cross-
   module reads in tablet controllers must sudo() the source recordset.

2. Workspace stuck on "Loading Job Workspace…" after Hand Off + relogin
   Action params aren't URL-encoded, so the workspace remounts with
   jobId=null. refresh() exited early, state.data stayed null, "Loading"
   shown indefinitely. onMounted now redirects to the plant kanban
   when jobId is null or the initial load returns no data.

3. 4-hour timer offset on active steps
   workspace_controller used fp_format() to serialize date_started —
   which converts naive UTC to user tz wall time first. JS then
   appended 'Z' and parsed as UTC, so timer was offset by the user's
   tz (4h on EDT). Switched to fp_isoformat_utc() (proper +00:00 ISO)
   and dropped the Z-append in formatActiveStepElapsed +
   isActiveStepOvertime.

4. Lock-screen clock follows FP regional setting
   tablet_lock.js used d.getHours() / d.toLocaleDateString() — browser
   tz. Now /fp/tablet/tiles returns tz_name (fp_user_tz resolution:
   user.tz → company.x_fc_default_tz → UTC) and the formatters use
   Intl.DateTimeFormat with the explicit timeZone option. plant_overview
   now consumes server_time (already fp_format'd) instead of toLocaleTime
   String. Same chain Odoo backend uses, so PDF / view / tablet all
   agree on what time it is.

Versions: fusion_plating_jobs 19.0.10.30.0,
fusion_plating_shopfloor 19.0.33.1.12.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 20:31:25 -04:00
gsinghpal
bfc138251a feat(fusion_plating): hide back-office menus from Plating Technicians
Per user request: technicians on the tablet should only see Discuss,
To-do, Plating, AI, Maintenance, Time Off. Every other top-level app
menu (Calendar, Contacts, CRM, Sales, Dashboards, RC, Faxes, Field
Service, Fusion Clock, Invoicing, Accounting, Project, Timesheets,
Planning, Shipping, Website, Purchase, Inventory, Sign, HR, Payroll,
Attendances, Recruitment, Expenses, IoT, Link Tracker, Apps) is now
restricted to a new group_fp_office_user.

Architecture:
- New group_fp_office_user (security/fp_menu_visibility.xml) — a
  marker group that controls back-office menu visibility.
- Owner / Manager / Quality Manager / Shop Manager / Sales Rep all
  imply office_user via implied_ids — they see everything they did
  before.
- Pure Technicians do NOT imply office_user — they see only the
  tablet-friendly menus.
- A "!technician" filter would have hit managers too (because Manager
  → ... → Technician via implication), so office_user is the inverse
  pattern that gets the right scoping.

Implementation:
- post_init_hook + migrations/19.0.21.4.0/post-migrate.py both call
  _fp_apply_office_user_menu_visibility(env) which iterates a curated
  list of menu xmlids and sets group_ids = [office_user] on each.
- Uses env.ref(..., raise_if_not_found=False) so menus from
  uninstalled modules silently skip — no hard depends added.
- ir.ui.menu uses `group_ids` in Odoo 19 (was groups_id pre-18 — same
  rename pattern as res.users; CLAUDE.md Rule 13c).
- Settings / Apps / Tests left untouched (already admin-restricted).
- Some menus (Field Service) end up with office_user OR their original
  group — that's correct behavior: Plating Techs have neither so still
  don't see them; explicit Field Technicians keep access.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 20:00:15 -04:00
gsinghpal
7dab5fb9c6 feat(record-inputs): tap-to-adjust steppers + inputmode keypad hint
Adds [-] / [+] buttons around every numeric input in the Record Inputs
dialog (single-value, dual-entry, and pass_fail+range branches). Tap
to increment / decrement by the recipe-author-derived step size
(stepFor() already computes this from target_min/target_max precision,
falling back to input-type defaults).

- Decrement clamps at 0 (typical qty/time/temp on a plating floor
  doesn't go negative; if needed, operator can still tap the input
  and type a negative value)
- Increment uses _stepRound() to avoid floating-point fuzz on decimals
- Center-aligned monospace-ish input between the buttons for clarity
- inputmode='decimal' (or 'numeric' for time fields) hint so when the
  operator does tap the input, the iPad shows a number keypad instead
  of the full keyboard

Touches single-value, dual-entry (min/max), and pass_fail+range. Other
multi-field widgets (multi-point thickness, bath chemistry panel) still
use plain inputs — separate request if they need steppers too.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 19:43:00 -04:00
gsinghpal
8d4c85cc52 fix(workspace): drop native confirm() on Close Receiving
Native browser confirm popups look out of place in the tablet UI.
Mark Counted is already a deliberate prior step, so requiring a
second confirmation on Close Receiving was just friction. If a
receiver hits Close prematurely, action_reset_to_counted on
fp.receiving from the back office is the recovery path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 19:36:48 -04:00
gsinghpal
fc17754996 fix(workspace): required-inputs gate fires + manager bypass dialog
Two bugs:

1. Gate silently passed when step.recipe_node_id was NULL — happened
   to every WO-30057 step after this morning's clone delete (the FK
   ON DELETE SET NULL wiped the link). _fp_missing_required_step_inputs
   returned an empty recordset when node was None, so the gate had
   nothing to fail on and button_finish succeeded with zero audit.
   Fix: _fp_check_step_inputs_complete now treats NULL recipe_node_id
   as an explicit "no recipe link" hard block. Operator can't finish;
   manager bypass posts chatter audit.

2. No tablet UI for the manager bypass. The gate's bypass was a
   Python context flag — invisible from the JS layer, so managers
   were stuck behind the same hard error as operators.
   Fix: new /fp/workspace/finish_step endpoint returns structured
   errors (gate type, missing_prompts list, bypass_available bool).
   Server-side enforces manager group when bypass=True (can't trust
   the client). New FpFinishBlockDialog OWL modal renders:
   - Non-manager: Cancel + Record Inputs
   - Manager:     Cancel + Record Inputs + ⚠ Bypass & Finish (audit)

JobWorkspace.onFinishStep routes plain finishes through the new
endpoint; signature-required steps still go through /fp/workspace/sign_off
(separate gate). Added is_manager to /fp/workspace/load payload so
the JS knows which dialog variant to render.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 19:30:39 -04:00
gsinghpal
0371624afb feat(workspace): live HH:MM:SS timer on active step
Pure client-side tick — 1s setInterval bumps state.tickNow which the
template reads via formatActiveStepElapsed(step). No RPC per tick.
Reads step.date_started_iso (UTC) from the existing payload, parses
to ms, displays elapsed since.

- Green pill (#d1fae5 bg, monospace tabular-nums) on the ACTIVE badge
- Flips red (#fee2e2 + pulse animation) when elapsed > 1.5x
  duration_expected — visual cue for the operator that the step is
  running long against the recipe target

Cleanup interval on onWillUnmount alongside the existing 15s refresh
interval.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 19:18:49 -04:00
gsinghpal
eed1c4619d feat(workspace): pre-recipe receiving card with box count + damage log
Adds the receiver workflow to the Job Workspace tablet view (was the
gap behind WO-30057 sitting in Receiving with no way to advance).
Receivers no longer need to go to the backend form.

Workspace card (renders above the step list when fp.receiving in
state draft/counted on the linked SO):
- Draft state: numeric box-count input + per-line received_qty /
  condition picker (good/damaged/mixed) + Damage Log panel + Mark
  Counted button. Autosaves on input blur.
- Counted state: read-only summary (boxes, parts, who/when) +
  Damage Log still editable + Close Receiving button.
- Closed: card disappears, recipe takes over.

New FpDamageDialog OWL modal:
- Severity pill picker (Cosmetic / Functional / Rejected) with
  color-coded active state
- Required description textarea
- Action Required pill picker (None / Notify / Return / As-Is)
- Photo capture: both "Take Photo" (input capture="environment"
  triggers tablet camera) AND "Upload" (file picker fallback).
  Multi-photo with preview grid + per-photo remove.

5 new endpoints on workspace_controller.py:
- receiving_save_lines (autosave box_count_in + per-line qty/cond)
- receiving_mark_counted (wraps action_mark_counted)
- receiving_close (wraps action_close)
- damage_create (creates fp.receiving.damage + attaches base64 photos)
- damage_delete (removes a damage row)

No model changes — wraps existing fp.receiving actions and damage
CRUD. C3 (outbound shipping carrier/label) is a separate spec.

Spec: in-conversation brainstorm (C1+C2) following the 2026-05-24
workspace step actions spec; no standalone doc since scope is small.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 19:08:30 -04:00
gsinghpal
170398ab6f feat(workspace): per-kind step action buttons in Job Workspace
Fix: in the Job Workspace tablet view, the Start button was buried
inside a parent t-if that required the step to already be in_progress
or blocked. So ready/paused steps showed no buttons at all -
operators couldn't advance the WO from this screen (the reason the
user couldn't complete anything on WO-30057).

Template restructure (job_workspace.xml):
- Always-visible line 1 (icon + step# + name + ACTIVE/PAUSED badge + meta)
- Non-terminal detail panel (chips + instructions + opt-out + GateViz)
  visible on every non-done step so operator reads ahead
- Action row dispatched per-kind via getStepActions() helper

Per-kind action dispatcher (job_workspace.js):
- in_progress -> Record Inputs, Pause, Finish (or Finish & Sign Off)
- paused      -> Resume, Record Inputs, Finish
- contract_review (ready) -> Open QA-005 Form
- gating (ready)          -> Mark Passed (1-click start+finish)
- requires_rack_assignment -> Start (Assign Rack) - opens FpRackPartsDialog
- else (ready)            -> Start

5 new handlers: onPauseStep / onResumeStep / onMarkPassed /
onOpenContractReview / onStartWithRack. Pause and Resume use ORM RPC
(button_pause/button_resume) since no HTTP endpoint exists.

New model method (fp.job.step.action_mark_gating_passed):
- 1-click pass for gating steps - does button_start + button_finish
  in one transaction, posts chatter "Gate X marked passed by Y"
- Raises UserError if called on a non-gating step (defensive)
- Bypasses S21 required-inputs gate (gating steps have no inputs)

Controller: workspace_controller.py adds requires_rack_assignment to
the step payload so the JS dispatcher can route correctly.

Spec: docs/superpowers/specs/2026-05-24-workspace-step-actions-design.md
Sub-B (Record Inputs tablet polish: inputmode/prefill/date pickers/
signature pad/camera) is brainstormed but deferred.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 18:38:22 -04:00
gsinghpal
d4e95dcd47 docs(plating): spec + plan for recipe cleanup + receiving enforcement
Root causes documented:
1. Recipe 3620 ENP-ALUM-BASIC had duplicate sequences (Contract
   Review + Masking both at seq 10; Incoming Inspection + Racking
   both at seq 20). Clones inherited the ambiguity and resolved by
   id ordering, putting Masking before Incoming Inspection — which
   meant new jobs went straight to Plating column after the
   contract-review auto-complete.
2. 24 per-part clone recipes accumulated, all carrying the broken
   ordering.
3. ~10 kind=other stragglers across the base recipes (Blasting,
   Adhesion Test Coupon, Strip Process, Chemical Conversion etc.)
4. Recipe duplication had no kind safety net.

Implementation shipped in commits referenced from the plan.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 18:08:46 -04:00
gsinghpal
e1fedf7231 fix(fusion_plating): wet_process passthrough + per-clone unlink safety
Two follow-up fixes caught during the entech deploy of recipe cleanup:

1. RESOLVER_KIND_TO_ACTIVE_KIND was missing a self-pass entry for
   'wet_process'. The new aliases added in 19.0.21.3.0 (Chemical
   Conversion, Trivalent Chromate Conversion, Strip Process - AL,
   Plug The Threaded Holes via mask) directly return 'wet_process'
   from the resolver — without the passthrough they didn't translate
   to any active kind and stayed as 'other'. Added 'wet_process':
   'wet_process' so the migration's Phase 2 backfill catches them.

2. Migration 19.0.10.26.0 Phase 3 was using batch unlink
   (clone_recipes.unlink()) which tripped a PostgreSQL FK cascade
   ordering bug on entech ("insert or update on parent_id violates
   FK ..." during the CASCADE chain). Rewrote Phase 3 to delete one
   clone at a time with SAVEPOINT per clone — slower but immune to
   the batching bug, and one failed clone doesn't roll back the
   whole transaction.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 18:08:35 -04:00
gsinghpal
9a2975b154 feat(jobs+shopfloor): recipe cleanup migration + no_parts column fix
Migration 19.0.10.26.0/post-migrate.py runs in 5 phases:
1. Resequence recipe 3620 ENP-ALUM-BASIC ops to fix the duplicate-
   sequence bug (Contract Review=10, Incoming Inspection=20,
   Masking=30, Racking=40, then the rest). Also delete the empty
   duplicate ENP-Alum Line sub_process (id 4056).
2. Backfill kind on all kind=other nodes via the extended resolver
   from fusion_plating 19.0.21.3.0
3. Delete all per-part clone recipes (name contains em-dash)
4. Recompute fp.job.step.area_kind on all steps
5. Recompute fp.job.active_step_id + card_state on in-flight jobs

Plant kanban: no_parts cards now always land in the Receiving column
regardless of active_step area_kind. The receiver works Receiving;
that's where the card belongs when parts haven't arrived.

Spec: docs/superpowers/specs/2026-05-24-recipe-cleanup-design.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 17:59:33 -04:00
gsinghpal
271a995455 feat(fusion_plating): extend resolver + auto-classify hook on process node
Resolver (fp_resolve_step_kind) extensions:
- New aliases: blasting/bead blast/media blast variants, adhesion
  testing, corrosion testing, lab testing, strip process, chemical
  conversion, trivalent chromate, plug the threaded holes, air dry,
  desmut, soak clean, cleaner, nickel strike/strip
- Parenthetical suffix stripping - "Masking (If Required)" resolves
  through "masking", "Incoming Inspection (Standard)" through
  "incoming inspection", "Trivalent Chromate Conversion (A-14 / A)"
  through "trivalent chromate conversion"
- New RESOLVER_KIND_TO_ACTIVE_KIND map translates the resolver's
  vocabulary (cleaning/electroclean/etch/rinse/strike/dry/wbf_test
  -> wet_process) so the resolver output lands on active kinds only

Auto-classify hook on fusion.plating.process.node:
- _fp_autoclassify_kind() upgrades kind_id when current is 'other'
  AND name resolves via the resolver. Idempotent - never overrides
  a non-'other' kind. Skip via context flag fp_skip_kind_autoclassify
- Wired into create() and write() (only fires when name or kind_id
  changed on write)
- Side-effects: recipe duplication via copy() auto-corrects newly
  copied nodes; Simple/Tree editor authoring auto-classifies as soon
  as the name is saved

Spec: docs/superpowers/specs/2026-05-24-recipe-cleanup-design.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 17:57:35 -04:00
gsinghpal
056178b433 fix(jobs): store=True on fp.job.active_step_id
Required because fp.job.card_state (stored) has @api.depends including
active_step_id.area_kind. When step.area_kind changes, Odoo's trigger
chain searches fp.job by active_step_id — non-stored fields can't be
queried in WHERE clauses, raising ValueError("Cannot convert ... to
SQL because it is not stored").

Caught during entech deploy of 19.0.10.25.0/post-migrate.py Phase 3
(steps._compute_area_kind() failed on first run). store=True makes
the column searchable and the trigger chain works.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 17:14:19 -04:00
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