diff --git a/fusion_plating/fusion_plating/__manifest__.py b/fusion_plating/fusion_plating/__manifest__.py
index fac84e44..6eff726f 100644
--- a/fusion_plating/fusion_plating/__manifest__.py
+++ b/fusion_plating/fusion_plating/__manifest__.py
@@ -5,7 +5,7 @@
{
'name': 'Fusion Plating',
- 'version': '19.0.21.1.3',
+ 'version': '19.0.21.2.0',
'category': 'Manufacturing/Plating',
'summary': 'Core plating / metal finishing ERP: facilities, processes, tanks, baths, jobs, operators.',
'description': """
diff --git a/fusion_plating/fusion_plating/controllers/simple_recipe_controller.py b/fusion_plating/fusion_plating/controllers/simple_recipe_controller.py
index 70fee115..e929d41d 100644
--- a/fusion_plating/fusion_plating/controllers/simple_recipe_controller.py
+++ b/fusion_plating/fusion_plating/controllers/simple_recipe_controller.py
@@ -484,8 +484,15 @@ class SimpleRecipeController(http.Controller):
type='jsonrpc', auth='user')
def kinds_list(self):
"""Sub 14b — Step Kind dropdown options for the inline library
- form. User-extensible via /fp/simple_recipe/kinds/create."""
+ form. User-extensible via /fp/simple_recipe/kinds/create.
+
+ 2026-05-24 — payload now includes `area_kind` + a humanized
+ `area_kind_label` so the Simple Editor picker can render
+ "Masking — Masking column" and authors see which Shop Floor
+ column they're routing the step to.
+ """
Kind = request.env['fp.step.kind']
+ area_labels = dict(Kind._fields['area_kind'].selection)
return {
'kinds': [
{
@@ -494,6 +501,8 @@ class SimpleRecipeController(http.Controller):
'name': k.name or '',
'icon': k.icon or '',
'sequence': k.sequence,
+ 'area_kind': k.area_kind or '',
+ 'area_kind_label': area_labels.get(k.area_kind, ''),
}
for k in Kind.search(
[('active', '=', True)], order='sequence, name',
diff --git a/fusion_plating/fusion_plating/data/fp_step_kind_data.xml b/fusion_plating/fusion_plating/data/fp_step_kind_data.xml
index 1203e68b..b09b9c77 100644
--- a/fusion_plating/fusion_plating/data/fp_step_kind_data.xml
+++ b/fusion_plating/fusion_plating/data/fp_step_kind_data.xml
@@ -18,7 +18,15 @@
(covers all bath-based steps).
- `mask` covers Masking + De-Masking, `racking` covers
Racking + De-Racking — operators differentiate by the
- step name. -->
+ step name.
+
+ 2026-05-24 update (19.0.21.2.0 — Shop Floor live-step fix):
+ - New `area_kind` field on fp.step.kind drives plant-view
+ column routing. Every record below carries an
+ area_kind. New `blast` kind for the Blasting column.
+ - `derack`, `demask`, `gating` get re-activated via the
+ pre-migrate (they're listed under "ACTIVE KINDS" here
+ now since they're meant to be active going forward). -->
@@ -29,13 +37,7 @@
Other
5
fa-circle-o
-
-
-
- wet_process
- Wet Process (Clean / Rinse / Etch / Dry / etc.)
- 55
- fa-tint
+ plating
@@ -43,144 +45,182 @@
Receiving / Incoming Inspection
10
fa-truck
+ receiving
contract_review
Contract Review (QA-005)
20
fa-file-text-o
+ receiving
racking
Racking
30
fa-server
+ racking
+
+
+ blast
+ Blasting / Media Blast
+ 35
+ fa-bullseye
+ blasting
mask
Masking
40
fa-eye-slash
+ masking
cleaning
Cleaning
50
fa-tint
+ plating
+
+
+ wet_process
+ Wet Process (Clean / Rinse / Etch / Dry / etc.)
+ 55
+ fa-tint
+ plating
electroclean
Electroclean
60
fa-bolt
+ plating
etch
Etch / Activation
70
fa-flask
+ plating
rinse
Rinse
80
fa-tint
+ plating
strike
Strike (Wood's Nickel / Activation)
90
fa-bolt
+ plating
plate
Plating
100
fa-shield
+ plating
replenishment
Tank Replenishment
110
fa-plus-circle
+ plating
wbf_test
Water Break Free Test
120
fa-check-square-o
+ plating
dry
Drying
130
fa-sun-o
+ plating
bake
Bake (HE Relief / Stress Relief)
140
fa-fire
+ baking
demask
De-Masking
150
fa-eye
+ de_racking
derack
De-Racking
160
fa-server
+ de_racking
inspect
Inspection
170
fa-search
+ inspection
hardness_test
Hardness Test (HV / HK / HRC)
180
fa-tachometer
+ inspection
adhesion_test
Adhesion Test
190
fa-link
+ inspection
salt_spray
Salt Spray / Corrosion Test
200
fa-cloud
+ inspection
final_inspect
Final Inspection
210
fa-check-circle
+ inspection
packaging
Packaging / Pre-Ship
220
fa-archive
+ shipping
ship
Shipping
230
fa-paper-plane
+ shipping
gating
Gating
240
fa-pause-circle
+ receiving
+
+
diff --git a/fusion_plating/fusion_plating/data/fp_step_template_data.xml b/fusion_plating/fusion_plating/data/fp_step_template_data.xml
index 5dc76a46..40e5f047 100644
--- a/fusion_plating/fusion_plating/data/fp_step_template_data.xml
+++ b/fusion_plating/fusion_plating/data/fp_step_template_data.xml
@@ -103,4 +103,30 @@
]]>
+
+
+
+ Hot Water Porosity Test (A-15)
+ HWP_A15
+
+ fa-tint
+ Hot-water porosity test for plated samples. Verify continuity
+ of the deposit across the test panel; record any porosity sites
+ and attach a photo when a defect is found.
+ ]]>
+
+
+
+ Final Inspection / Packaging
+ FINAL_PKG_STD
+
+ fa-check-circle
+ Combined final visual + dimensional inspection followed by
+ packaging into the customer's original boxes for shipment.
+ Verify part count, attach certs, photo the sealed load.
+ ]]>
+
+
diff --git a/fusion_plating/fusion_plating/migrations/19.0.21.2.0/pre-migrate.py b/fusion_plating/fusion_plating/migrations/19.0.21.2.0/pre-migrate.py
new file mode 100644
index 00000000..8b7feb94
--- /dev/null
+++ b/fusion_plating/fusion_plating/migrations/19.0.21.2.0/pre-migrate.py
@@ -0,0 +1,100 @@
+# -*- coding: utf-8 -*-
+# Copyright 2026 Nexa Systems Inc.
+# License OPL-1 (Odoo Proprietary License v1.0)
+"""19.0.21.2.0 — Shop Floor live-step + kind taxonomy.
+
+Seeds fp.step.kind.area_kind on existing kinds BEFORE the required
+NOT NULL constraint on the new field hits the schema. Also activates
+the three kinds (derack/demask/gating) that were deactivated in
+19.0.20.6.0 but are needed for the full area_kind taxonomy.
+
+Idempotent: only fills NULL / inactive rows.
+
+See docs/superpowers/specs/2026-05-24-shopfloor-live-step-fix-design.md.
+"""
+import logging
+
+_logger = logging.getLogger(__name__)
+
+KIND_TO_AREA = {
+ 'other': 'plating',
+ 'wet_process': 'plating',
+ 'receiving': 'receiving',
+ 'contract_review': 'receiving',
+ 'gating': 'receiving',
+ 'racking': 'racking',
+ 'derack': 'de_racking',
+ 'mask': 'masking',
+ 'demask': 'de_racking',
+ 'cleaning': 'plating',
+ 'electroclean': 'plating',
+ 'etch': 'plating',
+ 'rinse': 'plating',
+ 'strike': 'plating',
+ 'plate': 'plating',
+ 'replenishment': 'plating',
+ 'wbf_test': 'plating',
+ 'dry': 'plating',
+ 'bake': 'baking',
+ 'inspect': 'inspection',
+ 'final_inspect': 'inspection',
+ 'hardness_test': 'inspection',
+ 'adhesion_test': 'inspection',
+ 'salt_spray': 'inspection',
+ 'packaging': 'shipping',
+ 'ship': 'shipping',
+ 'blast': 'blasting',
+ 'bead_blast': 'blasting',
+ 'media_blast': 'blasting',
+}
+
+
+def migrate(cr, version):
+ # Phase 1 — Pre-create the column NULL-permitting so we can seed it
+ # BEFORE Odoo's schema sync tries to enforce NOT NULL.
+ cr.execute(
+ "ALTER TABLE fp_step_kind "
+ "ADD COLUMN IF NOT EXISTS area_kind VARCHAR"
+ )
+
+ # Phase 2 — Seed area_kind on existing kinds. Idempotent: only fills
+ # NULLs, so re-running -u is safe.
+ seeded = 0
+ for code, area in KIND_TO_AREA.items():
+ cr.execute(
+ "UPDATE fp_step_kind SET area_kind = %s "
+ "WHERE code = %s "
+ "AND (area_kind IS NULL OR area_kind = '')",
+ (area, code),
+ )
+ seeded += cr.rowcount
+ _logger.info(
+ '[live-step-fix] kind.area_kind seeded on %s known-code rows',
+ seeded,
+ )
+
+ # Phase 3 — Fallback: any user-created custom kinds not in our seed
+ # map → 'plating'. Clears the NOT NULL constraint for any leftover.
+ cr.execute(
+ "UPDATE fp_step_kind SET area_kind = 'plating' "
+ "WHERE area_kind IS NULL OR area_kind = ''"
+ )
+ if cr.rowcount:
+ _logger.info(
+ '[live-step-fix] %s unknown kinds defaulted to plating',
+ cr.rowcount,
+ )
+
+ # Phase 4 — Activate kinds we need for full coverage.
+ activated = 0
+ for code in ('derack', 'demask', 'gating'):
+ cr.execute(
+ "UPDATE fp_step_kind SET active = TRUE "
+ "WHERE code = %s AND active = FALSE",
+ (code,),
+ )
+ activated += cr.rowcount
+ _logger.info(
+ '[live-step-fix] %s kinds activated (derack/demask/gating)',
+ activated,
+ )
diff --git a/fusion_plating/fusion_plating/models/fp_step_kind.py b/fusion_plating/fusion_plating/models/fp_step_kind.py
index a863c218..af897cad 100644
--- a/fusion_plating/fusion_plating/models/fp_step_kind.py
+++ b/fusion_plating/fusion_plating/models/fp_step_kind.py
@@ -34,6 +34,32 @@ class FpStepKind(models.Model):
string='Icon',
default='fa-cog',
)
+ # 2026-05-24 — Shop Floor live-step fix.
+ # Each kind self-declares which plant-view column its steps land in.
+ # Replaces the hardcoded _STEP_KIND_TO_AREA dict (removed from
+ # fusion_plating_jobs/models/fp_job_step.py). Pre-migrate
+ # 19.0.21.2.0 seeds existing rows before NOT NULL hits the schema.
+ 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='Shop Floor Column',
+ required=True,
+ index=True,
+ tracking=True,
+ help='Determines which column on the Shop Floor plant kanban shows '
+ 'cards whose active step uses this kind. Step kinds drive '
+ 'routing automatically — picking a kind tells the system both '
+ 'what gates fire AND where the card lives.',
+ )
company_id = fields.Many2one(
'res.company', string='Company',
default=lambda self: self.env.company,
diff --git a/fusion_plating/fusion_plating/static/src/xml/simple_recipe_editor.xml b/fusion_plating/fusion_plating/static/src/xml/simple_recipe_editor.xml
index 1240c484..147a10b9 100644
--- a/fusion_plating/fusion_plating/static/src/xml/simple_recipe_editor.xml
+++ b/fusion_plating/fusion_plating/static/src/xml/simple_recipe_editor.xml
@@ -509,6 +509,7 @@