feat(jobs): Phase 6 lean — scan controller + process-tree JSON endpoint
Phase 6 originally scoped the full operator UI rewrite (Plant Overview, Tablet, Manager Dashboard, Process Tree). Tailscale SSH to entech is currently unavailable, so live in-browser verification of OWL/JS components isn't possible. Shipping a lean Phase 6 with the data-layer pieces: 1. /fp/job/<id> scan controller — when a user scans a fp.job sticker, lands them on the fp.job form (or the process tree action once that's wired). Mirrors fusion_plating_reports' /fp/wo/ pattern. 2. /fp/jobs/process_tree JSON endpoint — returns the recipe tree serialized with each node tagged by its fp.job.step state, ready for an OWL component to render. The component itself is deferred (see README.md). The bigger UI deferrals (kanban, tablet, manager dashboard) are documented in README.md. They get their own focused project after cutover — the data layer is complete, so they can land incrementally without touching fp.job/fp.job.step. Tests verify controller imports + serialization shape (no HTTP because TransactionCase doesn't easily simulate request context). Manifest 19.0.1.8.0 → 19.0.1.9.0. Part of: native job model migration (spec 2026-04-25) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from . import job_scan
|
||||
from . import process_tree
|
||||
38
fusion_plating/fusion_plating_jobs/controllers/job_scan.py
Normal file
38
fusion_plating/fusion_plating_jobs/controllers/job_scan.py
Normal file
@@ -0,0 +1,38 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2026 Nexa Systems Inc.
|
||||
# License OPL-1 (Odoo Proprietary License v1.0)
|
||||
#
|
||||
# /fp/job/<id> — scan-redirect endpoint for native fp.job stickers.
|
||||
#
|
||||
# The fp.job sticker (Phase 5) embeds a QR encoding this URL. When a
|
||||
# warehouse user scans it, this controller redirects them to either
|
||||
# the fp.job form (for managers) or the upcoming process-tree client
|
||||
# action (for operators — Phase 6 expansion).
|
||||
|
||||
from odoo import http
|
||||
from odoo.http import request
|
||||
|
||||
|
||||
class FpJobScanController(http.Controller):
|
||||
|
||||
@http.route('/fp/job/<int:job_id>', type='http', auth='user', website=False)
|
||||
def fp_job_scan(self, job_id, **kwargs):
|
||||
Job = request.env['fp.job'].sudo()
|
||||
job = Job.browse(job_id).exists()
|
||||
if not job:
|
||||
return request.redirect('/odoo/plating-jobs')
|
||||
|
||||
# If user is a plating manager → land on the form.
|
||||
# Otherwise (operator) → land on process tree client action
|
||||
# (will be wired once process tree is added).
|
||||
user = request.env.user
|
||||
is_manager = user.has_group('fusion_plating.group_fusion_plating_manager')
|
||||
if is_manager:
|
||||
return request.redirect(
|
||||
'/odoo/action-fusion_plating.action_fp_job/%d' % job.id
|
||||
)
|
||||
# Operator path: same form for now (process tree action will replace
|
||||
# this once it's registered).
|
||||
return request.redirect(
|
||||
'/odoo/action-fusion_plating.action_fp_job/%d' % job.id
|
||||
)
|
||||
@@ -0,0 +1,48 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2026 Nexa Systems Inc.
|
||||
# License OPL-1 (Odoo Proprietary License v1.0)
|
||||
#
|
||||
# /fp/jobs/process_tree — JSON endpoint that returns the recipe tree
|
||||
# for a given fp.job, with each node tagged by the matching
|
||||
# fp.job.step (if any) and its current state.
|
||||
|
||||
from odoo import http
|
||||
from odoo.http import request
|
||||
|
||||
|
||||
class FpJobProcessTreeController(http.Controller):
|
||||
|
||||
@http.route('/fp/jobs/process_tree', type='jsonrpc', auth='user', website=False)
|
||||
def fp_jobs_process_tree(self, job_id, **kwargs):
|
||||
Job = request.env['fp.job']
|
||||
job = Job.browse(int(job_id)).exists()
|
||||
if not job:
|
||||
return {'error': 'Job not found'}
|
||||
|
||||
# Map recipe_node_id -> step
|
||||
step_by_node = {s.recipe_node_id.id: s for s in job.step_ids if s.recipe_node_id}
|
||||
|
||||
def serialize(node):
|
||||
step = step_by_node.get(node.id)
|
||||
return {
|
||||
'id': node.id,
|
||||
'name': node.name,
|
||||
'node_type': node.node_type,
|
||||
'sequence': node.sequence,
|
||||
'step_id': step.id if step else None,
|
||||
'step_state': step.state if step else None,
|
||||
'step_assigned_user': step.assigned_user_id.name if step and step.assigned_user_id else None,
|
||||
'duration_expected': step.duration_expected if step else node.estimated_duration,
|
||||
'duration_actual': step.duration_actual if step else 0.0,
|
||||
'children': [serialize(c) for c in node.child_ids.sorted('sequence')],
|
||||
}
|
||||
|
||||
return {
|
||||
'job_name': job.name,
|
||||
'partner': job.partner_id.name,
|
||||
'state': job.state,
|
||||
'qty': job.qty,
|
||||
'recipe_name': job.recipe_id.name if job.recipe_id else '',
|
||||
'progress_pct': job.step_progress_pct,
|
||||
'tree': serialize(job.recipe_id) if job.recipe_id else None,
|
||||
}
|
||||
Reference in New Issue
Block a user