fix(manager-desk): include 'blocked' WOs + populate empty columns
Two complementary fixes — a real bug in the Manager Desk and demo
data that exercises the now-correct view.
The bug
=======
manager_controller.py used an explicit allow-list of WO states for
its Unassigned / Active columns and for the per-operator team load
count: ('pending','waiting','ready','progress'). That set MISSED the
'blocked' state Odoo emits when a WO's predecessor isn't done yet.
Result: an MO whose first WO is still running has all its downstream
WOs in 'blocked' state. They literally don't appear on the Manager
Desk — neither in "Needs a Worker" (even when unassigned) nor in
"In Progress" (even when assigned). The team load count also
under-reports because the operator's blocked queue is invisible.
Fix: switch all three domains from an allow-list to a deny-list
('done','cancel'). Same shape Plant Overview already uses, so the
two dashboards now agree on what "active" means.
Demo data
=========
Stage-filler gains two steps so the now-corrected view has obvious
data:
6e. _populate_active_wos walks the in-flight MO's blocked routing
and explicitly assigns the seven downstream WOs in sequence
order — Diego (training), Carlos (plating), James (demask),
Priya (oven), TWO unassigned (de-rack + post-bake — feed
"Needs a Worker"), Aisha (final inspection). Earlier
keyword-fuzzy matching missed WOs whose names didn't carry
the expected substring.
6f. _mark_so_awaiting_manager pushes two confirmed SOs to
receiving_status='inspected' + assigned_manager_id=False so
the "Awaiting Assignment" KPI is non-zero.
Verified on entech: 2 unassigned WOs, 6 active+assigned, 2
awaiting-assignment SOs. Six of seven operators carry at least one
open queue item; Marie has zero current load but a healthy past
completion history (she's on shift, between jobs).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -59,8 +59,14 @@ class FpManagerDashboardController(http.Controller):
|
||||
has_assign = 'x_fc_assigned_user_id' in MrpWO._fields
|
||||
|
||||
# ---- Column 1: Unassigned (no worker on an active WO) ----------
|
||||
# 'not in (done, cancel)' rather than an explicit allow-list so
|
||||
# we catch every active state Odoo emits — including 'blocked'
|
||||
# (predecessor not done yet). The previous allow-list missed
|
||||
# 'blocked' and left the column empty for entire MO routings
|
||||
# whose first WO was still running.
|
||||
ACTIVE_NEG_STATES = ('done', 'cancel')
|
||||
domain_unassigned = [
|
||||
('state', 'in', ('pending', 'waiting', 'ready', 'progress')),
|
||||
('state', 'not in', ACTIVE_NEG_STATES),
|
||||
]
|
||||
if has_assign:
|
||||
domain_unassigned.append(('x_fc_assigned_user_id', '=', False))
|
||||
@@ -126,8 +132,12 @@ class FpManagerDashboardController(http.Controller):
|
||||
unassigned_cards.append(_mo_card(mo, wos))
|
||||
|
||||
# ---- Column 2: In Progress (MOs with at least one active WO) ----
|
||||
# Same widening as the unassigned domain — capture every active
|
||||
# state. Without 'blocked' in the set, an MO whose only running
|
||||
# WO is currently blocked-waiting-on-predecessor disappears from
|
||||
# the column even though the assigned worker is still on point.
|
||||
domain_active = [
|
||||
('state', 'in', ('ready', 'progress')),
|
||||
('state', 'not in', ACTIVE_NEG_STATES),
|
||||
]
|
||||
if has_assign:
|
||||
domain_active.append(('x_fc_assigned_user_id', '!=', False))
|
||||
@@ -149,7 +159,7 @@ class FpManagerDashboardController(http.Controller):
|
||||
for user in operator_group.user_ids.sorted('name'):
|
||||
open_wos = MrpWO.search([
|
||||
('x_fc_assigned_user_id', '=', user.id),
|
||||
('state', 'in', ('ready', 'progress', 'waiting')),
|
||||
('state', 'not in', ACTIVE_NEG_STATES),
|
||||
])
|
||||
team.append({
|
||||
'user_id': user.id,
|
||||
|
||||
@@ -451,6 +451,93 @@ def _add_paused_wo(env):
|
||||
print(f"[6b] Paused-WO marker set on {progress.display_name}")
|
||||
|
||||
|
||||
def _populate_active_wos(env):
|
||||
"""Make sure the Manager Desk's three columns all have visible data.
|
||||
|
||||
Walks the in-flight MO's routing in sequence order and explicitly
|
||||
assigns each downstream WO to a specific operator (with two
|
||||
deliberately left unassigned so the "Needs a Worker" column has
|
||||
cards to pick from). Earlier keyword-based fuzzy matching missed
|
||||
a few WOs whose names didn't contain the expected substring, so
|
||||
this rewrite uses a positional plan instead — less clever, more
|
||||
predictable.
|
||||
|
||||
Manager Desk's WO domain was widened to include 'blocked' state in
|
||||
the same patch, so WOs sitting waiting on a predecessor finally
|
||||
show up.
|
||||
"""
|
||||
Emp = env['hr.employee']
|
||||
mo = env['mrp.production'].search([('state', '=', 'progress')], limit=1)
|
||||
if not mo:
|
||||
print("[6e] No in-progress MO available — skipping")
|
||||
return
|
||||
|
||||
# Sequence-aligned plan: the Nth downstream WO (skipping done +
|
||||
# progress) goes to the Nth operator. None means leave unassigned.
|
||||
PLAN = [
|
||||
'Diego Ramirez', # the training operator gets the first prep step
|
||||
'Carlos Silva', # senior owns the critical plating step
|
||||
"James O'Connor", # lead hand for plating_op also covers demask
|
||||
'Priya Sharma', # lead hand for oven
|
||||
None, # de-rack — left empty for "Needs a Worker"
|
||||
None, # post-bake — left empty for "Needs a Worker"
|
||||
'Aisha Khan', # final inspection
|
||||
]
|
||||
|
||||
# Skip done / cancel / progress (the latter is the live one we
|
||||
# don't want to disturb mid-flight).
|
||||
pending = mo.workorder_ids.sorted('sequence').filtered(
|
||||
lambda w: w.state not in ('done', 'cancel', 'progress')
|
||||
)
|
||||
moved = cleared = 0
|
||||
for wo, name in zip(pending, PLAN):
|
||||
if name is None:
|
||||
wo.x_fc_assigned_user_id = False
|
||||
cleared += 1
|
||||
continue
|
||||
emp = Emp.search([('name', '=', name)], limit=1)
|
||||
if emp and emp.user_id:
|
||||
wo.x_fc_assigned_user_id = emp.user_id.id
|
||||
moved += 1
|
||||
print(f"[6e] Active WOs: redistributed {moved} to new team, "
|
||||
f"left {cleared} unassigned")
|
||||
|
||||
|
||||
def _mark_so_awaiting_manager(env):
|
||||
"""Push two confirmed SOs to "inspected, no manager assigned" so the
|
||||
Manager Desk's "Awaiting Assignment" KPI has a non-zero value.
|
||||
|
||||
The KPI's domain on the controller side is:
|
||||
state == 'sale'
|
||||
x_fc_receiving_status == 'inspected'
|
||||
x_fc_assigned_manager_id is False
|
||||
Set those three on a couple of existing confirmed SOs.
|
||||
"""
|
||||
SO = env['sale.order']
|
||||
if not ('x_fc_receiving_status' in SO._fields
|
||||
and 'x_fc_assigned_manager_id' in SO._fields):
|
||||
print("[6f] receiving_status/assigned_manager fields not present")
|
||||
return
|
||||
already = SO.search_count([
|
||||
('state', '=', 'sale'),
|
||||
('x_fc_receiving_status', '=', 'inspected'),
|
||||
('x_fc_assigned_manager_id', '=', False),
|
||||
])
|
||||
if already >= 2:
|
||||
print(f"[6f] Awaiting-assignment SOs already populated ({already})")
|
||||
return
|
||||
sos = SO.search([
|
||||
('state', '=', 'sale'),
|
||||
('x_fc_receiving_status', '!=', 'inspected'),
|
||||
], limit=2)
|
||||
for so in sos:
|
||||
so.write({
|
||||
'x_fc_receiving_status': 'inspected',
|
||||
'x_fc_assigned_manager_id': False,
|
||||
})
|
||||
print(f"[6f] Marked {len(sos)} SOs as awaiting-manager-assignment")
|
||||
|
||||
|
||||
def _mark_quote_sent(env):
|
||||
"""Bump one draft SO into the 'sent' state so the funnel has data
|
||||
in every workflow column.
|
||||
@@ -541,6 +628,8 @@ _safe('6a. add quality holds', _add_quality_holds)
|
||||
_safe('6b. mark paused WO', _add_paused_wo)
|
||||
_safe('6c. add quote requests', _add_quote_requests)
|
||||
_safe('6d. mark one quote sent', _mark_quote_sent)
|
||||
_safe('6e. populate active WOs', _populate_active_wos)
|
||||
_safe('6f. SO awaiting-manager', _mark_so_awaiting_manager)
|
||||
print("=========================================================")
|
||||
print("Done. Re-run anytime — script is idempotent.")
|
||||
print("=========================================================\n")
|
||||
|
||||
Reference in New Issue
Block a user