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>
This commit is contained in:
gsinghpal
2026-04-27 21:19:05 -04:00
parent 902f3e8398
commit 11dbbf578e
4 changed files with 147 additions and 0 deletions

View File

@@ -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)
# ------------------------------------------------------------------