Commit Graph

92 Commits

Author SHA1 Message Date
gsinghpal
a2e254b934 fix(fp.job): post-shop state machine entech smoke fixes (Task 23)
Three bugs caught + fixed during entech battle test:

1. _fp_check_finish_gates calling button_mark_done triggered the
   step-completion gate prematurely (step still in_progress at
   pre-super time). Pass fp_skip_step_gate=True alongside
   fp_check_gates_only — we know the operator is about to finish
   the last open step.

2. _fp_schedule_cert_activity used env.get('fp.notification.template')
   for presence check. env.get returns an EMPTY recordset (falsy),
   not None — 'if not Template: return' silently exited and no
   activity was ever scheduled. Switch to 'in self.env' check
   pattern + explicit indexing. CLAUDE.md Rule 24.

3. _fp_check_advance_after_cert_issue + _fp_check_regress_after_cert_void
   used 'state != issued' as outstanding-cert count. This made
   voided certs count as outstanding forever, so void+re-issue
   cycles never re-advanced. Switch to per-type coverage check:
   each required cert TYPE needs at least one issued cert.
   Regress mirrors: only fire if a type loses all issued certs.

CLAUDE.md gains Rule 24 (env.get falsy empty recordset trap).
Rule 25 (mail.template parse-time validation) renumbered.

Battle test ALL PASS on entech admin DB:
  10/10 steps green — auto-advance, kanban placement, activity
  schedule + auto-resolve, ACL guard, cert issue advance, void
  regress, re-issue advance, manual ship.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 10:45:35 -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
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
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
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
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
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
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
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
7c2ae84e32 feat(jobs): Phase 2 — card_state + mini_timeline + precedence helpers
PV-Phase2 of the plant-view redesign.

Implements the 13-state classifier on fp.job:
  - card_state Char field, stored + indexed for fast filtering
  - _compute_card_state with explicit precedence dispatch matching
    spec §6.2 / §9.3 exactly (no_parts → on_hold → awaiting_signoff
    → awaiting_qc → bake_due → predecessor_locked → idle_warning →
    done → contract_review → running/_mine → ready/_mine)

Six precedence helpers, each isolated for testability:
  _fp_inbound_not_received, _fp_has_open_hold, _fp_has_pending_qc,
  _fp_bake_window_due_soon, _fp_is_mine + _fp_has_unfinished_predecessors
  on fp.job.step.

mini_timeline_json compute: 9-element array (one per column) with
state in {done, current, upcoming} and an optional 'variant' on the
current marker keyed to card_state for renderer color mapping.

Verified live:
  - 14 jobs in contract_review (no active step yet)
  - 8 in no_parts (confirmed + draft fp.receiving)
  - 1 running (WO-30051 with Pre-Measurements at Plating column)
  - mini_timeline JSON renders the full 9-area structure with the
    plating slot marked current+variant=running.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 20:48:14 -04:00
gsinghpal
63d692b322 feat(plating): Phase 1 — plant-view kanban data model foundation
PV-T1: fp.work.centre.area_kind Selection (9 floor columns)
PV-T2: fp.job.step.area_kind compute + _STEP_KIND_TO_AREA fallback
       (covers all 30+ step kinds in the project library, plus the
       spec D4 rule that de_mask folds into de_racking)
PV-T3: fp.job.step.last_activity_at + write hook + message_post
       override + fp.job.step.move.create() hook + _fp_is_idle helper
PV-T4: res.users.paired_work_centre_ids M2M (single-station for MVP,
       forward-compatible for Phase 2 multi-station picker)
PV-T5: res.config.settings.x_fc_shopfloor_layout feature flag backed
       by ir.config_parameter for the landing-action resolver

Migrations:
  fusion_plating 19.0.21.0.0      — backfill area_kind from kind
  fusion_plating_jobs 19.0.10.24.0 — backfill last_activity_at

Deployed + verified on entech:
  - 9/9 fp.work.centre rows have area_kind set
  - 400/400 fp.job.step rows have area_kind + last_activity_at
  - paired_work_centre_ids M2M relation table created
  - All 271 modules loaded cleanly, registry rebuilt in 27s

Part of the 2026-05-23 Shop Floor plant-view kanban redesign.
Plan: docs/superpowers/plans/2026-05-23-shopfloor-plant-view-plan.md
Spec: docs/superpowers/specs/2026-05-23-shopfloor-plant-view-design.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 20:43:15 -04:00
gsinghpal
1a3ca8704e feat(plating): session 2026-05-23 deploys — F1/F7/S22/S23 + UI fixes
Consolidated commit of session work already deployed to entech and
verified via the deep audit + the persona walk:

S22 — Signoff gate (fp.job.step.requires_signoff was 100% unenforced,
42/42 done steps had NULL signoff_user_id). Three-piece fix:
_fp_autosign_if_required (captures finisher on button_finish),
_fp_check_signoff_complete (raises UserError if NULL after autosign),
action_signoff (explicit supervisor pre-sign). Bypass:
fp_skip_signoff_gate=True.

S23 — Transition-form gate (same dormant-field shape as S22, caught
preventively before recipe authors flipped requires_transition_form
on). Model helpers on fp.job.step.move + controller gate in
move_controller (parts commit) + pre-reject in rack commit.

F7 — Chatter standardization: _fp_create_qc_check_if_needed,
_fp_fire_notification, _fp_create_delivery silent failures now also
post to job chatter instead of only logging to file.

UI fixes:
- Critical Rule 20 documented + applied: OWL templates only expose
  Math as a global. Calling String(d) inside t-on-click throws
  'v2 is not a function'. Fixed pin_pad.xml (string array instead of
  number array with String() coercion). Also swept parseInt/
  parseFloat in recipe_tree_editor + simple_recipe_editor.
- Notes panel HTML escape fix: chatter messages off /fp/workspace/load
  were rendered via t-out, escaping the HTML. Wrap with markup() in
  job_workspace.js refresh() before assigning to state.

Versions:
  fusion_plating         19.0.20.8.0 → 19.0.20.9.0
  fusion_plating_jobs    19.0.10.20.0 → 19.0.10.23.0
  fusion_plating_shopfloor 19.0.30.2.0 → 19.0.30.5.0

All deployed to entech (LXC 111) and verified live.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 20:37:17 -04:00
gsinghpal
4a304e02f3 fix(fusion_plating_jobs): restore quick_look_qty + instruction_attachment_ids
Same regression as the previous commit — b0070afc removed all 4 quick_look
related fields, my first fix only caught 2 of them. Restoring the remaining
2 so the quick-look view fully validates.
2026-05-22 22:47:43 -04:00
gsinghpal
0d08d2d135 fix(fusion_plating_jobs): restore quick_look_partner_id + quick_look_part_catalog_id
Commit b0070afc removed these two related fields from fp.job.step but
the view fp_job_step_quick_look_views.xml still references them. The
mismatch was dormant because entech never ran -u between b0070afc
and the 2026-05-22 deploy. Re-running -u during the Phase 1-4 deploy
caught it:

  Field "quick_look_part_catalog_id" does not exist in model
  "fp.job.step"

Restoring both as related fields (zero-cost, fixes the view without
touching XML).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 22:46:18 -04:00
gsinghpal
c06d3d442a feat(fusion_plating_jobs): auto-pause cron for stale in-progress steps
Plan tasks P2.4 + P2.5 batched.

Adds _cron_autopause_stale_steps method on fp.job.step + 30-min cron
registration. Flips in_progress steps idle > threshold to paused with
a chatter audit ("Auto-paused after Nh idle. Resume from the tablet
when work continues.").

Threshold from ir.config_parameter:
    fp.shopfloor.autopause_threshold_hours  (default 8.0)

Recipe nodes opt out via fusion.plating.process.node.long_running
(added in P2.1) — useful for 24h bakes and multi-shift soaks.

Fixes the 411-hour ghost timer that motivated the redesign. Doesn't
replace the existing nudge crons — those still notify the supervisor;
this one actually pauses the timer.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 22:03:20 -04:00
gsinghpal
c76eb94724 feat(fusion_plating_jobs): late_risk_ratio + active_step_id computes on fp.job
Plan tasks P2.2 + P2.3 batched (both small additive computes on fp.job;
local tests not run between them — entech verifies).

  late_risk_ratio  — stored Float, remaining_planned / minutes_to_deadline.
                     Drives the Manager At-Risk view (Phase 4).
                     Recomputes on step state, duration, deadline changes.

  active_step_id   — non-stored Many2one. Currently in_progress step
                     (lowest sequence if multiple — defensive).
                     Drives JobWorkspace landing focus.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 22:01:58 -04:00
gsinghpal
3fdbeed813 feat(fusion_plating_jobs): Open Workspace smart button on fp.job form
Plan task P1.16. Header button on the fp.job form that opens the
JobWorkspace OWL client action focused on the current WO. Primary
entry point for techs before the Landing kanban (Phase 3) ships;
remains as a back-office shortcut after.

Hidden when state == 'draft' (no steps to work yet).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 21:53:19 -04:00
gsinghpal
81da9bf71c feat(fusion_plating_jobs): fp.job.step blocker_kind/reason/jump_target computes
Plan task P1.2. Reuses _fp_should_block_predecessors so the new compute
stays in sync with the existing can_start logic. Drives the OWL GateViz
component on the tablet — "Can't start yet — Waiting on Step N: X".

Future work: extend with explicit branches for contract_review /
parts_not_received / racking_required / manager_input as those gate
models mature.

Tests not run locally (no fusion_plating mount in odoo-modsdev).
Verify on entech: -u fusion_plating_jobs --test-tags fp_jobs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 21:44:15 -04:00
gsinghpal
1d04ac8cb7 feat(fusion_plating_jobs): fp.job.display_wo_name compute (WO # 00001)
Plan task P1.1. Formats fp.job.name as "WO # <last-segment>" for
tablet/dashboard surfaces. Underlying name field is unchanged so
back-office forms, reports, and emails keep WH/JOB/00001.

Tests not run locally — fusion_plating not mounted in odoo-modsdev
container. Verify on entech: -u fusion_plating_jobs --test-tags fp_jobs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 21:43:36 -04:00
gsinghpal
f661724c72 changes 2026-05-22 18:01:31 -04:00
gsinghpal
53fd6114e7 changes 2026-05-21 03:42:46 -04:00
gsinghpal
1314f4581d changes 2026-05-21 03:37:25 -04:00
gsinghpal
091f98e1f9 changes 2026-05-18 22:33:23 -04:00
gsinghpal
f8586611c9 fix(portal): derive portal_job initial state from fp.job.state on create
_fp_create_portal_job hardcoded state='in_progress'. Now uses the
same _FP_JOB_STATE_TO_PORTAL_STATE map as write(), so a portal job
created for an already-confirmed (but not yet started) fp.job lands
in 'received' instead of jumping to 'in_progress'. Falls back to
'received' for unmapped states.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 03:16:22 -04:00
gsinghpal
28220f0732 fix(portal): 5 hotfixes - /my route, button sizing, clickable cards, state sync, SO doc
1. /my now serves the FP dashboard (stock Odoo home was leaking
   through because parent route declared ['/my', '/my/home'] but my
   override only listed /my/home).
2. Button padding bumped to .5rem 1rem + font 1rem so o_fp_btn matches
   Odoo's standard Bootstrap button rhythm. Ghost button drops its
   custom padding override.
3. .o_fp_job_card on /my/home + /my/jobs is now an <a> wrapping the
   whole card area — full row is the click target, not just the WO
   number. Inner <a> on job.name dropped to avoid nested anchors;
   focus-visible outline added for keyboard nav.
4. fp.job.write() now mirrors state -> fp.portal.job.state via new
   _FP_JOB_STATE_TO_PORTAL_STATE map (confirmed->received,
   in_progress->in_progress, done->ready_to_ship). Fixes the bug where
   completed backend jobs left the portal stuck on 'in_progress'.
   'on_hold' and 'cancelled' intentionally not mirrored — manager
   choice what to surface.
5. Sales Order Confirmation now surfaces in the 'From You' group on
   the job detail page, pulled via job.x_fc_job_id.sale_order_id ->
   /report/pdf/sale.report_saleorder/<id>. Falls back to the upload
   placeholder when no SO is linked.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 03:13:00 -04:00
gsinghpal
152ed86c3a feat(thickness): single Char range field — drop fp.recipe.thickness picker
Per client direction: every order is a thickness RANGE (e.g.
"0.0005-0.0008 mils" or "5-10 mils"), never a single value. The
old picker model (fp.recipe.thickness with a single 'value' Float)
was modelling the wrong concept and overcrowding the order entry
UI. Replaced with one free-text Char field that auto-fills from
last-used or part default.

DELETED entirely:
- fp.recipe.thickness model (file + view + ACL + manifest entry)
- recipe.thickness_option_ids One2many (the picker source)
- "Thickness Options" inline list on the recipe form
- sale.order.line.x_fc_thickness_id (M2O picker)
- account.move.line.x_fc_thickness_id
- fp.delivery.x_fc_thickness_id
- fp.direct.order.line.thickness_id

ADDED:
- sale.order.line.x_fc_thickness_range (Char) — operator types range
- account.move.line.x_fc_thickness_range — for invoice rendering
- fp.delivery.x_fc_thickness_range — for packing slip
- fp.direct.order.line.thickness_range — for the wizard
- fp.part.catalog.x_fc_default_thickness_range — part default

AUTO-FILL CHAIN (sale.order.line + wizard line):
1. Operator already typed → keep
2. Most recent SO line for (this part, this customer) with a
   non-empty thickness_range → copy that
3. part.x_fc_default_thickness_range → copy
4. Blank — operator types

Implemented as both an @api.onchange (interactive) AND a
create() override (programmatic — wizard, sale_mrp bridge,
imports). Same logic in both paths.

WIZARD push-to-defaults: when "Save as Default" toggle is ticked
on a wizard line, persist the line's thickness_range to
part.x_fc_default_thickness_range so future first-customer orders
get a sensible starting point.

REPORTS: customer_line_header.xml + report_fp_wo_sticker.xml now
print the Char range as-typed (no display_name lookup needed).

KEPT (admin documentation only — doesn't affect order entry):
- recipe.thickness_min, thickness_max, thickness_uom on the recipe
  root: documents the recipe's CAPABILITY range. No UI gate; just
  for spec authors to record what the chemistry can produce.

JOB GROUPING: fp.job auto-create groups SO lines by (recipe, part,
spec, thickness, serial). Updated to key on the thickness_range
Char (stripped) instead of the deleted thickness_id integer.

DB cleanup: --update=base ran on the upgrade, dropping the
fp_recipe_thickness table + the four x_fc_thickness_id columns.
Existing data was already nulled in earlier dev work.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 08:54:40 -04:00
gsinghpal
d891002c84 feat(promote-customer-spec): Phase E — final removal of coating + treatment
DELETED entirely (model + view + ACL + data file + menu):
- fp.coating.config (configurator)
- fp.treatment (configurator + seeded data)
- fp.coating.thickness (configurator) — replaced by fp.recipe.thickness in Phase A
- fp.customer.price.list (configurator) — coating-keyed, no replacement

Field deletions:
- sale.order.x_fc_coating_config_id
- sale.order.line.x_fc_coating_config_id + x_fc_treatment_ids
- account.move.line.x_fc_coating_config_id
- fp.part.catalog.x_fc_default_coating_config_id + x_fc_default_treatment_ids
- fp.job.coating_config_id
- fp.pricing.rule.coating_config_id
- fp.quality.point.coating_config_ids
- fp.direct.order.line.coating_config_id + treatment_ids
- fp.sale.description.template.coating_config_id

Refactored:
- fp.quote.configurator.coating_config_id → recipe_id (now points at
  fusion.plating.process.node, the actual recipe). All compute, onchange,
  and matcher logic updated to use recipe directly. Quality inherit
  extends matcher with spec-tier scoring.
- fp.job._fp_create_certificates now reads spec from job.customer_spec_id
  and formats spec_reference as "code Rev rev". Same for thickness
  source — bake fields read from recipe_root (Phase A).
- fp.job.step.button_finish bake-window auto-spawn reads bake settings
  from recipe_root instead of coating.
- fp.certificate auto-fill spec_min_mils/max_mils from recipe (Phase A
  thickness fields) instead of coating.
- jobs/sale_order.py: job creation reads x_fc_customer_spec_id from
  line, drops coating refs and the legacy header-coating fallback.
- Wizards drop coating + treatment fields and refs.
- Configurator views drop x_fc_coating_config_id + x_fc_treatment_ids
  fields entirely. Quality inherits re-anchor on stable fields
  (x_fc_part_catalog_id, x_fc_internal_description, default_process_id,
  process_variant_id, substrate_material) so they keep working.
- Reports drop coating fallback elifs; print recipe / spec.
- Tablet payload drops coating_config_id from job.read fields.

Skipped (deferred to backlog):
- fusion_plating_bridge_mrp — module is uninstalled per Sub 11; source
  files retain coating refs but no runtime impact.
- fusion_plating_portal — circular dep (portal → quality → certs →
  portal). Customer-facing portal coating picker stays for now;
  promote-spec polish is a separate sub-project.

Verification: grep for "coating_config_id|fp.coating.config|
fp.treatment|fp.coating.thickness" in live (non-bridge_mrp,
non-portal, non-script, non-test) Python/XML/CSV returns 3 hits,
all in module / class docstrings explaining Phase E history.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 02:00:41 -04:00
gsinghpal
c637f82ae2 feat(promote-customer-spec): Phase C — pricing, quality, job, cert re-keyed
Pricing:
- Quality inherit on fp.pricing.rule adds customer_spec_id + recipe_id
- Quality inherit on fp.quote.configurator adds customer_spec_id field
  + extends _find_matching_rule with priority chain:
    spec (+8) > recipe (+6) > coating (+4) > material (+2) > cert (+1)
- View inherit surfaces both new pickers on the rule form

Quality points:
- fp.quality.point now has customer_spec_ids + recipe_ids M2M filters
- Matcher (_matches + _find_matching) accepts new args
- Hook overrides on SO confirm + job confirm/done + step finish
  pass spec/recipe context through to the matcher
- View surfaces both new M2M widgets

Job:
- jobs/sale_order.py wires x_fc_customer_spec_id from SO line to
  fp.job.customer_spec_id on action_confirm

Cert:
- Quality inherit on fp.certificate adds customer_spec_id field +
  create() override auto-fills spec_reference from spec.code+revision
  Resolution priority: explicit spec_reference > cert.customer_spec_id
  > SO line spec (with print_on_cert) > legacy coating fallback

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 01:23:06 -04:00
gsinghpal
f07e1bcce1 fix(chatter): wrap HTML message_post bodies in Markup() — 4 sites
Four message_post calls were passing strings with HTML tags as
plain `body=_(...)` instead of `body=Markup(_(...))`. Odoo escapes
non-Markup strings, so the chatter rendered "<b>QA Review failed</b>"
as literal text instead of bolding it.

Original bug surfaced via the Contract Review (QA-005) flow:
  body: "&lt;b&gt;QA Review failed&lt;/b&gt; by Garry Singh. Awaiting
  client information.&lt;br/&gt;&lt;b&gt;Reason:&lt;/b&gt;&lt;br/&gt;
  &lt;div data-oe-version=\"2.0\"&gt;Need to get updated
  drawing...&lt;/div&gt;"

Audit scan turned up three more identical patterns:

  fusion_plating/models/fp_parent_numbered_mixin.py:118
     "Issued <strong>%s</strong> to ..."
  fusion_plating_jobs/models/sale_order.py:282
     "Confirmed quote <strong>%s</strong> as <strong>%s</strong>."
  fusion_plating_quality/models/fp_contract_review.py:430
     "<b>QA Review failed</b> by ... <b>Reason:</b><br/>%(reason)s"
  fusion_plating_quality/models/fp_contract_review.py:524
     "<b>QA Review completed</b> by ... <b>Special Instructions
      captured:</b><br/>%(notes)s"

Fixes:
- Wrapped each body=_(...) with Markup(_(...)) using the
  Markup(template) % values pattern (auto-escapes the substituted
  values; user-supplied free text stays safe).
- For Html-field substitutions (qa_failure_reason,
  special_instructions), explicitly wrapped the value in Markup()
  so already-formatted HTML editor content (with data-oe-version="2.0"
  wrapper divs) flows through without being re-escaped.
- Added `from markupsafe import Markup` to the two files that
  didn't already import it (mixin + contract_review).

Drift cleanup: pulled the 180-line newer fp_contract_review.py
from entech to the local repo (added action_qa_review_failed,
action_open_client_email_wizard, action_view_client_emails,
action_complete_after_info, awaiting_info state, qa_failure_reason
+ special_instructions Html fields, etc. that had been edited on
entech without being committed).

Tested by re-posting via odoo shell on review 10: body now stores
"<b>QA Review failed</b>..." with literal HTML tags instead of
the double-escaped "&lt;b&gt;..." entities. Old chatter records
with the bad escape stay as-is in the audit trail.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 08:41:39 -04:00
gsinghpal
e4681a58c6 fix(jobs): split fp.jobs by thickness + serial on SO confirm
The _fp_auto_create_job grouping key was (recipe, part, coating).
Lines that shared all three but differed in thickness (or serial)
silently collapsed into one fp.job — the second line's thickness/SN
was lost, and any downstream cert printed the first line's values
across both batches. Silent mis-attestation = compliance hole.

Extended the key tuple to (recipe, part, coating, thickness, serial).
Single-line SOs and same-(thickness, SN) multi-line SOs collapse
identically to before. Only lines that previously merged when they
shouldn't have now split into their own fp.jobs.

TDD via test_so_confirm_splits_by_thickness:
  - seeds the part with default_process_id so both lines hit the
    `if recipe:` branch (where the bug lived — the no_recipe branch
    already split correctly per line)
  - confirms 2 jobs after action_confirm with each carrying its
    own thickness via the linked SO line

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 07:57:56 -04:00
gsinghpal
7a02382623 fix(reports): WO Margin model name must match report_name + '_template' suffix
The previous fix swapped t-field -> t-esc so the QWeb error stopped,
but the report still printed blank. Root cause: Odoo looks up the
report data model via env['report.<report_name>'], but our model was
named 'report.fusion_plating_jobs.report_fp_job_margin' while the
action's report_name is 'fusion_plating_jobs.report_fp_job_margin_template'.
The model lookup missed, _get_report_values never fired, and the
template rendered with no 'rows' in scope — empty foreach -> empty
page.

Renamed the model to report.fusion_plating_jobs.report_fp_job_margin_template.

Verified: PDF size jumped from 1229 bytes (blank) to 125880 bytes
(fully populated). HTML now contains 'Job Margin', 'Step Breakdown',
and the actual WO name.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 19:08:38 -04:00
gsinghpal
457d9b7dbf fix(numbering): post-review fixes — credit notes, SO unlink, multi-part grouping, SQL whitelist
- B1: Add Credit Note wizard path was blocked because invoice_origin
  has copy=False and the wizard doesn't set fp_from_so_invoice. Now
  the validator allows reversals when reversed_entry_id points at a
  customer-facing move that itself went through the validator at
  original creation time. account.move._fp_parent_sale_order also
  walks self.reversed_entry_id._fp_parent_sale_order so the credit
  note inherits the parent number (CN-<parent>).

- Bug 1: sale.order.unlink() now blocks deletion when x_fc_parent_number
  is set (matches spec §6.2). Draft quotes remain freely deletable
  per Odoo standard. Applies to all users including admins.

- Bug 2: out_receipt added to CUSTOMER_TYPES so POS-style receipts
  hit the same SO-flow gate as out_invoice / out_refund.

- C1: WO grouping key changed from recipe.id to (recipe.id, part.id,
  coating.id). Bundling lines with different parts under one WO put
  first_line's part_number on the CoC header — silent compliance
  mis-attestation. Now distinct parts always get distinct WOs even
  when they share a recipe.

- C3: SQL whitelist (_FP_COUNTER_FIELD_RE) on _fp_assign_parent_name's
  interpolated counter field name. No user input today; defence in
  depth for future subclasses that might read the name from context.

Verified on entech: parent=30017, credit note = CN-30017,
multi-part SO produces 2 WOs (one per part), confirmed-SO unlink
blocked, out_receipt blocked, whitelist regex enforced.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 18:19:08 -04:00
gsinghpal
0d85063b5e feat(numbering): wire CoC/RCV/DLV/PU into parent-numbered mixin + rename counters
Per-model counter fields on sale.order renamed to x_fc_pn_*_count
to avoid collision with pre-existing compute fields of the same
short name in bridge_mrp / receiving / configurator (silent
compute-override was suppressing the storage). 4 child models
(fp.certificate, fp.receiving, fusion.plating.delivery,
fusion.plating.pickup.request) now derive names as PFX-<parent>
with -NN suffix from the 2nd onward.

fusion.plating.pickup.request gains a sale_order_id field
(optional) so pickups created against an SO get parent-derived
names, while standalone pickups (pre-SO) fall back to PU/YYYY/NNNN.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 13:30:37 -04:00
gsinghpal
765a0a4c82 feat(numbering): block direct invoice creation + wire account.move into mixin
Customer invoices (out_invoice / out_refund) can only be created via
sale.order._create_invoices() or with an invoice_origin matching an
existing SO. Applies to ALL users including admins. Once created,
the move's name is derived from the SO's parent number: IN-30000,
IN-30000-02, CN-30000, ... Pre-existing portal-job link on
action_post() preserved.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 13:21:09 -04:00
gsinghpal
6c6fb8d2a4 feat(numbering): WO grouping by recipe + parent-derived bulk naming
Replaces x_fc_wo_group_tag grouping with resolved-recipe grouping.
Bare WO-<parent> when 1 recipe, WO-<parent>-NN zero-padded for N>1
ordered by min line sequence. fp.job inherits parent-numbered mixin
for the manual-add path; bulk SO-confirm sets names explicitly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 13:18:10 -04:00
gsinghpal
1b1bebdcd8 feat(numbering): assign parent_number + rename to SO-<n> on confirm
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 13:14:47 -04:00
gsinghpal
e0d1998811 feat(numbering): draw quote name from fp.quote.number on SO create
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 13:12:45 -04:00
gsinghpal
bc3f584851 feat(numbering): add parent_number + counters to sale.order
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 13:10:55 -04:00
gsinghpal
cba9a6da6b feat(jobs): mirror delivery_method/ship_via/invoice_strategy on fp.job
Tier 3 of the SO->fp.job persistence audit. Three logistics/billing
fields surface on fp.job as related read-only (not stored) mirrors:

- x_fc_delivery_method - Local Delivery / Shipping Partner / Customer
  Pickup. Cargo classification used by logistics planning.
- x_fc_ship_via - Carrier name (UPS, FedEx, customer pickup, etc.).
- x_fc_invoice_strategy - Deposit / Progress / Net Terms / COD-Prepay.
  Read by the invoicing module's hooks; mirroring on the WO is for
  manager visibility only.

These were intentionally chosen as related (not stored persisted)
because the SO is the authoritative source - the existing downstream
code (delivery + invoicing modules) already reads them off SO directly.
A stored copy would risk drift. Related auto-follows SO updates.

Same three fields also mirrored on sale.order.line as stored related
for per-line list visibility.

Closes the SO->fp.job persistence audit. All 10 operational fields
identified now flow through to the WO (7 stored + populated at confirm,
3 related read-only).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 08:39:12 -04:00
gsinghpal
15eac309ee feat(jobs): persist deadlines + planned start + notes on fp.job
Tier 2 of the SO->fp.job persistence audit. Four operational metadata
fields mirrored from sale.order:

- x_fc_internal_deadline (Date) - shop's internal target finish date,
  ahead of the customer-facing deadline. Kept separate from
  date_deadline (which scheduling code may adjust).
- x_fc_planned_start_date (Date) - customer-quoted planned start date.
  Kept separate from date_planned_start (Datetime, capacity-adjusted).
- x_fc_internal_note (Text) - shop-internal notes from the order.
- x_fc_external_note (Text) - customer-facing notes, printed on
  traveller / BoL / cert.

All four populate at SO confirm via _fp_auto_create_job, and surface
on sale.order.line as stored related fields for per-line visibility.
fp.job form view gets a Notes group alongside the Customer References
group from Tier 1.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 08:37:19 -04:00
gsinghpal
7d37f5713c feat(jobs): persist Customer Job # + PO # + Rush Order on fp.job
Tier 1 of the SO->fp.job persistence audit. Three customer-reference
fields entered on sale.order's Plating tab were not flowing through
to fp.job (or SO lines), so the shop floor and printed paperwork
(traveller, BoL, cert) had to round-trip via sale_order_id every time.

Changes:
- fp.job: new x_fc_customer_job_number (Char, tracking), x_fc_po_number
  (Char, tracking), x_fc_rush_order (Boolean, tracking). All three
  populated by _fp_auto_create_job at SO confirm time.
- sale.order.line: x_fc_customer_job_number / x_fc_po_number added as
  stored related fields off order_id so per-line list views show the
  customer's references without navigating to the order header
  (x_fc_rush_order was already on lines).
- fp.job form view: small Customer References group under the title
  surfaces the three fields where the user expects them.

Verified end-to-end: SO -> SO line related fields -> fp.job direct
fields all carry the same value.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 08:34:56 -04:00
gsinghpal
cd2584d6ee ui(rename): "Plating Job" -> "Work Order" / display "WO # 01368"
Standardise user-facing terminology across 5 modules (27 files):
  - display_name compute: 'Work Order # 01368' -> 'WO # 01368'
  - _description on 5 models: Plating Job{," Step"," Step Time Log"," Margin Report"," Recipe Node Override"} -> Work Order equivalents
  - field labels (string=...) on 13 Many2one / One2many fields
    across fp.batch, fp.thickness_reading, fp.quality.hold,
    fp.job_consumption, fp.portal.job, fp.certificate, fp.delivery,
    fp.quality.check, fp.racking.inspection, res.partner, sale.order
  - XML view labels: action names, list/form/search strings,
    portal template names, dashboard tile titles

What's deliberately preserved:
  - DB model name 'fp.job' (technical identifier — used by
    sale_order.x_fc_plating_job_ids and all comodel refs)
  - Module name 'fusion_plating_jobs' (directory / import path)
  - Settings -> Apps display label 'Fusion Plating Jobs' (module
    identity for Odoo's app picker)
  - 'Use Native Plating Jobs' migration toggle (internal mechanism
    flag, not user-facing terminology)

Verified on entech: WH/JOB/01368 now displays as 'WO # 01368'
everywhere humans look (form header, breadcrumbs, M2O dropdowns,
error messages, smart-button titles).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 08:22:09 -04:00
gsinghpal
dcbe8305d0 ui(process-tree): back to Work Order + pulsing green for done steps
Two improvements to the Process Tree visualization opened from the
Work Order's Process Tree header button:

  1. Back button returns to the Work Order (job form) instead of
     Plant Overview. fp.job.action_open_process_tree now passes
     back_job_id in the client-action context; process_tree.js
     reads it via a new backJobId getter, updates the button label
     to "Back to Work Order", and routes onBack to fp.job form.
     The Plant Overview fallback stays for callers that don't pass
     either back_step_id or back_job_id.

  2. Completed operation/step cards now have a green fill (#1e8449)
     and a subtle pulsing glow (box-shadow animation, 2.6s alternate)
     so finished work pops against still-pending dark cards. Hover
     pauses the animation so the click target is steady. Reuses the
     same green the workflow-state slice already used.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 08:13:46 -04:00
gsinghpal
30a1141997 ui+fix(jobs): compact row buttons + remove racking inspection gate
UI: secondary row buttons in the embedded step list are now icon-only
with tooltips (Pause / Complete 1 → Next / Record / Skip / Move…).
Saves ~70% horizontal space. "Finish & Next" stays text+icon as the
primary action.

Fix: removed the racking-inspection gate from button_finish. Racking
is now a recipe step (not a separate inspection workflow), so the
"Racking inspection for ... is Inspecting — must be Done" error no
longer fires. _fp_check_racking_inspection_complete() helper is
preserved for diagnostics but no longer called from the finish path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 01:01:50 -04:00
gsinghpal
a0644a7e5c fix(jobs): Finish & Next bulk-moves all parts, no more "click N times"
User feedback: operators with small parts (e.g. valve bodies) batch
them through the whole recipe. The previous behavior — Finish & Next
raising "use Complete 1 → Next or Move..." when qty>1 — forced N
clicks for a workflow that's naturally one click.

Change: _fp_record_one_piece_auto_move now ALWAYS bulk-moves
qty_at_step parts to the next step in one move record, regardless of
whether the qty is seed-only (first / paperwork step) or real (parked
from an upstream move). Audit trail is preserved (one move row per
finish), operator gets one click.

Three buttons now map cleanly to the three workflows:
  - Finish & Next: bulk all parts forward, finish, auto-start next
  - Complete 1 -> Next: streaming flow, move 1 part, stay open
  - Move...: explicit qty + destination wizard for partial batches

Verified end-to-end on entech: seed qty=6 + real-incoming qty=6 both
move forward in a single click each.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 00:55:03 -04:00
gsinghpal
8b5472bf4e fix(jobs): qty gate false-positive on paperwork / first steps
The qty gate I added refused Finish on steps where qty_at_step > 0,
to force operators to move parts forward first. But the first-step
seed in _compute_qty_at_step gives the earliest non-terminal step
a notional qty = job.qty — a UI hint, not actual parked parts.
Paperwork steps (Contract Review, Inspection-by-paperwork, etc.)
sit on that seed, and the gate was blocking Finish with a misleading
error.

Fixes:
- button_finish gate now checks for REAL incoming moves before
  refusing. Seed-only qty (no incoming_move_ids filtered to non-
  self-loop) is exempt.
- _fp_record_one_piece_auto_move detects seed-only qty and bulk-
  moves ALL parts in one shot to the downstream step. Correct for
  paperwork / first steps where parts don't physically wait
  per-piece — one click finishes the paperwork and pushes the whole
  batch forward.

For steps with REAL incoming moves (parts actually moved here via
a Move record), the original gate semantics still apply: qty == 1
auto-moves one part; qty > 1 raises with the "use Complete 1 → Next
or Move…" message.

Verified on entech: Contract Review with seed qty=6 now finishes
cleanly, bulk-moving all 6 parts to the next step in one move.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 00:44:29 -04:00
gsinghpal
d6bda9740f fix(jobs): Received workflow milestone keys off pre-recipe receiving
The Received milestone was tied to recipe steps tagged
default_kind='receiving'. But receiving in this system is a pre-
recipe inbound logistics flow (fp.receiving model in
fusion_plating_receiving). When parts physically arrive, the flow
sets sale_order.x_fc_receiving_status to partial or received.

Changes:
  - New trigger_on_parts_received Boolean on fp.job.workflow.state.
  - _fp_is_passed_for_job branch: passes when sale_order's
    x_fc_receiving_status is in (partial, received).
  - _compute_workflow_state_id depends extended with
    sale_order_id.x_fc_receiving_status so the bar recomputes
    automatically when the receiving flow updates the SO.
  - DB seed update: Received state drops trigger_default_kinds=
    'receiving' and gains trigger_on_parts_received=True.

Verified end-to-end on entech: bar moves Confirmed → Received on
status change, regresses on rollback, accepts both 'partial' and
'received' as satisfying the milestone.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 00:34:19 -04:00