From 11dbbf578ed001e0744045a225ddc716e647cd12 Mon Sep 17 00:00:00 2001 From: gsinghpal Date: Mon, 27 Apr 2026 21:19:05 -0400 Subject: [PATCH] feat(sub12b): plant overview Racks pane (Task 16) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- .../controllers/shopfloor_controller.py | 45 +++++++++++++++++++ .../static/src/js/plant_overview.js | 21 +++++++++ .../static/src/scss/move_dialogs.scss | 44 ++++++++++++++++++ .../static/src/xml/plant_overview.xml | 37 +++++++++++++++ 4 files changed, 147 insertions(+) diff --git a/fusion_plating/fusion_plating_shopfloor/controllers/shopfloor_controller.py b/fusion_plating/fusion_plating_shopfloor/controllers/shopfloor_controller.py index 754157c6..a2921465 100644 --- a/fusion_plating/fusion_plating_shopfloor/controllers/shopfloor_controller.py +++ b/fusion_plating/fusion_plating_shopfloor/controllers/shopfloor_controller.py @@ -1274,11 +1274,56 @@ class FpShopfloorController(http.Controller): 'cards': cards_by_wc[0], }) + # Sub 12b — Racks pane payload alongside the existing parts cards. + # Filters to racks currently in active racking-state. Each entry + # has the data the OWL plant overview's Racks pane renders: + # tag chips, current node breadcrumb, part count, and the + # rack-level MOVE RACK button target. + rack_domain = [ + ('racking_state', 'in', ('loaded', 'in_use', 'awaiting_unrack')), + ('active', '=', True), + ] + if facility_id: + rack_domain.append(('facility_id', '=', int(facility_id))) + racks_payload = [] + for r in env['fusion.plating.rack'].search(rack_domain): + cur_step = r.current_job_step_id + racks_payload.append({ + 'id': r.id, + 'name': r.name, + 'racking_state': r.racking_state, + 'tag_ids': [ + {'id': t.id, 'name': t.name, 'color': t.color} + for t in r.tag_ids + ], + 'current_part_count': r.current_part_count, + 'current_node_name': cur_step.name if cur_step else '', + 'current_tank_code': ( + r.current_tank_id.code if r.current_tank_id else '' + ), + 'current_step_id': cur_step.id if cur_step else False, + # Default destination: next sibling step in the recipe + # sequence. Falls back to current_step_id if no successor + # (operator can pick a different one in the dialog). + 'next_step_id': self._fp_next_step_id(cur_step) if cur_step else False, + }) + return { 'facility_name': facility_name, 'columns': columns, + 'racks': racks_payload, } + def _fp_next_step_id(self, step): + """Return the id of the next step in this job's recipe sequence, + or False if `step` is the last.""" + if not step or not step.job_id: + return False + successors = step.job_id.step_ids.filtered( + lambda s: s.sequence > step.sequence + ).sorted('sequence') + return successors[0].id if successors else False + # ------------------------------------------------------------------ # Urgency scoring (v19.0.24.8.0) # ------------------------------------------------------------------ diff --git a/fusion_plating/fusion_plating_shopfloor/static/src/js/plant_overview.js b/fusion_plating/fusion_plating_shopfloor/static/src/js/plant_overview.js index b7afdb61..3522ef08 100644 --- a/fusion_plating/fusion_plating_shopfloor/static/src/js/plant_overview.js +++ b/fusion_plating/fusion_plating_shopfloor/static/src/js/plant_overview.js @@ -23,6 +23,7 @@ import { registry } from "@web/core/registry"; import { rpc } from "@web/core/network/rpc"; import { useService } from "@web/core/utils/hooks"; import { QrScanner } from "./qr_scanner"; +import { FpMoveRackDialog } from "./move_rack_dialog"; // ============================================================================= // TimerChip — per-card live elapsed-in-stage chip (v19.0.24.10.0) @@ -132,10 +133,12 @@ export class PlantOverview extends Component { setup() { this.notification = useService("notification"); this.action = useService("action"); + this.dialog = useService("dialog"); this.state = useState({ facilityName: "", columns: [], + racks: [], // Sub 12b — Racks pane payload searchTerm: "", loading: false, lastRefresh: null, @@ -192,6 +195,7 @@ export class PlantOverview extends Component { if (result) { this.state.facilityName = result.facility_name || "Plant 1"; this.state.columns = result.columns || []; + this.state.racks = result.racks || []; this.state.lastRefresh = new Date().toLocaleTimeString(); } } catch (err) { @@ -215,6 +219,23 @@ export class PlantOverview extends Component { this._debouncedSearch(); } + // ===================================================== Sub 12b — racks + + openMoveRackDialog(rackId, toStepId) { + if (!toStepId) { + this.notification.add( + "No destination step available — rack is at the last " + + "step of its job. Use the rack form to manually pick " + + "a new step.", + { type: "warning" }); + return; + } + this.dialog.add(FpMoveRackDialog, { + rackId, toStepId, + onCommit: () => this.loadData(), + }); + } + _debouncedSearch() { if (this._searchTimer) clearTimeout(this._searchTimer); this._searchTimer = setTimeout(() => this.loadData(), 200); diff --git a/fusion_plating/fusion_plating_shopfloor/static/src/scss/move_dialogs.scss b/fusion_plating/fusion_plating_shopfloor/static/src/scss/move_dialogs.scss index c2c8ccdc..166e207b 100644 --- a/fusion_plating/fusion_plating_shopfloor/static/src/scss/move_dialogs.scss +++ b/fusion_plating/fusion_plating_shopfloor/static/src/scss/move_dialogs.scss @@ -129,3 +129,47 @@ $fp-md-page: var(--fp-page-bg, #{$_fp_md_page_hex}); font-size: .875rem; border-left: 4px solid $fp-md-accent; } + +// ===================================================== Plant overview racks pane + +.o_fp_racks_pane { + background: $fp-md-card; + border: 1px solid $fp-md-border; + border-radius: 4px; + padding: .75rem; + margin-bottom: 1rem; + + .o_fp_racks_header { + display: flex; + align-items: center; + gap: .5rem; + margin-bottom: .5rem; + + h3 { + margin: 0; + font-size: 1rem; + color: $fp-md-accent; + } + } + + .o_fp_rack_row { + display: flex; + align-items: center; + gap: .5rem; + padding: .5rem; + border-bottom: 1px solid $fp-md-border; + font-size: .875rem; + + &:last-child { border-bottom: none; } + + .o_fp_rack_name { font-weight: 600; min-width: 6rem; } + .o_fp_rack_count { + color: $fp-md-muted; + min-width: 5rem; + } + .o_fp_rack_breadcrumb { + flex: 1; + color: $fp-md-muted; + } + } +} diff --git a/fusion_plating/fusion_plating_shopfloor/static/src/xml/plant_overview.xml b/fusion_plating/fusion_plating_shopfloor/static/src/xml/plant_overview.xml index baebb93c..2ceb95e7 100644 --- a/fusion_plating/fusion_plating_shopfloor/static/src/xml/plant_overview.xml +++ b/fusion_plating/fusion_plating_shopfloor/static/src/xml/plant_overview.xml @@ -72,6 +72,43 @@

No work centres with active orders found.

+ + +
+
+

Racks

+ + + +
+
+ + + parts + + + + + / + + + + + + +
+
+