# -*- 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}