Two operator-facing client actions for the native job model. Plant Overview: kanban with columns = fp.work.centre, cards = active fp.job.step rows (ready/in_progress/paused). Drag a card to a different column to reassign the step's work_centre_id; click to open the step form. Backend: /fp/jobs/plant_overview returns columns with cards; /fp/jobs/plant_overview/move_card reassigns work_centre. Manager Dashboard: list of in-flight fp.job rows with progress bars, deadline (overdue highlight), current_step / current_location, and a priority side-bar (rush=red, high=orange, normal=blue, low=grey). Click a row to open the job form. State-count pills filter by state. Backend: /fp/jobs/manager_dashboard returns rows + state counts. Both menu entries land inside the existing 'Plating Jobs (Native)' submenu under the Plating app (manager-only). The menu items are defined in this module rather than in fusion_plating core, because the action xmlids they reference aren't loaded yet at the time the core menu file is parsed (fusion_plating_jobs depends on core, not the other way round). Manifest 19.0.2.2.0 → 19.0.2.3.0. Three new SCSS, three new JS, three new XML files registered in web.assets_backend. Verified on entech: module loaded clean, all 41 fusion_plating_jobs tests pass, asset bundle regenerates without errors, both menus and both client actions registered in ir_ui_menu / ir_act_client. Part of: native job model migration (spec 2026-04-25) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
99 lines
3.8 KiB
Python
99 lines
3.8 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Copyright 2026 Nexa Systems Inc.
|
|
# License OPL-1 (Odoo Proprietary License v1.0)
|
|
#
|
|
# /fp/jobs/plant_overview — JSON endpoints powering the native-job
|
|
# Plant Overview kanban (operator triage view, Phase 6 of the native
|
|
# job migration). Columns are fp.work.centre rows; cards are
|
|
# fp.job.step rows in ready / in_progress / paused state. Drag a
|
|
# card across columns to reassign that step's work_centre_id.
|
|
|
|
from odoo import http
|
|
from odoo.http import request
|
|
|
|
|
|
class FpJobsPlantOverviewController(http.Controller):
|
|
|
|
@http.route('/fp/jobs/plant_overview', type='jsonrpc', auth='user', website=False)
|
|
def fp_jobs_plant_overview(self, facility_id=None, **kwargs):
|
|
env = request.env
|
|
WorkCentre = env['fp.work.centre']
|
|
Step = env['fp.job.step']
|
|
|
|
wc_domain = [('active', '=', True)]
|
|
if facility_id:
|
|
wc_domain.append(('facility_id', '=', int(facility_id)))
|
|
centres = WorkCentre.search(wc_domain, order='sequence, code, name')
|
|
|
|
# Active steps grouped by work_centre. We pull paused too so a
|
|
# manager can see — and re-route — a step that's been paused
|
|
# on the wrong line.
|
|
step_domain = [('state', 'in', ('ready', 'in_progress', 'paused'))]
|
|
if facility_id:
|
|
step_domain.append(('facility_id', '=', int(facility_id)))
|
|
active_steps = Step.search(step_domain, order='job_id, sequence')
|
|
|
|
cards_by_wc = {}
|
|
for step in active_steps:
|
|
wc_id = step.work_centre_id.id or 0
|
|
cards_by_wc.setdefault(wc_id, []).append({
|
|
'id': step.id,
|
|
'name': step.name,
|
|
'state': step.state,
|
|
'job_id': step.job_id.id,
|
|
'job_name': step.job_id.name,
|
|
'partner': step.job_id.partner_id.name or '',
|
|
'sequence': step.sequence,
|
|
'kind': step.kind,
|
|
'duration_expected': step.duration_expected,
|
|
'duration_actual': step.duration_actual,
|
|
'assigned_user': (
|
|
step.assigned_user_id.name
|
|
if step.assigned_user_id else None
|
|
),
|
|
'thickness_target': step.thickness_target,
|
|
'thickness_uom': step.thickness_uom,
|
|
'priority': step.job_id.priority,
|
|
})
|
|
|
|
columns = []
|
|
for wc in centres:
|
|
columns.append({
|
|
'id': wc.id,
|
|
'code': wc.code,
|
|
'name': wc.name,
|
|
'kind': wc.kind,
|
|
'facility': wc.facility_id.name if wc.facility_id else None,
|
|
'cards': cards_by_wc.get(wc.id, []),
|
|
})
|
|
# An "Unassigned" pseudo-column for steps without a work centre —
|
|
# only rendered when there's something to show, so empty plants
|
|
# don't pick up a stray column.
|
|
if cards_by_wc.get(0):
|
|
columns.append({
|
|
'id': 0,
|
|
'code': '—',
|
|
'name': 'Unassigned',
|
|
'kind': 'other',
|
|
'facility': None,
|
|
'cards': cards_by_wc[0],
|
|
})
|
|
|
|
return {'columns': columns}
|
|
|
|
@http.route('/fp/jobs/plant_overview/move_card', type='jsonrpc', auth='user', website=False)
|
|
def fp_jobs_move_card(self, step_id, work_centre_id, **kwargs):
|
|
"""Reassign a step to a different work centre.
|
|
|
|
work_centre_id == 0 (or falsy) clears the work centre — the card
|
|
will land in the Unassigned pseudo-column on the next refresh.
|
|
"""
|
|
env = request.env
|
|
Step = env['fp.job.step']
|
|
step = Step.browse(int(step_id)).exists()
|
|
if not step:
|
|
return {'ok': False, 'error': 'Step not found'}
|
|
wc_id = int(work_centre_id) if work_centre_id else False
|
|
step.work_centre_id = wc_id
|
|
return {'ok': True}
|