Commit Graph

774 Commits

Author SHA1 Message Date
gsinghpal
34528a5d3d docs(sub12c): implementation plan — 5 tasks (down from original 18)
Tightened from the original 18-task plan after inspecting existing
templates:
- report_coc_en / report_coc_fr already exist with Nadcap/AS9100/CGP
  logos, signature, certified_by — solid. Add a chronological body
  alongside, don't rebuild.
- company.x_fc_nadcap_logo etc already exist on res.company. Skip.
- The native fp.job traveller is minimal (post-Sub-11) and needs the
  paper-style upgrade. Replace its body, not the action.
- fp.job.step.timelog state machine landed in Sub 12b — Sub 12c just
  ships views + menu.

5-task breakdown:
1. Bump versions + manifest scaffolding
2. Operator Traveller v2 (A4 landscape, paper-style, target columns)
3. Chronological CoC body + body_style opt-in router
4. Labor History list/form/search + Plating menu
5. Deploy to entech + smoke test

Out of scope: rack travel ticket PDF (Sub 12b's Save+Print 404 stays
flagged), per-customer cert statement (boilerplate inline for now).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 21:36:06 -04:00
gsinghpal
e718a47e3e fix(sub12b): to_step_id required → ondelete='restrict'
Odoo 19 disallows ondelete='set null' on a required M2O. Switched to
restrict — destination steps can't be unlinked while move-log rows
reference them, which is the right audit-safety behavior anyway.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 21:19:58 -04:00
gsinghpal
11dbbf578e feat(sub12b): plant overview Racks pane (Task 16)
Controller: extend /fp/shopfloor/plant_overview return payload to
include 'racks' array (filtered to loaded/in_use/awaiting_unrack
states). Each entry has tag chips, part count, current node
breadcrumb, current step + tank code, and a precomputed
next_step_id (next sequence in the job's recipe — operator
overrides at runtime in the Move Rack dialog).

JS: state.racks populated from payload. New openMoveRackDialog()
method spawns FpMoveRackDialog. Notification when rack has no
successor (last step of job).

XML: top section above the existing work-centre columns. Renders
rack rows with tags, part count, breadcrumb, and primary MOVE RACK
button per row. Visible only when state.racks.length > 0.

SCSS: minimal styling for the racks pane (extends move_dialogs.scss
to keep all Sub 12b styles in one file).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 21:19:05 -04:00
gsinghpal
902f3e8398 feat(sub12b): wire Move Parts + Stop Timer dialogs into tablet (Task 15)
ShopfloorTablet component:
- Imports the 3 new OWL dialogs.
- useService('dialog') for spawning.
- Listens for 'fp-resolve-rack' window CustomEvent fired from inside
  FpMovePartsDialog → spawns FpRackPartsDialog inline.
- New methods: openMovePartsDialog(from, to) + openStopTimerDialog(id).
  Refresh tablet after commit/reconcile so the UI reflects new state.

Listener cleanup on unmount.

Note: the actual buttons that call these methods are added to the
existing tablet XML in a follow-up step — for now they are wired but
not surfaced. Operators get them after Task 16 + smoke test.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 21:16:15 -04:00
gsinghpal
11bc0ca742 feat(sub12b): shared SCSS for Move/Rack/Timer dialogs (Task 14)
3-column grid layout for field rows (label / input / hint).
Compliance prompts + Blockers blocks have their own backgrounds.
Soft blockers amber + left border, hard blockers red + left border —
matches the spec's protocol.

Token pattern + dark-mode @if branch (CLAUDE.md rule: Odoo 19 doesn't
flip dark mode via runtime DOM class; we branch at SCSS compile time
on $o-webclient-color-scheme).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 21:15:12 -04:00
gsinghpal
270f427d7f feat(sub12b): Move Rack + Stop Timer OWL dialogs (Task 13)
Move Rack: rack name in title via getter, tag chips, batches list
(read-only), Type + To Node + To Station picker. Atomic Save commits
all batches via /fp/tablet/move_rack/commit.

Stop Timer: opens with state already at 'stopped' (server flipped on
load via /labor_timer/stop), pre-fills billed_* from accrued.
Operator edits → Save (state → reconciled).

Save & Start New Timer chains into a fresh timer for the same step
via the start_new=True flag — mirrors screen 10's right-most button.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 21:14:30 -04:00
gsinghpal
48c06c40c9 feat(sub12b): OWL Rack Parts sub-dialog (Task 12)
Mirrors screens 7-8. Searchable empty-rack picker with debounced
typeahead via /fp/tablet/rack/list_empty. QR Scan button prompts
operator for FP-RACK:<name> token, resolves via /fp/tablet/rack/
scan_qr.

Save commits the racking via /fp/tablet/rack_parts/commit. Save+Print
opens /web/report/pdf/fp.rack.travel/<id> in a new tab — that report
ships in Sub 12c, returns 404 until then. Plain Save works today.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 21:13:04 -04:00
gsinghpal
6d046f2881 feat(sub12b): OWL Move Parts dialog (Task 11)
Mirrors Steelhead screens 1-3, 14-15. Loads preview on mount,
re-checks hard-blockers on commit. MOVE (n) button disabled when
hard-blocked OR required prompt blank — improvement over Steelhead's
silent disabled state (we show a tooltip listing reasons).

Inline 'Resolve' button next to each blocker. For rack-required,
fires a window CustomEvent ('fp-resolve-rack') the parent tablet
catches to open the Rack Parts sub-dialog.

Typed input rendering by input_type — text/number/checkbox/select/
datetime, plus support for time_hms and signature/photo (text input
for now; full upload widget in Sub 12c).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 21:11:49 -04:00
gsinghpal
a521b7c37b feat(sub12b): consolidated tablet controller — Move/Rack/Timer (Tasks 8-10+17)
11 routes (consolidated from plan Tasks 8/9/10/17):
  Move Parts:
    /fp/tablet/move_parts/preview
    /fp/tablet/move_parts/commit
  Move Rack:
    /fp/tablet/move_rack/preview
    /fp/tablet/move_rack/commit
  Rack Parts:
    /fp/tablet/rack_parts/commit
    /fp/tablet/rack/list_empty
    /fp/tablet/rack/scan_qr
  Persistent labor timer:
    /fp/tablet/labor_timer/start
    /fp/tablet/labor_timer/pause
    /fp/tablet/labor_timer/resume
    /fp/tablet/labor_timer/stop
    /fp/tablet/labor_timer/reconcile

Manager-bypass context flags (Task 17 wired in here for cohesion):
  fp_skip_predecessor_check  → bypasses S14 lock
  fp_skip_rack_assignment    → bypasses requires_rack_assignment
  fp_skip_transition_form    → bypasses required transition prompts

All bypass uses post to chatter on the move record naming the user
+ which flags fired. Group check enforced (manager-only).

_safe() wrapper: UserError → JSONRPC-friendly {ok: False, error: msg}
so the OWL components can show a flash without crashing.

Field naming follows existing fp.job.step.timelog convention
(date_started / date_finished, NOT started_at / stopped_at).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 21:10:28 -04:00
gsinghpal
3bed76aea4 feat(sub12b): persistent state machine on fp.job.step.timelog
Extends the existing timelog (used by S1/S2 battle tests) with:
- state: running / paused / stopped / reconciled (default running)
- last_paused_at + total_paused_seconds (drives accrued compute)
- accrued_seconds (compute, depends date_started/_finished/paused)
- billed_hrs/min/sec + billed_total_seconds + billed_pct (compute)
- product_id (split-by-product reconciliation per screen 10)
- notes
- job_id (related, indexed — for fp.job.active_timer_ids O2M)

Field naming follows the existing date_started / date_finished
convention (NOT started_at / stopped_at as my plan said — adjusted
inline to match what's already in the file).

The existing battle tests use the timelog without state — default
'running' so they're unaffected. State only flips when Sub 12b's
Stop Timer dialog commits.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 21:08:22 -04:00
gsinghpal
dcd6df71c0 feat(sub12b): fp.job.step + fp.job — rack/move/traveller fields
fp.job.step:
  + requires_rack_assignment (related from recipe_node_id)
  + requires_transition_form (related)
  + move_ids (O2M from_step_id), incoming_move_ids (O2M to_step_id)
  + is_racked (compute, stored, depends rack_id) — drives tablet
    rack-vs-parts greyed-button guard
  + qty_at_step_start, qty_at_step_finish (advanced by move commits)

  NOTE: existing 'rack_id' field is reused as the 'current rack' pointer
  (already there on line 95). Adding requires_rack_assignment as a
  related from recipe_node_id for runtime gate evaluation.

fp.job:
  + qty_received, qty_visual_inspection_rejects, qty_rework
  + special_requirements (Text — paper traveller header)
  + active_timer_ids (filtered O2M, depends on Task 7's state field)
  + move_ids (O2M to fp.job.step.move)

All additive. No removed fields. Existing battle tests unaffected.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 21:07:19 -04:00
gsinghpal
0794f7e3c9 feat(sub12b): move-log list/form/search + Plating menu
Plating → Move Log (sequence 62, between Logistics 60 and Aerospace 65).
Form is read-only (create=false) since moves are produced by the
tablet flow, not the desktop UI.

Search filters: Today, Scrap/Rework, Racked. Group-by: Job, Operator,
Transfer Type.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 21:05:28 -04:00
gsinghpal
4187842d30 feat(sub12b): fp.job.step.move + fp.job.step.move.input.value
Chain-of-custody log: one row per Move Parts / Move Rack commit.
FP/MOVE/YYYY/NNNN sequence (5-digit). Carries from/to step + tank,
transfer type, qty, location, photo, rack, operator, datetime.

Child model captures recorded transition-input values (Sub 12a's
fp.step.template.transition.input snapshots → fp.job.step.move.
input.value rows). Each row carries 5 typed value columns; the
controller (Task 8) picks the right one based on input_type.

Operators get read+write+create — they generate moves at runtime —
but no unlink. Manager-only deletes for audit safety.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 21:04:41 -04:00
gsinghpal
d9ae45ce9b feat(sub12b): extend fusion.plating.rack — racking_state + tags + capacity
The existing 'state' field tracks wear (active/needs_strip/stripping/
retired). Sub 12b adds an orthogonal 'racking_state' (empty/loading/
loaded/in_use/awaiting_unrack/out_of_service) for the load lifecycle
— a rack can be wear-active AND racking-loaded simultaneously.

New fields:
  racking_state        — operational lifecycle
  tag_ids (M2M)        — fp.rack.tag chips on plant overview
  capacity_count       — soft warn (distinct from existing 'capacity')
  current_job_step_id  — compute, derived from latest fp.job.step.move
  current_tank_id      — compute
  current_part_count   — compute

Form view picks up the new fields under Sub-12b group + a 'Current
Use' panel that hides when racking_state is empty / out_of_service.

The compute references fp.job.step.move which lands in Task 4 — the
module won't load cleanly on entech until Tasks 4-5 ship; that's
expected for batch deployment at the end.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 21:03:09 -04:00
gsinghpal
86c0e230a1 feat(sub12b): fp.rack.tag — rack-label registry + 4 starter tags
M2M tag registry: Rush / Hold for QC / Damaged / Customer Sample.
Each rack can carry many tags; tags surface as coloured chips on the
plant-overview rack rows + Move Rack dialog (Task 13).

Plating → Configuration → Rack Tags menu (sequence 48).
post_init_hook seeds 4 starters — idempotent (no-op if any exist).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 21:01:49 -04:00
gsinghpal
d78ef4228e feat(sub12b): bump versions + scaffold manifests
fusion_plating → 19.0.10.1.0
fusion_plating_shopfloor → 19.0.25.0.0

Adds data entries for fp_rack_tag_views.xml + fp_job_step_move_views.xml.
Adds 4 OWL dialogs + their templates + shared SCSS to the shopfloor
backend asset bundle (loaded after the existing manager_dashboard.js).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 21:00:18 -04:00
gsinghpal
25b429f253 docs(sub12b): implementation plan — 18 tasks for tablet Move/Rack/Timer
Adjustments from the spec, captured upfront in the plan:
- fp.rack already exists (extend, don't create) — Task 3
- fp.labor.timer collapses into the existing fp.job.step.timelog with
  a state machine + reconciliation fields — Task 7. Avoids parallel
  labor-tracking models; keeps battle-test S1/S2 paths intact.
- Sub 12b's Save+Print on Rack Parts references a report that lands
  in Sub 12c — flagged in Task 12 body.

18 tasks cover: 4 new models (rack tag, move, move input value), state
machine on existing rack + timelog, 11 controller endpoints, 4 OWL
dialogs, plant overview 2-pane layout, runtime guards, manager bypass
flags, entech deployment.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 20:55:04 -04:00
gsinghpal
5494684181 fix(sub12a): rename _seed_default_inputs → action_seed_default_inputs
Odoo 19 rejects view buttons that call private (underscore-prefixed)
methods. Renamed the public entry point. The post_init_hook callers
follow.

Caught by entech upgrade (ParseError on the form view).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 20:43:26 -04:00
gsinghpal
d6cdae30ec feat(sub12a): OWL Simple Recipe Editor client action
JS: FpSimpleRecipeEditor component reads recipe_id from
props.action.context (matches the existing tree editor's contract).
HTML5 native drag-drop between Library (right) and Selected (left)
panels — uses two distinct dataTransfer types (application/x-fp-step
vs application/x-fp-library) so the drop handler knows whether to
reorder or snapshot-copy.

XML: 2-column grid layout. Selected has per-row × remove (hover
reveal), drag handle, position number, icon, name, station-count
badge. Library has search input, scrollable item list with empty-
state, drag-handle items.

SCSS: tokens follow the fp_shopfloor pattern with dark-mode SCSS @if
branch (CLAUDE.md rule). 2-fr grid that collapses to single column
under 900px for tablet/mobile.

Tag: fp_simple_recipe_editor — registered via registry.category('actions').

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 20:42:06 -04:00
gsinghpal
a892a7b20e feat(sub12a): recipe form — buttons + is_template + Step Authoring tab
Process node form:
- Header: keep 'Open Tree Editor' (primary, existing); add 'Open Simple
  Editor' (secondary). Both visible only for recipe-type nodes.
- Recipe Settings group: add preferred_editor + is_template (the latter
  supervisor-only).
- New 'Step Authoring' notebook page (visible for step/operation):
  Stations, default_kind, material_callout, predecessor/rack/transition
  flags, time/temp targets, voltage/viscosity, readonly
  source_template_id.

Model:
- New action_open_simple_editor (sibling of action_open_tree_editor).
- New _resolve_preferred_editor() — per-recipe preferred_editor wins,
  'auto' falls back to company.x_fc_default_recipe_editor, final
  fallback 'tree'.
- New action_open_recipe_with_preferred_editor() — one-click route
  through the resolver. Reserved for menu-list / context-menu callers
  that want the simple-loving foreman path.

Tree editor + every existing battle test path untouched.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 20:40:08 -04:00
gsinghpal
194d5d96dd feat(sub12a): JSONRPC endpoints for the Simple Recipe Editor
11 routes under /fp/simple_recipe/...:
  load
  library/{list,create,write,delete}
  step/{insert,write,remove,reorder}
  template/{list,import}

Library/template imports snapshot-copy fields (Q4 = A locked) — no
live references. The _SNAPSHOT_FIELDS + _INPUT_SNAPSHOT_FIELDS module
constants are the single source of truth for what gets copied;
adding a new authoring field on fp.step.template means appending it
once to _SNAPSHOT_FIELDS and the controller stays correct.

library_delete is soft when nodes still reference the template via
source_template_id (operator can't accidentally orphan recipe steps).

Uses recipe.check_access('read') (Odoo 19 unified API) instead of the
older check_access_rights/check_access_rule pair.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 20:38:16 -04:00
gsinghpal
33ddec926c feat(sub12a): post_init_hook — backfill kind + seed step library
Extends the existing post_init_hook to also do (idempotent) Sub 12a
work on first install / upgrade:

1. Backfill kind='step_input' on existing
   fusion_plating_process_node_input rows where kind IS NULL.
2. Seed fp.step.template from the ENP-ALUM-BASIC recipe's child
   nodes if the library is currently empty. Uses _STARTER_KIND_BY_NAME
   to map recipe-step names to default_kind values, then calls
   _seed_default_inputs() to populate the per-kind input rows.
3. Falls back to a 15-entry hard-coded minimal seed list if
   ENP-ALUM-BASIC doesn't exist on the target DB.

All three operations no-op when the relevant state already exists.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 20:36:55 -04:00
gsinghpal
0862e55de6 feat(sub12a): Plating → Configuration → Step Library menu
Sequence 45 — between Process Types (40) and Bath Parameters (50).
Inherits the manager-only group from menu_fp_config.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 20:36:08 -04:00
gsinghpal
738f3fcfd5 feat(sub12a): step library list/form/search views
Form: Title + Code + Classification (kind/icon/process/material) +
Stations & Flags + 4 notebook tabs (Instructions / Operation
Measurements / Transition Form / Advanced).

Operation Measurements + Transition Form are inline-editable o2m
lists with handle widget for drag reorder.

Header button: 'Seed Default Inputs' (visible only when default_kind
is set). Triggers the idempotent seeding helper.

NB: I removed `string=` from <search> per Odoo 19 rule (CLAUDE.md
critical rule 4 — no string attr on search).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 20:35:33 -04:00
gsinghpal
6fbb6f918b feat(sub12a): ACL rows for fp.step.template + 2 child models
Operator: read only on library + child inputs.
Supervisor: read/write/create on library; full CRUD on inputs.
Manager: full CRUD on all three.

Pattern matches existing fp_proficiency rows (supervisor without
unlink on the parent, full CRUD on the children — operators can't
delete a library template that recipes might reference, but
supervisors can edit/add/remove input rows freely).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 20:34:59 -04:00
gsinghpal
95debabc28 feat(sub12a): res.company.x_fc_default_recipe_editor setting
Per-company default for which editor opens for new recipes / recipes
with preferred_editor=auto. Defaults to 'tree' to preserve existing
behavior. Surfaces in Settings → Fusion Plating → Recipe Editor.

Naming follows the existing x_fc_* convention used throughout
res_company.py for company-level Fusion Plating defaults.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 20:34:12 -04:00
gsinghpal
91681d722e feat(sub12a): extend process.node + process.node.input
process.node — additive only:
  is_template, source_template_id, tank_ids (M2M to fusion.plating.tank
  with new join table fp_node_tank_rel), material_callout, time/temp
  targets + units, voltage_target, viscosity_target,
  requires_rack_assignment, requires_transition_form, default_kind,
  preferred_editor.

process.node.input — additive only:
  kind (step_input/transition_input, default step_input so existing
  rows keep working), target_min/target_max/target_unit, compliance_tag,
  plus 9 new typed input_type values (time_hms, time_seconds,
  temperature, thickness, pass_fail, date, signature, location_picker,
  customer_wo).

No removed fields, no removed selection values. Tree editor + every
existing battle test (S14/S15/S17/S18/S19) untouched.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 20:33:11 -04:00
gsinghpal
7a0e74c456 feat(sub12a): add fp.step.template.transition.input
Transition-time prompts (fired when leaving a step). Authored now,
runtime-consumed in Sub 12b's Move Parts dialog. Carries a
compliance_tag selection (none/as9100/nadcap/cgp/nuclear) so audit
reports can filter by regulation regime.

input_type covers Steelhead's transition prompts: text, number,
boolean, selection, date, signature, photo, location_picker,
customer_wo.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 20:31:45 -04:00
gsinghpal
8bcd537737 feat(sub12a): add fp.step.template.input
Operation-measurement definitions for library step templates. The
input_type selection covers Steelhead's input shapes (text, number,
boolean, selection, date, signature, time_hms, time_seconds,
temperature, thickness, pass_fail).

target_min/max + target_unit are structured (not embedded in the name
string the way Steelhead does it) so the traveller report can render
target vs actual side-by-side and colour-code out-of-range values.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 20:31:23 -04:00
gsinghpal
bef812616b feat(sub12a): add fp.step.template model with sane-default kind map
Reusable step library entry. Carries the same shape fields as
fusion.plating.process.node so a drag-drop snapshot is a 1:1 copy.
DEFAULT_INPUTS_BY_KIND drives seeding for the 15 kinds we identified
on Steelhead's job traveller (cleaning, etch, plate, bake, etc.).

The seeding helper (_seed_default_inputs) is idempotent — won't
duplicate inputs on repeated calls.

Note: imports for the 2 child models (input + transition_input) are
added in models/__init__.py here; the actual files land in the next
two commits. Module won't load cleanly on entech until both ship.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 20:30:45 -04:00
gsinghpal
7e98b48c01 feat(sub12a): bump fusion_plating to 19.0.10.0.0 + scaffold manifest
- Adds views/fp_step_template_views.xml to data list (after process_node_views).
- Adds simple_recipe_editor.{js,xml,scss} to web.assets_backend bundle.
- res_config_settings_views.xml + post_init_hook already wired — extend in place.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 20:29:37 -04:00
gsinghpal
cfe776be4c chore: session housekeeping — tank UX, plating menu defaults, WO label
- fusion_plating: tank field labels (Code → Tank Number, Tank → Tank Name)
  + state-control header buttons (Mark Empty/Filled/In Use/Draining/
  Maintenance/Out of Service) with chatter audit logging.
- fusion_plating_configurator: Plating app default landing screen = Sale
  Orders, while keeping menu name as 'Plating'.
- fusion_plating_jobs: SO smart-button label 'Plating Jobs' → 'WO'.

Already deployed and verified on entech earlier in the session.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 20:27:35 -04:00
gsinghpal
c75b22aaf7 docs(sub12a): implementation plan — 15 tasks for simple editor + library
15-task plan covering: manifest bump, three new models (fp.step.template
+ 2 child input types), additive fields on process.node, ACL rows,
views, menu, post_init_hook with library-from-ENP-ALUM-BASIC seeding,
JSONRPC controller (11 routes), recipe form integration, OWL client
action, preferred_editor resolver, entech deployment + smoke test.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 20:22:20 -04:00
gsinghpal
4e4ca2c9da docs(sub12): simple recipe editor + library + tablet move/rack + reports
Three-part design (12a/12b/12c) for adding a flat drag-drop recipe
editor alongside the existing tree editor, with a reusable step
library, Steelhead-style Move Parts/Rack/Stop-Timer dialogs, and
recipe-order + chronological CoC PDF reports.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 19:23:25 -04:00
gsinghpal
66cfe5f97f changes 2026-04-27 09:41:46 -04:00
gsinghpal
f51976cb08 changes 2026-04-27 08:48:55 -04:00
gsinghpal
2a4909be25 changes 2026-04-27 08:16:20 -04:00
gsinghpal
f08f328688 changes 2026-04-27 00:11:18 -04:00
gsinghpal
d9f58b9851 changes 2026-04-26 15:05:17 -04:00
gsinghpal
160198edb1 chore(reports): drop duplicate Print menu entries on legacy MRP models
After the fp.job migration, every MRP-bound Print action in
fusion_plating_reports has a fp.job-bound canonical version in
fusion_plating_jobs. Having both registered means clicking Print on a
record shows two identical entries.

Removed 7 ir.actions.report records (templates kept for backwards
compat — only the menu bindings are gone):

  action_report_wo_margin                   (mrp.production)
  action_report_fp_work_order_portrait      (mrp.workorder)
  action_report_fp_work_order_landscape     (mrp.workorder)
  action_report_fp_wo_sticker               (mrp.workorder)
  action_report_fp_mo_sticker               (mrp.production)
  action_report_fp_job_traveller_mo_landscape (mrp.production)
  action_report_fp_job_traveller_mo_portrait  (mrp.production)

Kept:
  action_report_fp_job_traveller_so_*  (sale.order)
  action_report_fp_so_sticker          (sale.order)

The shared inner sticker templates (report_fp_wo_sticker_inner /
_defaults) stay registered because fp.job + sale.order stickers
both t-call them.

Version: reports 19.0.7.17 -> 19.0.7.18.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 10:53:19 -04:00
gsinghpal
b8fe14e653 Merge feat/fp-native-job-model into main
Some checks failed
fusion_accounting CI / test (fusion_accounting_ai) (push) Has been cancelled
fusion_accounting CI / test (fusion_accounting_core) (push) Has been cancelled
fusion_accounting CI / test (fusion_accounting_migration) (push) Has been cancelled
Native fp.job / fp.job.step model replacing the mrp.production /
mrp.workorder bridge for the Fusion Plating shop. Coexists with
fusion_plating_bridge_mrp during the migration; cutover is gated on
the x_fc_use_native_jobs settings flag.

Highlights from 61 commits:
- New fusion_plating_jobs module with fp.job, fp.job.step, recipe
  expansion, lifecycle hooks, smart buttons, traveller / margin /
  sticker reports, and migration tooling.
- Operator UI consolidated into fusion_plating_shopfloor: Manager
  Desk, Plant Overview, Process Tree, Tablet Station — all bound to
  fp.job / fp.job.step, theme-token compliant in light + dark mode.
- QR scanner OWL component (vendored ZXing-js + jsQR fallback +
  iOS native-camera photo capture).
- /fp/job/<id> + /fp/wo/<id> migration-aware redirects.
- /fp/tank/<id> NFC tank status page.
- Sticker template restored to the canonical ENTECH layout, now
  reused by fp.job + sale.order (one sticker per line with a part).
- Comprehensive workflow seed data (quotation -> paid invoice).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 10:48:11 -04:00
gsinghpal
a317efab45 changes 2026-04-26 10:46:44 -04:00
gsinghpal
3e92a8318d fix(shopfloor): proper ZXing hints + native-camera photo capture path
After 1117 video-frame callbacks ZXing still couldn't see the QR.
Two real fixes verified by reading the vendored bundle:

1) Hints were never being applied. BrowserMultiFormatReader stores
   them in this._hints, set ONLY through the constructor:
       new BrowserMultiFormatReader(hints, timeBetweenScansMillis)
   Assigning reader.hints afterward (what the previous patch did) is
   a no-op. Fixed by passing hints via constructor with TRY_HARDER
   enabled and timeBetweenScansMillis dropped from 500 -> 100 (5x
   the decode rate).

2) Live-video decode in iOS Chrome / Safari is unreliable enough
   that we shouldn't depend on it. Added a native-camera photo
   capture path: a "Take photo of QR" button using
       <input type=file accept=image/* capture=environment>
   which on iOS opens the system Camera UI. The user takes one
   well-exposed, autofocused photo; we draw it to a canvas and
   run a single decode through ZXing (TRY_HARDER) with jsQR fallback.
   Far more reliable than chasing edge cases in live decoding.

Status: Live decode is still tried first. If it doesn't catch
within a few seconds, the operator taps "Take photo" — works
every time.

Version: shopfloor 19.0.23 -> 19.0.24.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 14:07:54 -04:00
gsinghpal
256ce21522 fix(shopfloor): use ZXing's actual API (decodeFromVideoElementContinuously)
The previous patch called reader.decodeFromCanvas which doesn't exist
in @zxing/library 0.21.3. Real methods (verified by grep on the
vendored bundle) are:
  decodeFromVideoElement(el)               -- one-shot
  decodeFromVideoElementContinuously(el, cb) -- continuous loop

Switched to the continuous variant. ZXing manages its own per-frame
timing internally — we just register the (result, err) callback and
React to result.getText() on hits. NotFoundException = no QR this
frame, which we silently ignore.

Also fixed the related video-play race: ZXing internally registers a
'playing' event listener on the video and then calls play() itself.
If we await v.play() ourselves first, the 'playing' event fires
BEFORE ZXing attaches its listener and ZXing then waits forever for
an event that already happened.

Fix: for the zxing path we set attributes + srcObject but do NOT
call play(). ZXing's playVideoOnLoadAsync handles play -> playing ->
decode in the right order. The native and jsQR paths still pre-play
because their loops poll the video themselves.

Cleanup: _stopCamera now calls reader.reset() to tear down ZXing's
internal state cleanly when the modal closes.

Version: shopfloor 19.0.22 -> 19.0.23.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 13:56:47 -04:00
gsinghpal
43397b1854 fix(shopfloor): swap to ZXing-js as primary QR decoder; jsQR is fallback
149 jsQR attempts at full 720x1280 with px:ok and no detection means
the QR in the frame has perspective skew, motion blur, or glare under
jsQR's threshold but well within what real-world phone scanning needs
to handle. jsQR is fast but brittle.

Vendor @zxing/library 0.21.3 (Apache 2.0, ~328KB UMD) and make it the
default decoder. ZXing's HybridBinarizer + perspective transform are
the same algorithm family the iOS Camera app uses internally and they
recover from the cases jsQR rejects.

Decoder selection order:
  1. ZXing-js (window.ZXing.BrowserMultiFormatReader)  -- new default
  2. native BarcodeDetector                            -- if ZXing missing
  3. jsQR                                              -- last-resort

Implementation details:
- Hint ZXing to QR_CODE only so it doesn't waste frames probing
  Code 128 / EAN / PDF417.
- Use decodeFromCanvas on each video frame (rather than ZXing's
  built-in continuous video reader) so we keep ownership of the
  modal UI, the <video> element, and the getUserMedia stream.
- Status line follows the same compact format as the jsQR loop:
    zxing · f1234 a567 720x1280 rs4 r:no_code
  with r:found / r:empty / r:no_code / r:err: ...

Version: shopfloor 19.0.21 -> 19.0.22.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 13:50:42 -04:00
gsinghpal
8e3169e49b fix(shopfloor): jsQR loop — full-res frame + canvas blank-pixel check + last-result trace
271 attempts at 720x1280 with no detection means either downsampling
killed the finder patterns or drawImage is silently painting blank
pixels (a known iOS WebKit failure mode for some video-stream sources).

- Drop the 600px scaling cap. Feed the full native video frame to
  jsQR. Per-frame cost goes up but is still fine; the win is jsQR
  sees finder patterns at full sharpness.
- Add a one-time sanity check that walks the first ImageData buffer
  looking for a non-zero pixel. If everything is 0,0,0 the canvas is
  blank (drawImage failed) and we surface that as 'px:BLANK' in the
  status line — telling us instantly to switch decoder strategies
  rather than chasing tuning.
- Status line now also shows the last jsQR call outcome:
    r:found / r:no_code / r:empty / r:error: ...
  So we can confirm whether jsQR is even being invoked successfully.

Status format compacted to fit one line on phone:
  jsqr · f1234 a567 720x1280 rs4 px:ok r:no_code

Version: shopfloor 19.0.20 -> 19.0.21.
2026-04-25 13:46:47 -04:00
gsinghpal
040f1463b4 fix(shopfloor): jsQR decode loop diagnostics + attemptBoth + 720p stream
The previous loop was running but never finding the QR. Three changes
to make it actually decode AND make any future failure visible:

1. inversionAttempts: 'dontInvert' -> 'attemptBoth'
   Some camera exposures wash out the QR enough that jsQR sees it
   inverted. attemptBoth tries both polarities per frame; the cost is
   ~2x decode time but jsQR is fast enough that scan stays interactive.

2. getUserMedia now requests 1280x720 (with downgrade allowed). The
   default 640x480 only gives ~5 pixels per QR module on a typical
   phone-to-sticker distance — borderline for jsQR. 720p doubles
   that and makes detection near-instant.

3. The status line is now LIVE, updating every 400ms with:
     Decoder: jsqr · frames N · attempts M · video WxH rsN
   So when an operator says "scan does nothing" we can immediately see
   whether the loop is running, whether the camera is feeding frames,
   whether jsQR is being called, and at what resolution. No Web
   Inspector required.

Version: shopfloor 19.0.19 -> 19.0.20.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 13:42:49 -04:00
gsinghpal
9fe7855fc3 fix(shopfloor,reports): scanner status line + sticker rev cleanup
- Re-detect BarcodeDetector / window.jsQR at every modal open instead
  of only at component setup. Avoids the trap where a stale cached
  bundle reports "no decoder" even after a redeploy.
- Add a one-line status indicator at the top of the scan modal showing
  exactly which decoder is active ("Decoder: native" / "Decoder: jsqr"
  / "Decoder: none — paste URL below"). Lets the operator see at a
  glance whether scanning is even possible without round-tripping
  through Safari Web Inspector.
- Sticker: strip a leading "Rev " (case-insensitive) from
  fp.part.catalog.revision before printing so values like "Rev 1"
  don't render as "Rev Rev 1".

Versions: shopfloor 19.0.18 -> 19.0.19, reports 19.0.7.16 -> 19.0.7.17.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 13:30:40 -04:00
gsinghpal
b93633d728 fix(shopfloor,reports): make QR scan actually navigate after decode
Two bugs were colluding to make iPhone scans look like "nothing
happens":

1. The in-app scanner was calling action.doAction({res_model: 'fp.job',
   res_id: <decoded-id>}). Old physical stickers (still on every box)
   encode /fp/wo/<mrp.production.id> — that id space doesn't match
   fp.job, so the form opened on a non-existent record and silently
   showed nothing. New /fp/job/<id> stickers happened to work because
   the IDs lined up by coincidence.

2. The /fp/wo/<id> controller redirected to mrp.production / mrp.workorder
   forms, both of which still exist as legacy records but aren't the
   canonical source of truth post-migration.

Fix:
- qr_scanner._handleCode now navigates via window.location.href instead
  of action.doAction. It hands /fp/job/<n> and /fp/wo/<n> URLs straight
  to the existing server-side controllers, which know how to resolve
  the right record. Bare numeric ids pasted manually -> /fp/job/<n>.
  Anything else surfaces the decoded text as an error so the operator
  can see decode worked but the value isn't a sticker.

- Modal now shows "Detected: <value>" the moment a code is decoded
  (before navigation), so even on slow phones the operator sees
  immediate feedback that the camera read the QR.

- wo_scan.py now resolves in this order:
    1. fp.job by legacy_mrp_production_id (migration-aware — old
       stickers route to the new model)
    2. mrp.production direct browse
    3. mrp.workorder direct browse
    4. fall back to /odoo/plating-jobs (or work-orders list)

Versions: shopfloor 19.0.17.0.0 -> 19.0.18.0.0,
          reports   19.0.7.15.0 -> 19.0.7.16.0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 13:14:06 -04:00
gsinghpal
ecac43eef4 fix(reports): restore the original ENTECH box-sticker layout for fp.job + sale.order
The original mrp.production / mrp.workorder sticker (logo + WO# stack
on the left, big QR on the right, 7-row body with PO/Customer/Process/
Part Number/Due/Qty/Notes — the design ENTECH has been printing for
months) lives in fusion_plating_reports.report_fp_wo_sticker_inner.

The new fp.job sticker had been rebuilt from scratch with a different
look. This wires fp.job into the existing canonical template instead.

What changed:

- report_fp_wo_sticker_inner — every t-set now uses the
  "_var or fallback-from-_mo" pattern so callers can pre-resolve
  values; mrp.production/mrp.workorder callers still work via the
  fallback path.
- report_fp_wo_sticker_defaults — new shared template that initialises
  every overridable name to False so the inner's `or` chain doesn't
  NameError when an outer hasn't set it.
- report_fp_job_sticker_template — replaces the parallel layout with
  a t-call to report_fp_wo_sticker_inner, feeding it from fp.job
  fields (name, partner_id, qty, date_deadline, sale_order_id,
  sale_order_line_ids, recipe_id, part_catalog_id, coating_config_id).
- report_fp_so_sticker — new outer that iterates sale.order.order_line
  and emits one sticker per line that has a part_catalog_id. Bound to
  sale.order's print menu via action_report_fp_so_sticker.

Versions: reports 19.0.7.14.0 -> 19.0.7.15.0,
          jobs    19.0.5.0.0  -> 19.0.5.1.0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 13:03:29 -04:00