feat(plating): Phase 1 — plant-view kanban data model foundation

PV-T1: fp.work.centre.area_kind Selection (9 floor columns)
PV-T2: fp.job.step.area_kind compute + _STEP_KIND_TO_AREA fallback
       (covers all 30+ step kinds in the project library, plus the
       spec D4 rule that de_mask folds into de_racking)
PV-T3: fp.job.step.last_activity_at + write hook + message_post
       override + fp.job.step.move.create() hook + _fp_is_idle helper
PV-T4: res.users.paired_work_centre_ids M2M (single-station for MVP,
       forward-compatible for Phase 2 multi-station picker)
PV-T5: res.config.settings.x_fc_shopfloor_layout feature flag backed
       by ir.config_parameter for the landing-action resolver

Migrations:
  fusion_plating 19.0.21.0.0      — backfill area_kind from kind
  fusion_plating_jobs 19.0.10.24.0 — backfill last_activity_at

Deployed + verified on entech:
  - 9/9 fp.work.centre rows have area_kind set
  - 400/400 fp.job.step rows have area_kind + last_activity_at
  - paired_work_centre_ids M2M relation table created
  - All 271 modules loaded cleanly, registry rebuilt in 27s

Part of the 2026-05-23 Shop Floor plant-view kanban redesign.
Plan: docs/superpowers/plans/2026-05-23-shopfloor-plant-view-plan.md
Spec: docs/superpowers/specs/2026-05-23-shopfloor-plant-view-design.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-05-23 20:43:15 -04:00
parent 1a3ca8704e
commit 63d692b322
11 changed files with 302 additions and 3 deletions

View File

@@ -5,7 +5,7 @@
{
'name': 'Fusion Plating',
'version': '19.0.20.10.0',
'version': '19.0.21.0.0',
'category': 'Manufacturing/Plating',
'summary': 'Core plating / metal finishing ERP: facilities, processes, tanks, baths, jobs, operators.',
'description': """

View File

@@ -0,0 +1,52 @@
# -*- coding: utf-8 -*-
# Copyright 2026 Nexa Systems Inc.
# License OPL-1 (Odoo Proprietary License v1.0)
#
# 19.0.21.0.0 — Plant-view Shop Floor kanban redesign.
# Backfill fp.work.centre.area_kind from the existing `kind` taxonomy so
# every routing station has a defined Floor Column on day 1. Admins can
# override afterwards via Configuration → Shop Setup → Routing Stations.
import logging
_logger = logging.getLogger(__name__)
def migrate(cr, version):
"""Backfill area_kind on existing fp.work.centre rows.
Mapping is intentionally permissive: every existing kind maps to a
sensible default. Unmapped (e.g. 'other') falls to 'plating' as the
safe wet-shop catch-all and is logged for review.
"""
cr.execute("""
UPDATE fp_work_centre
SET area_kind = CASE kind
WHEN 'wet_line' THEN 'plating'
WHEN 'bake' THEN 'baking'
WHEN 'mask' THEN 'masking'
WHEN 'rack' THEN 'racking'
WHEN 'inspect' THEN 'inspection'
ELSE 'plating'
END
WHERE area_kind IS NULL
""")
# Log any rows that landed on the catch-all so the admin can review.
cr.execute("""
SELECT id, name, code, kind
FROM fp_work_centre
WHERE area_kind = 'plating'
AND kind = 'other'
""")
rows = cr.fetchall()
if rows:
_logger.warning(
"%d fp.work.centre rows had kind='other' and were defaulted "
"to area_kind='plating'; review and adjust if needed: %s",
len(rows),
', '.join(
'%s (id=%s, code=%s)' % (r[1], r[0], r[2])
for r in rows[:10]
),
)
_logger.info("Backfilled area_kind on fp.work.centre")

View File

@@ -77,6 +77,27 @@ class FpJobStepMove(models.Model):
string='Transition Input Values',
)
@api.model_create_multi
def create(self, vals_list):
"""Stamp last_activity_at on from_step + to_step so the plant-view
idle gate (S16) sees moves as activity. Without this, a step that
only ever gets moves (no chatter, no state edits) eventually
trips the 8-hour idle warning falsely.
"""
moves = super().create(vals_list)
Step = self.env['fp.job.step']
step_ids = set()
for m in moves:
if m.from_step_id:
step_ids.add(m.from_step_id.id)
if m.to_step_id:
step_ids.add(m.to_step_id.id)
if step_ids:
Step.browse(list(step_ids)).sudo().with_context(
tracking_disable=True,
).write({'last_activity_at': fields.Datetime.now()})
return moves
# ------------------------------------------------------------------
# S23 — required transition-input gate
# ------------------------------------------------------------------

View File

@@ -48,6 +48,26 @@ class FpWorkCentre(models.Model):
required=True,
default='other',
)
area_kind = fields.Selection(
[
('receiving', 'Receiving'),
('masking', 'Masking'),
('blasting', 'Blasting'),
('racking', 'Racking'),
('plating', 'Plating'),
('baking', 'Baking'),
('de_racking', 'De-Racking'),
('inspection', 'Final inspection'),
('shipping', 'Shipping'),
],
string='Floor Column',
help='Which Shop Floor column this work centre belongs to. '
'Drives the plant-view kanban grouping — any job whose '
'active step uses this work centre routes into this column. '
'See docs/superpowers/specs/2026-05-23-shopfloor-plant-view-'
'design.md §4.2 for the mapping rules.',
index=True,
)
cost_per_hour = fields.Monetary(
currency_field='currency_id',
help='Used for fp.job.step cost rollups.',