From 18b5918d3d10d773ef84de03937c6047ac282e86 Mon Sep 17 00:00:00 2001 From: gsinghpal Date: Sat, 25 Apr 2026 10:38:50 -0400 Subject: [PATCH] fix(shopfloor): Manager Desk speaks fp.job/fp.job.step end-to-end MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous shopfloor consolidation kept the data layer correct (controller queries fp.job.step) but left the UI labels, JS variables, and RPC kwargs in legacy WO/MO vocabulary. Result: every label said 'Unassigned WOs' / 'X WO' even though the underlying records are fp.job.step rows. Renames throughout: wo → step (variable / loop / payload key) WO → Step (label) unassigned_wos → unassigned_steps (KPI key) active_wos → active_steps ready_to_ship_mos → ready_to_ship_jobs mo_id / mo_name / expandedMoId → job_id / job_name / expandedJobId wo_kind → kind, wo_kind_label → kind_label o_fp_mgr_wo_* CSS classes → o_fp_mgr_step_* RPC routes /fp/manager/assign_worker, /fp/manager/assign_tank, /fp/manager/take_over: primary kwarg is step_id; workorder_id accepted as a deprecated alias for one release with a logged warning, so any uncaught caller doesn't break. No layout / visual changes — same UI shape, native vocabulary. SCSS class renames are mechanical (only `_wo_` → `_step_` in selectors); XML updated in lockstep. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../controllers/manager_controller.py | 81 ++++++++---- .../static/src/js/manager_dashboard.js | 40 +++--- .../static/src/scss/manager_dashboard.scss | 26 ++-- .../static/src/xml/manager_dashboard.xml | 116 +++++++++--------- 4 files changed, 149 insertions(+), 114 deletions(-) diff --git a/fusion_plating/fusion_plating_shopfloor/controllers/manager_controller.py b/fusion_plating/fusion_plating_shopfloor/controllers/manager_controller.py index 15cb4e97..85ffdc2d 100644 --- a/fusion_plating/fusion_plating_shopfloor/controllers/manager_controller.py +++ b/fusion_plating/fusion_plating_shopfloor/controllers/manager_controller.py @@ -2,11 +2,11 @@ # Copyright 2026 Nexa Systems Inc. # License OPL-1 (Odoo Proprietary License v1.0) # Part of the Fusion Plating product family. -"""JSON-RPC endpoints for the Manager Dashboard (client action). +"""JSON-RPC endpoints for the Manager Desk (client action). -Native fp.job / fp.job.step edition (consolidated 2026-04-24). All -endpoint URLs are preserved (`/fp/manager/*`); the underlying data -layer is now fp.job + fp.job.step. +Native fp.job / fp.job.step edition. Speaks fp.job/fp.job.step +end-to-end — payload keys, variables, and RPC kwargs all use the +job/step vocabulary. Manager Desk ergonomics: - Column 1 ("Needs a Worker") = jobs that have at least one step @@ -34,8 +34,7 @@ _NEG_JOB_STATES = ('done', 'cancelled') _ACTIVE_JOB_STATES = ('confirmed', 'in_progress', 'on_hold') # A step needs an operator and (for wet/bake/mask) the right equipment -# before the operator can tap Start. Mirrors the legacy -# x_fc_is_release_ready compute on mrp.workorder. +# before the operator can tap Start. def _step_release_readiness(step): """Return (is_release_ready, missing_str) for a fp.job.step.""" missing = [] @@ -55,7 +54,7 @@ def _step_release_readiness(step): def _priority_int(priority): - """fp.job.priority → int 0/1/2 (parallel of legacy x_fc_priority).""" + """fp.job.priority → int 0/1/2.""" return {'rush': 2, 'high': 1, 'normal': 0, 'low': 0}.get(priority, 0) @@ -120,10 +119,10 @@ class FpManagerDashboardController(http.Controller): ) steps_iter = steps_iter.sorted('sequence') - wo_rows = [] + step_rows = [] for s in steps_iter: ready, missing = readiness_by_step.get(s.id, (False, '')) - wo_rows.append({ + step_rows.append({ 'id': s.id, 'name': s.name or '', 'workcenter': s.work_centre_id.name or '', @@ -138,8 +137,8 @@ class FpManagerDashboardController(http.Controller): 'assigned_user_name': s.assigned_user_id.name or '', 'role_id': False, 'role_name': '', - 'wo_kind': s.kind or 'other', - 'wo_kind_label': dict(s._fields['kind'].selection).get( + 'kind': s.kind or 'other', + 'kind_label': dict(s._fields['kind'].selection).get( s.kind, '', ) if s.kind else '', 'is_release_ready': ready, @@ -150,8 +149,8 @@ class FpManagerDashboardController(http.Controller): }) return { - 'mo_id': job.id, - 'mo_name': job.name or '', + 'job_id': job.id, + 'job_name': job.name or '', 'so_name': job.origin or '', 'customer': partner.name if partner else '', 'product': job.product_id.display_name if job.product_id else '', @@ -163,7 +162,7 @@ class FpManagerDashboardController(http.Controller): 'recipe': job.recipe_id.name if job.recipe_id else '', 'priority_any': _priority_int(job.priority), 'current_location': job.current_location or '', - 'wos': wo_rows, + 'steps': step_rows, } unassigned_cards = [_job_card(j) for j in unassigned_jobs] @@ -256,14 +255,14 @@ class FpManagerDashboardController(http.Controller): ready_to_ship_jobs = Job.search_count([('state', '=', 'done')]) kpis = { - 'unassigned_wos': len(all_steps.filtered( + 'unassigned_steps': len(all_steps.filtered( lambda s: not readiness_by_step.get(s.id, (False, ''))[0], )), - 'active_wos': len(all_steps.filtered( + 'active_steps': len(all_steps.filtered( lambda s: readiness_by_step.get(s.id, (False, ''))[0] and s.state in ('ready', 'in_progress'), )), - 'ready_to_ship_mos': ready_to_ship_jobs, + 'ready_to_ship_jobs': ready_to_ship_jobs, 'pending_accept_sos': pending_accept_sos, } @@ -295,10 +294,20 @@ class FpManagerDashboardController(http.Controller): # Assign a worker to a step # ------------------------------------------------------------------ @http.route('/fp/manager/assign_worker', type='jsonrpc', auth='user') - def assign_worker(self, workorder_id, user_id): - """`workorder_id` is the canonical kwarg name from the legacy - XML; it now resolves to a fp.job.step id.""" - step = request.env['fp.job.step'].browse(int(workorder_id)) + def assign_worker(self, step_id=None, user_id=None, workorder_id=None, **kwargs): + """Assign an operator to a step. ``step_id`` is the canonical + kwarg; ``workorder_id`` is accepted as a deprecated alias for + one release so any caller we missed doesn't break. + """ + if step_id is None and workorder_id is not None: + _logger.warning( + "workorder_id kwarg is deprecated; use step_id " + "(/fp/manager/assign_worker)", + ) + step_id = workorder_id + if not step_id: + return {'ok': False, 'error': 'step_id required'} + step = request.env['fp.job.step'].browse(int(step_id)) if not step.exists(): return {'ok': False, 'error': 'Step not found.'} step.assigned_user_id = int(user_id) if user_id else False @@ -313,8 +322,19 @@ class FpManagerDashboardController(http.Controller): # Reassign or swap tank on a step # ------------------------------------------------------------------ @http.route('/fp/manager/assign_tank', type='jsonrpc', auth='user') - def assign_tank(self, workorder_id, tank_id): - step = request.env['fp.job.step'].browse(int(workorder_id)) + def assign_tank(self, step_id=None, tank_id=None, workorder_id=None, **kwargs): + """Swap the tank on a step. ``step_id`` is the canonical kwarg; + ``workorder_id`` is accepted as a deprecated alias. + """ + if step_id is None and workorder_id is not None: + _logger.warning( + "workorder_id kwarg is deprecated; use step_id " + "(/fp/manager/assign_tank)", + ) + step_id = workorder_id + if not step_id: + return {'ok': False, 'error': 'step_id required'} + step = request.env['fp.job.step'].browse(int(step_id)) if not step.exists(): return {'ok': False, 'error': 'Step not found.'} step.tank_id = int(tank_id) if tank_id else False @@ -329,8 +349,19 @@ class FpManagerDashboardController(http.Controller): # Manager takes over a step (no-show coverage) # ------------------------------------------------------------------ @http.route('/fp/manager/take_over', type='jsonrpc', auth='user') - def take_over(self, workorder_id): - step = request.env['fp.job.step'].browse(int(workorder_id)) + def take_over(self, step_id=None, workorder_id=None, **kwargs): + """Manager takes over a step. ``step_id`` is the canonical kwarg; + ``workorder_id`` is accepted as a deprecated alias. + """ + if step_id is None and workorder_id is not None: + _logger.warning( + "workorder_id kwarg is deprecated; use step_id " + "(/fp/manager/take_over)", + ) + step_id = workorder_id + if not step_id: + return {'ok': False, 'error': 'step_id required'} + step = request.env['fp.job.step'].browse(int(step_id)) if not step.exists(): return {'ok': False, 'error': 'Step not found.'} user = request.env.user diff --git a/fusion_plating/fusion_plating_shopfloor/static/src/js/manager_dashboard.js b/fusion_plating/fusion_plating_shopfloor/static/src/js/manager_dashboard.js index 1595052d..665b531b 100644 --- a/fusion_plating/fusion_plating_shopfloor/static/src/js/manager_dashboard.js +++ b/fusion_plating/fusion_plating_shopfloor/static/src/js/manager_dashboard.js @@ -1,15 +1,15 @@ /** @odoo-module **/ // ============================================================================= -// Fusion Plating — Manager Dashboard (OWL client action) +// Fusion Plating — Manager Desk (OWL client action) // Copyright 2026 Nexa Systems Inc. // License OPL-1 (Odoo Proprietary License v1.0) // // Manager-level view: assign workers, swap tanks, cover no-shows, drill -// into detail when needed. Three columns: Unassigned / In Progress / Team. +// into detail when needed. Three columns: Needs a Worker / In Progress / Team. // -// Native fp.job / fp.job.step edition (consolidated 2026-04-24). The -// "wo" naming inside payloads is preserved so the existing XML template -// keeps rendering — those keys now carry fp.job.step rows under the hood. +// Native fp.job / fp.job.step edition. Speaks job/step end-to-end — +// payload keys, variables, and RPC kwargs all use the job/step +// vocabulary. // ============================================================================= import { Component, useState, onMounted, onWillUnmount } from "@odoo/owl"; @@ -29,7 +29,7 @@ export class ManagerDashboard extends Component { overview: null, loadError: "", // visible error instead of stuck spinner mode: "quick", // quick | detailed - expandedMoId: null, + expandedJobId: null, message: "", messageType: "info", isFetching: false, // pulses the "updating" dot in the header @@ -134,8 +134,8 @@ export class ManagerDashboard extends Component { this.state.mode = this.state.mode === "quick" ? "detailed" : "quick"; } - toggleCard(moId) { - this.state.expandedMoId = this.state.expandedMoId === moId ? null : moId; + toggleCard(jobId) { + this.state.expandedJobId = this.state.expandedJobId === jobId ? null : jobId; } toggleOffShift() { @@ -143,7 +143,7 @@ export class ManagerDashboard extends Component { } /** - * Sort + filter the operator list for a specific WO's dropdown. + * Sort + filter the operator list for a specific step's dropdown. * * Buckets, top-down, each kept in original (alphabetical) order: * 1. Qualified for this role AND clocked in — primary picks @@ -155,9 +155,9 @@ export class ManagerDashboard extends Component { * Each option carries a `bucket` so the template can render a tiny * green/grey dot and (for buckets 3-4) a soft helper label. */ - operatorsForWO(wo) { + operatorsForStep(step) { const all = (this.state.overview && this.state.overview.operators) || []; - const roleId = wo && wo.role_id; + const roleId = step && step.role_id; const out = []; for (const op of all) { const qualified = roleId && op.role_ids && op.role_ids.includes(roleId); @@ -184,15 +184,15 @@ export class ManagerDashboard extends Component { } // ---------------------------------------------------------- Actions - async onAssignWorker(wo, userIdRaw) { + async onAssignWorker(step, userIdRaw) { const userId = parseInt(userIdRaw) || null; try { const res = await rpc("/fp/manager/assign_worker", { - workorder_id: wo.id, user_id: userId, + step_id: step.id, user_id: userId, }); if (res && res.ok) { this.setMessage( - `Assigned ${res.user_name || 'unassigned'} to ${wo.name}`, + `Assigned ${res.user_name || 'unassigned'} to ${step.name}`, "success", ); } @@ -202,15 +202,15 @@ export class ManagerDashboard extends Component { await this.refresh(); } - async onAssignTank(wo, tankIdRaw) { + async onAssignTank(step, tankIdRaw) { const tankId = parseInt(tankIdRaw) || null; try { const res = await rpc("/fp/manager/assign_tank", { - workorder_id: wo.id, tank_id: tankId, + step_id: step.id, tank_id: tankId, }); if (res && res.ok) { this.setMessage( - `Tank ${res.tank_name || 'cleared'} for ${wo.name}`, + `Tank ${res.tank_name || 'cleared'} for ${step.name}`, "success", ); } @@ -220,13 +220,13 @@ export class ManagerDashboard extends Component { await this.refresh(); } - async onTakeOver(wo) { + async onTakeOver(step) { try { const res = await rpc("/fp/manager/take_over", { - workorder_id: wo.id, + step_id: step.id, }); if (res && res.ok) { - this.setMessage(`You now own ${wo.name}.`, "success"); + this.setMessage(`You now own ${step.name}.`, "success"); } } catch (err) { this.setMessage(`Takeover failed: ${err.message || err}`, "danger"); diff --git a/fusion_plating/fusion_plating_shopfloor/static/src/scss/manager_dashboard.scss b/fusion_plating/fusion_plating_shopfloor/static/src/scss/manager_dashboard.scss index 3a818681..34c396bd 100644 --- a/fusion_plating/fusion_plating_shopfloor/static/src/scss/manager_dashboard.scss +++ b/fusion_plating/fusion_plating_shopfloor/static/src/scss/manager_dashboard.scss @@ -440,12 +440,12 @@ // ------------------------------------------------------------------------- - // WO row inside expanded card + // Step row inside expanded card // ------------------------------------------------------------------------- - // WO row = info column (vertical stack) + actions column (pickers + buttons) + // Step row = info column (vertical stack) + actions column (pickers + buttons) // Flex with wrap so narrow viewports drop actions below the info naturally // instead of squishing everything into a single broken grid line. - .o_fp_mgr_wo_row { + .o_fp_mgr_step_row { display: flex; flex-wrap: wrap; gap: $fp-space-3; @@ -458,7 +458,7 @@ font-size: $fp-text-sm; } - .o_fp_mgr_wo_info { + .o_fp_mgr_step_info { flex: 1 1 280px; // grows but never narrower than 280px min-width: 0; // allows children to shrink properly display: flex; @@ -466,8 +466,8 @@ gap: $fp-space-1; color: $fp-ink; - // Title row — kind badge + WO name + step number - .o_fp_mgr_wo_title { + // Title row — kind badge + step name + sequence + .o_fp_mgr_step_title { display: flex; align-items: center; gap: $fp-space-2; @@ -477,7 +477,7 @@ line-height: 1.25; } // Meta row — workcenter / role / set equipment - .o_fp_mgr_wo_meta { + .o_fp_mgr_step_meta { display: flex; align-items: center; gap: $fp-space-2; @@ -487,7 +487,7 @@ i { margin-right: 2px; } } // Chip row — what's still missing for the manager to set - .o_fp_mgr_wo_needs { + .o_fp_mgr_step_needs { margin-top: 2px; } } @@ -496,7 +496,7 @@ // takes the remaining horizontal space (the dropdown then grows to // fill); flex-wrap so on narrow widths the dropdown sits on its own // line and the buttons go below at 50/50. - .o_fp_mgr_wo_actions { + .o_fp_mgr_step_actions { display: flex; flex-wrap: wrap; align-items: center; @@ -531,7 +531,7 @@ &:focus { @include fp-focus-ring; border-color: $fp-accent; } } .o_fp_mgr_btn, - .o_fp_mgr_wo_row .btn { + .o_fp_mgr_step_row .btn { min-height: 40px; padding: 0 $fp-space-3; border: none; @@ -549,13 +549,13 @@ @media (max-width: 900px) { // Mobile / narrow tablet: dropdown takes full width on its own // line; the two buttons split 50/50 underneath. - .o_fp_mgr_wo_actions { + .o_fp_mgr_step_actions { flex: 1 1 100%; justify-content: stretch; } .o_fp_mgr_picker { flex: 1 1 100%; } .o_fp_mgr_btn, - .o_fp_mgr_wo_row .btn { + .o_fp_mgr_step_row .btn { flex: 1 1 calc(50% - #{$fp-space-2}); min-height: $fp-touch-min; } @@ -580,7 +580,7 @@ &.o_fp_chip_danger { @include fp-pill(--bs-danger); } &.o_fp_chip_muted { background-color: $fp-card-soft; color: $fp-ink-mute; } - // WO-kind colour bands so the manager can spot + // Step-kind colour bands so the manager can spot // mask vs wet vs bake at a glance. &.o_fp_chip_kind { text-transform: none; diff --git a/fusion_plating/fusion_plating_shopfloor/static/src/xml/manager_dashboard.xml b/fusion_plating/fusion_plating_shopfloor/static/src/xml/manager_dashboard.xml index 5fd2070d..05c013f3 100644 --- a/fusion_plating/fusion_plating_shopfloor/static/src/xml/manager_dashboard.xml +++ b/fusion_plating/fusion_plating_shopfloor/static/src/xml/manager_dashboard.xml @@ -2,7 +2,7 @@ @@ -71,17 +71,17 @@
-
-
Unassigned WOs
+
+
Unassigned Steps
-
+
In Progress
-
+
Ready to Ship
@@ -94,7 +94,7 @@
- +

Needs a Worker

@@ -102,17 +102,17 @@
-
Every active WO has a worker assigned.
+
Every active step has a worker assigned.
- +
+ t-on-click="() => this.toggleCard(card.job_id)">
- + ·
@@ -126,46 +126,48 @@ HOT Urgent - WO + + Step + Steps
- -
+ t-if="state.expandedJobId === card.job_id or state.mode === 'detailed'"> + +
-
-
- - +
+
+ +
-
- - · - · - · - · - · +
+ + · + · + · + · + ·
-
+
- Needs: + Needs:
-
+
-
@@ -214,14 +216,14 @@
Nothing running right now.
- +
+ t-on-click="() => this.toggleCard(card.job_id)">
- + ·
@@ -232,33 +234,35 @@
HOT - WO + + Step + Steps
- -
-
- + t-if="state.expandedJobId === card.job_id or state.mode === 'detailed'"> + +
+
+ - - + + · - +
- - + +