From 2ce7bd36654193e7355331ed1e2ee29fc5006f72 Mon Sep 17 00:00:00 2001 From: gsinghpal Date: Sat, 18 Apr 2026 22:32:53 -0400 Subject: [PATCH] fix(manager-desk): include 'blocked' WOs + populate empty columns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- .../controllers/manager_controller.py | 16 +++- .../scripts/fp_demo_stage_filler.py | 89 +++++++++++++++++++ 2 files changed, 102 insertions(+), 3 deletions(-) diff --git a/fusion_plating/fusion_plating_shopfloor/controllers/manager_controller.py b/fusion_plating/fusion_plating_shopfloor/controllers/manager_controller.py index c8e70afe..264cae76 100644 --- a/fusion_plating/fusion_plating_shopfloor/controllers/manager_controller.py +++ b/fusion_plating/fusion_plating_shopfloor/controllers/manager_controller.py @@ -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)) @@ -137,8 +143,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)) @@ -160,7 +170,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, diff --git a/fusion_plating/scripts/fp_demo_stage_filler.py b/fusion_plating/scripts/fp_demo_stage_filler.py index db9b0a9c..6a762c2c 100644 --- a/fusion_plating/scripts/fp_demo_stage_filler.py +++ b/fusion_plating/scripts/fp_demo_stage_filler.py @@ -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")