From 3a120dd4006095c57fce39d2d2708a40d28a8884 Mon Sep 17 00:00:00 2001 From: gsinghpal Date: Mon, 25 May 2026 09:47:17 -0400 Subject: [PATCH] feat(plant_kanban): post-shop states visible on board (Tasks 9-13) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Controller (plant_kanban.py): - Widen domain: state IN (confirmed, in_progress, awaiting_cert, awaiting_ship). Done jobs still drop off. - _resolve_card_area: state=awaiting_cert → 'inspection' column, state=awaiting_ship → 'shipping' column. State drives column regardless of recipe shape. - _state_chip: 🏷️ Awaiting CoC (amber) + 📦 Ready to ship (green). - _SORT_PRIORITY: awaiting_cert=3.5, awaiting_ship=8.5. - KPI dict: awaiting_cert + awaiting_ship counts. - Filter clauses for the two new chips. Model (fp_job.py): - _compute_card_state handles new states in BOTH branches: the no-active-step early return (where awaiting_cert/ship cards land — all steps terminal) AND the per-step branch (defensive). - _compute_mini_timeline_json: awaiting_cert paints inspection dot 'current'; awaiting_ship paints shipping dot 'current'. All earlier dots show 'done'. SCSS (_plant_tokens.scss + _plant_card.scss): - New tokens for amber (cert) + green (ship), light + dark variants via the existing $o-webclient-color-scheme compile-time branch. - .state-awaiting_cert / .state-awaiting_ship modifier classes match the existing border-left pattern. XML (plant_kanban.xml): - Two new KPI tiles + two new filter chips wired to the state filter clauses. Manifest: fusion_plating_shopfloor 19.0.33.2.0 → 19.0.34.0.0. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../fusion_plating_jobs/models/fp_job.py | 60 ++++++++++++++++++- .../fusion_plating_shopfloor/__manifest__.py | 2 +- .../controllers/plant_kanban.py | 36 ++++++++++- .../static/src/scss/_plant_tokens.scss | 18 ++++++ .../src/scss/components/_plant_card.scss | 11 ++++ .../static/src/xml/plant_kanban.xml | 18 ++++++ 6 files changed, 141 insertions(+), 4 deletions(-) diff --git a/fusion_plating/fusion_plating_jobs/models/fp_job.py b/fusion_plating/fusion_plating_jobs/models/fp_job.py index 5e6f5730..f59d6540 100644 --- a/fusion_plating/fusion_plating_jobs/models/fp_job.py +++ b/fusion_plating/fusion_plating_jobs/models/fp_job.py @@ -287,6 +287,13 @@ class FpJob(models.Model): if not job.active_step_id: if job.state == 'done': job.card_state = 'done' + elif job.state == 'awaiting_cert': + # Spec 2026-05-25 — state drives card_state for + # post-shop jobs (active_step_id is False because + # every step is terminal). + job.card_state = 'awaiting_cert' + elif job.state == 'awaiting_ship': + job.card_state = 'awaiting_ship' elif (job.state == 'confirmed' and job._fp_inbound_not_received()): job.card_state = 'no_parts' @@ -327,6 +334,17 @@ class FpJob(models.Model): and step._fp_is_idle(threshold_hours=8)): job.card_state = 'idle_warning' continue + # Rule 7.5 — awaiting_cert + awaiting_ship (spec 2026-05-25) + # State drives card_state regardless of step state. Inserted + # BEFORE the done rule because state='done' jobs are filtered + # off the board upstream so the done rule is unreachable + # from cards we'd actually render. + if job.state == 'awaiting_cert': + job.card_state = 'awaiting_cert' + continue + if job.state == 'awaiting_ship': + job.card_state = 'awaiting_ship' + continue # Rule 8 — done if step.area_kind == 'shipping' and job.state == 'done': job.card_state = 'done' @@ -356,10 +374,50 @@ class FpJob(models.Model): 'step_ids.area_kind', 'active_step_id', 'card_state', + 'state', ) def _compute_mini_timeline_json(self): - """9-element JSON array, one per Shop Floor column.""" + """9-element JSON array, one per Shop Floor column. + + For awaiting_cert / awaiting_ship (spec 2026-05-25): the + Final-inspection or Shipping dot renders as 'current' with the + state-named variant; all earlier dots render 'done'. Lets the + QM see at a glance "this card has cleared the line, just + waiting on paperwork/shipping". + """ for job in self: + # Post-shop state override (spec 2026-05-25): visually walk + # the card across the two right-most columns even though + # the recipe may not have steps with those area_kinds. + if job.state == 'awaiting_cert': + timeline = [] + for area in _COLUMN_SEQUENCE: + if area == 'inspection': + timeline.append({ + 'area': area, + 'state': 'current', + 'variant': 'awaiting_cert', + }) + elif area == 'shipping': + timeline.append({'area': area, 'state': 'upcoming'}) + else: + timeline.append({'area': area, 'state': 'done'}) + job.mini_timeline_json = json.dumps(timeline) + continue + if job.state == 'awaiting_ship': + timeline = [] + for area in _COLUMN_SEQUENCE: + if area == 'shipping': + timeline.append({ + 'area': area, + 'state': 'current', + 'variant': 'awaiting_ship', + }) + else: + timeline.append({'area': area, 'state': 'done'}) + job.mini_timeline_json = json.dumps(timeline) + continue + # Standard path — pre-existing logic. active_area = (job.active_step_id.area_kind if job.active_step_id else None) timeline = [] diff --git a/fusion_plating/fusion_plating_shopfloor/__manifest__.py b/fusion_plating/fusion_plating_shopfloor/__manifest__.py index 8e0ce0f3..f3e17f1b 100644 --- a/fusion_plating/fusion_plating_shopfloor/__manifest__.py +++ b/fusion_plating/fusion_plating_shopfloor/__manifest__.py @@ -5,7 +5,7 @@ { 'name': 'Fusion Plating — Shop Floor', - 'version': '19.0.33.2.0', + 'version': '19.0.34.0.0', 'category': 'Manufacturing/Plating', 'summary': 'Shop-floor tablet stations, QR scanning, bake window enforcer.', 'description': """ diff --git a/fusion_plating/fusion_plating_shopfloor/controllers/plant_kanban.py b/fusion_plating/fusion_plating_shopfloor/controllers/plant_kanban.py index 52a12863..c1f9b70d 100644 --- a/fusion_plating/fusion_plating_shopfloor/controllers/plant_kanban.py +++ b/fusion_plating/fusion_plating_shopfloor/controllers/plant_kanban.py @@ -38,11 +38,13 @@ _SORT_PRIORITY = { 'no_parts': 1, 'bake_due': 2, 'awaiting_signoff': 3, + 'awaiting_cert': 3.5, # spec 2026-05-25 — after awaiting_signoff 'awaiting_qc': 4, 'ready_mine': 5, 'running_mine': 6, 'ready': 7, 'running': 8, + 'awaiting_ship': 8.5, # spec 2026-05-25 — after running 'idle_warning': 9, 'predecessor_locked': 10, 'contract_review': 11, @@ -65,13 +67,18 @@ class PlantKanbanController(http.Controller): else env['fp.work.centre']) paired_area = paired.area_kind if paired else None - # Base domain — only in-flight jobs. + # Base domain — in-flight jobs. # 2026-05-24 (spec 2026-05-24-shopfloor-live-step-fix-design.md # Defect 4 / Change 3): done + cancelled jobs drop off the live # board. They stay reachable via smart buttons, the Plating Jobs # backend list, and history reports. + # 2026-05-25 (spec post-shop-cert-shipping-job-states): awaiting_cert + # + awaiting_ship are included so completed-but-uncertified / + # ready-to-ship jobs stay visible in the Final inspection / + # Shipping columns. domain = [ - ('state', 'in', ('confirmed', 'in_progress')), + ('state', 'in', ('confirmed', 'in_progress', + 'awaiting_cert', 'awaiting_ship')), ] filters = filters or {} if filters.get('overdue'): @@ -88,6 +95,11 @@ class PlantKanbanController(http.Controller): ))) if filters.get('mine'): domain.append(('card_state', 'in', ('ready_mine', 'running_mine'))) + # Spec 2026-05-25 — post-shop state filter chips + if filters.get('awaiting_cert'): + domain.append(('state', '=', 'awaiting_cert')) + if filters.get('awaiting_ship'): + domain.append(('state', '=', 'awaiting_ship')) if filters.get('fair'): # Match either part-catalog or partner level requires_first_article domain.append('|') @@ -133,6 +145,13 @@ class PlantKanbanController(http.Controller): 'on_hold': sum( 1 for j in jobs if j.card_state == 'on_hold' ), + # Spec 2026-05-25 — post-shop state KPIs + 'awaiting_cert': sum( + 1 for j in jobs if j.state == 'awaiting_cert' + ), + 'awaiting_ship': sum( + 1 for j in jobs if j.state == 'awaiting_ship' + ), 'overdue': sum( 1 for j in jobs if j.date_deadline and j.date_deadline.date() < date.today() @@ -181,6 +200,14 @@ def _resolve_card_area(job): # active step is — the receiver is who acts. if job.card_state == 'no_parts': return 'receiving' + # 2026-05-25 (spec post-shop-cert-shipping-job-states): state drives + # column for the two post-shop states. The recipe may not even have + # a step with inspection / shipping area_kind, but the card belongs + # in those columns once the job has cleared all shop steps. + if job.state == 'awaiting_cert': + return 'inspection' + if job.state == 'awaiting_ship': + return 'shipping' if job.active_step_id and job.active_step_id.area_kind: return job.active_step_id.area_kind # Orphan fallback — represents a data integrity issue, not a @@ -319,6 +346,11 @@ def _state_chip(card_state, step): return {'label': _('📦 Parts in transit'), 'kind': 'no_parts'} if card_state == 'contract_review': return {'label': _('📋 QA-005 review'), 'kind': 'paperwork'} + # Spec 2026-05-25 — post-shop states + if card_state == 'awaiting_cert': + return {'label': _('🏷️ Awaiting CoC'), 'kind': 'awaiting_cert'} + if card_state == 'awaiting_ship': + return {'label': _('📦 Ready to ship'), 'kind': 'awaiting_ship'} if card_state == 'done': return {'label': _('✓ Ready for pickup'), 'kind': 'done'} return {'label': '', 'kind': ''} diff --git a/fusion_plating/fusion_plating_shopfloor/static/src/scss/_plant_tokens.scss b/fusion_plating/fusion_plating_shopfloor/static/src/scss/_plant_tokens.scss index 4f19f42d..b7942e57 100644 --- a/fusion_plating/fusion_plating_shopfloor/static/src/scss/_plant_tokens.scss +++ b/fusion_plating/fusion_plating_shopfloor/static/src/scss/_plant_tokens.scss @@ -34,6 +34,12 @@ $_plant-noparts-border-hex: #6c757d; $_plant-done-bg-hex: #f0f9f4; $_plant-done-border-hex: #28a745; +// Spec 2026-05-25 — post-shop states +$_plant-awaiting-cert-bg-hex: #fff3cd; +$_plant-awaiting-cert-border-hex: #ff9800; +$_plant-awaiting-ship-bg-hex: #d1f1d4; +$_plant-awaiting-ship-border-hex: #2e7d32; + // === Dark-mode overrides (compile-time branch per project rule) === @if $o-webclient-color-scheme == dark { $_plant-bg-hex: #1a1d21 !global; @@ -51,6 +57,12 @@ $_plant-done-border-hex: #28a745; $_plant-locked-bg-hex: #2d3138 !global; $_plant-noparts-bg-hex: #2d3138 !global; $_plant-done-bg-hex: #14281a !global; + + // Spec 2026-05-25 — post-shop states (dark) + $_plant-awaiting-cert-bg-hex: #3a2f15 !global; + $_plant-awaiting-cert-border-hex: #ffb74d !global; + $_plant-awaiting-ship-bg-hex: #1a2d1f !global; + $_plant-awaiting-ship-border-hex: #66bb6a !global; } // === CSS-custom-property wrappers so future themes can override === @@ -78,3 +90,9 @@ $plant-noparts-bg: var(--fp-plant-noparts-bg, $_plant-noparts-bg-hex); $plant-noparts-border: var(--fp-plant-noparts-border, $_plant-noparts-border-hex); $plant-done-bg: var(--fp-plant-done-bg, $_plant-done-bg-hex); $plant-done-border: var(--fp-plant-done-border, $_plant-done-border-hex); + +// Spec 2026-05-25 — post-shop states +$plant-awaiting-cert-bg: var(--fp-plant-awaiting-cert-bg, $_plant-awaiting-cert-bg-hex); +$plant-awaiting-cert-border: var(--fp-plant-awaiting-cert-border, $_plant-awaiting-cert-border-hex); +$plant-awaiting-ship-bg: var(--fp-plant-awaiting-ship-bg, $_plant-awaiting-ship-bg-hex); +$plant-awaiting-ship-border: var(--fp-plant-awaiting-ship-border, $_plant-awaiting-ship-border-hex); diff --git a/fusion_plating/fusion_plating_shopfloor/static/src/scss/components/_plant_card.scss b/fusion_plating/fusion_plating_shopfloor/static/src/scss/components/_plant_card.scss index 4f8cc585..f6fe3c1d 100644 --- a/fusion_plating/fusion_plating_shopfloor/static/src/scss/components/_plant_card.scss +++ b/fusion_plating/fusion_plating_shopfloor/static/src/scss/components/_plant_card.scss @@ -66,6 +66,17 @@ border-left: 4px solid $plant-done-border; padding-left: 7px; } + // Spec 2026-05-25 — post-shop states + &.state-awaiting_cert { + background: $plant-awaiting-cert-bg; + border-left: 4px solid $plant-awaiting-cert-border; + padding-left: 7px; + } + &.state-awaiting_ship { + background: $plant-awaiting-ship-bg; + border-left: 4px solid $plant-awaiting-ship-border; + padding-left: 7px; + } &.overdue:not(.mine):not(.state-on_hold):not(.state-bake_due) { border-left: 4px solid $plant-hold-border; padding-left: 7px; diff --git a/fusion_plating/fusion_plating_shopfloor/static/src/xml/plant_kanban.xml b/fusion_plating/fusion_plating_shopfloor/static/src/xml/plant_kanban.xml index e279ec32..501f67ee 100644 --- a/fusion_plating/fusion_plating_shopfloor/static/src/xml/plant_kanban.xml +++ b/fusion_plating/fusion_plating_shopfloor/static/src/xml/plant_kanban.xml @@ -47,6 +47,17 @@ kind="'urgent'" active="!!state.filters.on_hold" onClick="() => this.toggleFilter('on_hold')"/> + + + + + +