feat(fusion_plating_shopfloor): mobile responsiveness, boxes stepper, racking panel
- Plant Kanban + Job Workspace made phone-responsive: height:100% + single internal scroll (was 100vh, broke mobile scroll), compact header/workflow bar, receiving part-line stacking so fields don't overflow, responsive lock-screen tile grid. - +/- stepper on the receiving "Boxes received" field. - Multi-rack Racking panel (Phase 1): split a WO's parts across racks (+Add Rack / Divide Equally / manual qty + Unassigned counter) on the Job Workspace, shown only when the WO is at the Racking step (area_kind based, excludes De-Racking). New /fp/racking/* controller. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -10,3 +10,4 @@ from . import workspace_controller
|
||||
from . import landing_controller
|
||||
from . import tablet_controller
|
||||
from . import plant_kanban
|
||||
from . import racking_controller
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2026 Nexa Systems Inc.
|
||||
# License OPL-1 (Odoo Proprietary License v1.0)
|
||||
#
|
||||
# Multi-rack splitting at Racking — Phase 1 controller. Endpoints run as the
|
||||
# technician (request.env.user); the rack-load + division logic lives on
|
||||
# fp.rack.load (core + fusion_plating_jobs extension).
|
||||
|
||||
from odoo import http
|
||||
from odoo.http import request
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class FpRackingController(http.Controller):
|
||||
|
||||
def _job(self, job_id):
|
||||
return request.env['fp.job'].browse(int(job_id))
|
||||
|
||||
def _payload(self, job):
|
||||
Load = request.env['fp.rack.load']
|
||||
loads = Load._fp_job_loads(job)
|
||||
total = Load._fp_racking_total(job)
|
||||
return {
|
||||
'ok': True,
|
||||
'job_id': job.id,
|
||||
'wo_name': job.display_wo_name,
|
||||
'total': total,
|
||||
'unassigned': max(total - sum(loads.mapped('qty_total')), 0),
|
||||
'loads': [{
|
||||
'id': load.id,
|
||||
'name': load.name,
|
||||
'rack_id': load.rack_id.id or False,
|
||||
'rack_name': load.rack_id.name or '',
|
||||
'rack_capacity': load.rack_id.capacity or 0,
|
||||
'qty': load.qty_total,
|
||||
'over_capacity': bool(
|
||||
load.rack_id and load.rack_id.capacity
|
||||
and load.qty_total > load.rack_id.capacity),
|
||||
'moved': bool(load.current_step_id),
|
||||
} for load in loads],
|
||||
}
|
||||
|
||||
@http.route('/fp/racking/load', type='jsonrpc', auth='user')
|
||||
def load(self, job_id):
|
||||
job = self._job(job_id)
|
||||
request.env['fp.rack.load']._fp_ensure_seeded(job)
|
||||
return self._payload(job)
|
||||
|
||||
@http.route('/fp/racking/add_rack', type='jsonrpc', auth='user')
|
||||
def add_rack(self, job_id):
|
||||
job = self._job(job_id)
|
||||
try:
|
||||
request.env['fp.rack.load']._fp_add_rack(job)
|
||||
except UserError as e:
|
||||
return {'ok': False, 'error': str(e.args[0])}
|
||||
return self._payload(job)
|
||||
|
||||
@http.route('/fp/racking/divide_equally', type='jsonrpc', auth='user')
|
||||
def divide_equally(self, job_id):
|
||||
job = self._job(job_id)
|
||||
request.env['fp.rack.load']._fp_divide_equally(job)
|
||||
return self._payload(job)
|
||||
|
||||
@http.route('/fp/racking/set_qty', type='jsonrpc', auth='user')
|
||||
def set_qty(self, load_id, qty):
|
||||
load = request.env['fp.rack.load'].browse(int(load_id))
|
||||
job = load.line_ids[:1].job_id
|
||||
try:
|
||||
load._fp_set_qty(qty)
|
||||
except UserError as e:
|
||||
return {'ok': False, 'error': str(e.args[0])}
|
||||
return self._payload(job)
|
||||
|
||||
@http.route('/fp/racking/remove_rack', type='jsonrpc', auth='user')
|
||||
def remove_rack(self, load_id):
|
||||
load = request.env['fp.rack.load'].browse(int(load_id))
|
||||
job = load.line_ids[:1].job_id
|
||||
try:
|
||||
load._fp_remove_rack()
|
||||
except UserError as e:
|
||||
return {'ok': False, 'error': str(e.args[0])}
|
||||
return self._payload(job)
|
||||
|
||||
@http.route('/fp/racking/assign_rack', type='jsonrpc', auth='user')
|
||||
def assign_rack(self, load_id, rack_id):
|
||||
load = request.env['fp.rack.load'].browse(int(load_id))
|
||||
rack = request.env['fusion.plating.rack'].browse(int(rack_id))
|
||||
load.rack_id = rack.id
|
||||
if 'racking_state' in rack._fields:
|
||||
rack.racking_state = 'loaded'
|
||||
return self._payload(load.line_ids[:1].job_id)
|
||||
@@ -283,6 +283,14 @@ class FpWorkspaceController(http.Controller):
|
||||
'is_manager': env.user.has_group(
|
||||
'fusion_plating.group_fusion_plating_manager',
|
||||
),
|
||||
# Racking panel (multi-rack split) shows when the WO is at the
|
||||
# racking step and it's the current actionable work. Detect by
|
||||
# area_kind == 'racking' (corrected classification) — NOT
|
||||
# _fp_is_racking_step(), which would also match mis-tagged
|
||||
# De-Racking steps (kind='racking' in the data).
|
||||
'is_at_racking': bool(job.step_ids.filtered(
|
||||
lambda s: s.area_kind == 'racking'
|
||||
and s.state in ('ready', 'in_progress', 'paused'))),
|
||||
}
|
||||
|
||||
# ======================================================================
|
||||
|
||||
Reference in New Issue
Block a user