Spec documents the 4 code defects + structural vocabulary mismatch between fp.step.kind taxonomy and the legacy _STEP_KIND_TO_AREA dict, plus the 30 library templates missing metadata. Plan breaks the work into 15 bite-sized tasks across 2 phases. Implementation shipped in: -c75d2bde(Odoo 19 session.authenticate signature fix — separate) -7b90f210(Phase 1: fusion_plating) -b06d28e7(Phase 2: jobs + shopfloor) -6afc9e3c(follow-up tracking + pattern anchor) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
30 KiB
Shop Floor — Live Step + Kind/Library Cleanup
Date: 2026-05-24
Modules: fusion_plating, fusion_plating_jobs, fusion_plating_shopfloor
Status: Revised after step-library audit. Awaiting implementation plan.
Problem
All 7 jobs on entech are stuck in the Receiving column of the Shop Floor
plant kanban, each tagged with a purple "📋 QA-005 review" chip, even though
every step on every one of them is done. The board doesn't reflect shop
state.
Investigation surfaced four code defects, a structural vocabulary
mismatch between the user-extensible step kind taxonomy and the hardcoded
area_kind mapping, gaps in the kind taxonomy (no blast kind, three
relevant kinds inactive), and 30 step-library templates missing codes,
descriptions, and meaningful icons.
Defect 1 — _compute_card_state edge case mislabels done jobs
fusion_plating_jobs/models/fp_job.py:261-267
A job whose active_step_id is False (all steps done OR no steps at all)
defaults to 'contract_review' regardless of job.state. Done jobs get a
QA-005 chip they don't deserve.
Defect 2 — _compute_active_step_id is too narrow
fusion_plating_jobs/models/fp_job.py:386-391
Only matches state == 'in_progress'. Between-step / paused / ready jobs
have active_step_id = False. Combined with Defect 3, these teleport to
Receiving.
Defect 3 — column-resolve fallback is 'receiving'
fusion_plating_shopfloor/controllers/plant_kanban.py:161-170
When active_step_id is False this fallback fires for every non-running
job. Receiving becomes a parking lot.
Defect 4 — done jobs aren't filtered off the board
Done + cancelled jobs stay visible forever. The 7 stuck cards on entech are
all state='done' jobs that shipped weeks ago.
Defect 5 (structural) — kind→area_kind vocabulary mismatch
fp.step.kind is a user-extensible taxonomy (28 records, 12 active in the
dropdown post 2026-05-24 dedup). kind_id is required=True on both
fp.step.template and fusion.plating.process.node, defaulting to
code='other'.
fp.job.step._compute_area_kind reads recipe_node.default_kind (the kind
code) through the hardcoded _STEP_KIND_TO_AREA dict in
fp_job_step.py:25-73.
The two vocabularies overlap on 7 of 28 codes. Adoption on entech:
kind.code |
Nodes | Mapping exists? | Falls to |
|---|---|---|---|
other |
240 | ❌ | 'plating' |
racking |
122 | ✅ | 'racking' ✓ |
wet_process |
105 | ❌ | 'plating' (lucky — wet line IS plating) |
bake |
103 | ✅ | 'baking' ✓ |
mask |
92 | ❌ (dict has 'masking') |
'plating' (wrong) |
inspect |
52 | ❌ (dict has 'inspection') |
'plating' (wrong) |
plate |
35 | ❌ (dict has 'e_nickel_plate') |
'plating' (lucky) |
final_inspect |
31 | ❌ (dict has 'final_inspection') |
'plating' (wrong) |
contract_review |
17 | ✅ | 'receiving' ✓ |
receiving |
16 | ✅ | 'receiving' ✓ |
ship |
3 | ❌ (dict has 'shipping') |
'plating' (wrong) |
The structural fix: make area_kind a required field on fp.step.kind
itself so each kind self-declares its column.
Defect 6 (taxonomy) — kinds that should exist but don't / are inactive
| Kind | Currently | Needed because |
|---|---|---|
blast |
Does not exist | 11 recipe nodes named "Blasting" can't be classified correctly. There's no kind that maps to the Blasting column. |
derack |
Exists but active=False |
23+ recipe nodes named "De-racking" / "DeRacking" need their own kind for tablet routing clarity (area_kind='de_racking'). |
demask |
Exists but active=False |
33 recipe nodes named "De-Masking" are misclassified as mask → land in Masking column. Per spec §D4 De-Masking folds into De-Racking. |
gating |
Exists but active=False |
50+ "Ready For X" recipe nodes are unclassified gates. Without gating they fall back to other → catch-all. |
Defect 7 (library) — 30 step-library templates missing metadata
Step Library audit (38 active templates):
| Field | Has it | Missing |
|---|---|---|
code |
8 | 30 |
description |
8 | 30 |
Meaningful icon (not fa-cog) |
13 | 25 |
material_callout |
0 | 38 |
process_type_id |
0 | 38 |
The 8 well-formed templates (RECV_STD, ELEC_CLEAN_STD, STRIKE_STD, etc.)
came from the XML data file. The remaining 30 came from
_seed_step_library_if_empty() (programmatic seed from ENP-ALUM-BASIC recipe)
without their library-management metadata.
Several library templates are also classified to the wrong kind. Examples:
| Template | Currently kind |
Should be kind |
|---|---|---|
| Blasting | other |
blast (kind we're creating) |
| De-Masking | mask |
demask (per spec §D4) |
| Ready for Plating / Ready for processing | plate / other |
gating |
| Pre-Measurements / Check Sulfamate Nickel Area | other |
inspect |
| Nickel Strip / Nickel Strip - Steel Line | plate |
wet_process (it's a strip, not plating) |
Defect 8 (recipe nodes) — in-the-wild misclassifications
Once kinds are fixed and library is corrected, the EXISTING ~880 recipe nodes still point at the wrong kind in well-defined patterns:
| Pattern | Affected nodes | Re-point to |
|---|---|---|
name = 'Blasting' AND kind = other |
11 | kind = blast |
name ILIKE 'Ready %' AND kind != gating |
~50+ | kind = gating |
name ILIKE '%De-Masking%' OR '%DeMasking%' AND kind = mask |
33 | kind = demask |
name = 'Scheduling' AND kind = other |
5 | kind = gating |
name ILIKE '%Nickel Strip%' AND kind = plate |
~10 | kind = wet_process |
name ILIKE '%Pre-Measurement%' OR '%Check Sulfamate%' AND kind = other |
~10 | kind = inspect |
These are auto-migratable because the patterns are unambiguous. The harder
calls (e.g. "Post Plate Inspection" — inspect or final_inspect?) stay
manual.
Approved fix
Change 1 — _compute_active_step_id priority chain
Replace the single-state filter with a priority lookup over step_ids
sorted by sequence. First match wins:
in_progress > paused > ready > first pending
If every step is done (or no steps exist), returns False — handled by
Change 2.
Why this order:
in_progressis the most informative.pausedmeans someone was working and stopped; the card belongs at that station so the next operator can pick it up.readyis the next-up step waiting on an operator.- The first
pendingafter adoneis the "next gate" — where the card visually waits.
File: fusion_plating_jobs/models/fp_job.py
Change 2 — _compute_card_state edge case
Replace the buggy "no active step → contract_review" fallback with:
if not job.active_step_id:
if job.state == 'done':
job.card_state = 'done'
elif job._fp_inbound_not_received():
job.card_state = 'no_parts'
else:
job.card_state = 'ready' # no steps yet — recipe not assigned
continue
File: fusion_plating_jobs/models/fp_job.py
Change 3 — Board state filter
Add ('state', 'in', ('confirmed', 'in_progress')) to the fp.job search
domain in /fp/landing/plant_kanban. Done + cancelled jobs disappear from
the board; they remain reachable elsewhere.
File: fusion_plating_shopfloor/controllers/plant_kanban.py
Change 4 — Column-resolve fallback (comment only)
_resolve_card_area's 'receiving' fallback stays but updates inline
comment to explain the new semantics (truly orphaned cards only).
File: fusion_plating_shopfloor/controllers/plant_kanban.py
Change 5 — fp.step.kind.area_kind field (structural)
Add a required Selection field to fp.step.kind. Each kind self-declares
which plant-view column its steps belong in.
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.',
)
File: fusion_plating/models/fp_step_kind.py
Change 6 — _compute_area_kind priority chain
Simplify fp.job.step._compute_area_kind:
@api.depends(
'work_centre_id.area_kind',
'recipe_node_id.kind_id.area_kind',
)
def _compute_area_kind(self):
for step in self:
# 1. work_centre.area_kind (explicit operator setup)
if step.work_centre_id and step.work_centre_id.area_kind:
step.area_kind = step.work_centre_id.area_kind
continue
# 2. recipe_node.kind_id.area_kind (kind taxonomy is authoritative)
node = step.recipe_node_id
if node and node.kind_id and node.kind_id.area_kind:
step.area_kind = node.kind_id.area_kind
continue
# 3. Catch-all — data integrity issue if we land here
step.area_kind = 'plating'
The legacy _STEP_KIND_TO_AREA dict is deleted.
File: fusion_plating_jobs/models/fp_job_step.py
Change 7 — Step Kind UI surfaces area_kind
- Form view (
fp_step_kind_views.xml) — addarea_kindas a prominent picker next tocode+name, with a help-text inline ("Cards whose active step uses this kind appear in this column on the Shop Floor board"). - List view — add
area_kindas a chip column. - Simple Editor kind picker (
simple_recipe_editor.xml:506-522) — option label becomes "Masking — Masking column" so authors see the routing at pick time. Requires updatingkindOptionspayload insimple_recipe_controller.pyto includearea_kind+ a human-readable column label per kind.
Change 8 — Step Kind taxonomy expansion (Cat A)
XML data file additions / updates in
fusion_plating/data/fp_step_kind_data.xml:
<!-- NEW: Blasting kind -->
<record id="step_kind_blast" model="fp.step.kind">
<field name="code">blast</field>
<field name="name">Blasting / Media Blast</field>
<field name="sequence">35</field>
<field name="icon">fa-bullseye</field>
<field name="area_kind">blasting</field>
</record>
<!-- Activate existing kinds + set area_kind. The records already exist
from 19.0.20.6.0 with active=False; here we flip + classify.
noupdate=1 protects user edits, so use a one-shot migration to
do the flip on existing installs (Change 10). -->
Migration (Change 10) handles the flip on existing installs since the data
file has noupdate="1":
# Activate kinds that were dropped in 19.0.20.6.0 but are needed
# for the area_kind taxonomy to be complete.
for code, area in (
('derack', 'de_racking'),
('demask', 'de_racking'),
('gating', 'receiving'),
):
cr.execute("""
UPDATE fp_step_kind
SET active = TRUE, area_kind = %s
WHERE code = %s AND active = FALSE
""", (area, code))
Change 9 — Step Template metadata backfill + additions (Cat B)
Migration backfills metadata on the 30 templates seeded without it. Idempotent — only fills NULL/empty fields, doesn't overwrite human edits.
TEMPLATE_BACKFILL = {
# name : (code, icon, kind_code, description_snippet)
'Acid Dip': ('ACID_DIP_STD', 'fa-flask', 'wet_process', 'Short acid immersion to activate the substrate before plating.'),
'Air Dry': ('AIR_DRY_STD', 'fa-sun-o', 'wet_process', 'Air drying step between wet-line operations.'),
'Bake': ('BAKE_STD', 'fa-fire', 'bake', 'Post-plate bake for hydrogen embrittlement relief.'),
'Blasting': ('BLAST_STD', 'fa-bullseye', 'blast', 'Media or bead blasting to prepare the substrate.'),
'Check Sulfamate Nickel Area': ('CHECK_SN_AREA', 'fa-search', 'inspect', 'Quick visual area check on the sulfamate nickel line.'),
'Contract Review': ('CR_STD', 'fa-file-text-o', 'contract_review', 'QA-005 contract review gate. Required when the customer flag is on.'),
'De-Masking': ('DEMASK_STD', 'fa-eraser', 'demask', 'Remove masking material after plating. Folds into De-Racking column.'),
'DeRacking': ('DERACK_STD', 'fa-th', 'derack', 'Remove parts from racks for inspection / packaging.'),
'Desmut': ('DESMUT_STD', 'fa-flask', 'wet_process', 'Remove smut from aluminium surfaces after etching.'),
'Drying': ('DRYING_STD', 'fa-sun-o', 'wet_process', 'Drying step (oven or air) at the end of the wet line.'),
'E-Nickel Plating': ('ENP_STD', 'fa-diamond', 'plate', 'Electroless nickel plate operation. Time and temp per recipe.'),
'Electroclean': ('ECLEAN_STD', 'fa-bolt', 'wet_process', 'Anodic / cathodic electrocleaning step on the cleaning line.'),
'Etch': ('ETCH_STD', 'fa-flask', 'wet_process', 'Chemical etching to prepare the substrate.'),
'Final Inspection': ('FINAL_INSP_STD','fa-check-circle','final_inspect','Final visual + dimensional QA before packing.'),
'HCl Activation': ('HCL_ACT_STD', 'fa-flask', 'wet_process', 'HCl activation dip prior to strike or plate.'),
'Inspection': ('INSP_STD', 'fa-search', 'inspect', 'In-process inspection step.'),
'Masking': ('MASK_STD', 'fa-paint-brush', 'mask', 'Apply masking to areas that should not be plated.'),
'Nickel Strip (S-1)': ('NI_STRIP_S1', 'fa-undo', 'wet_process', 'Chemical strip of prior nickel deposit (rework path).'),
'Nickel Strip - Steel Line': ('NI_STRIP_SL','fa-undo', 'wet_process', 'Chemical strip on the steel line (rework path).'),
'Post-plate Inspection': ('POST_INSP_STD', 'fa-check-circle','inspect', 'Post-plate inspection — thickness sample + visual.'),
'Pre-Measurements': ('PRE_MEAS_STD', 'fa-tachometer', 'inspect', 'Pre-process dimensional measurements (FAIR start point).'),
'Racking': ('RACK_STD', 'fa-th', 'racking', 'Load parts onto racks for plating.'),
'Ready for Plating': ('GATE_PLATE', 'fa-flag', 'gating', 'Gating step — parts staged ready for the plating line.'),
'Ready for processing': ('GATE_PROC', 'fa-flag', 'gating', 'Generic gating step — parts staged ready for the next operation.'),
'Rinse': ('RINSE_STD', 'fa-tint', 'wet_process', 'Rinse step between wet-line operations.'),
'Shipping': ('SHIP_STD', 'fa-paper-plane', 'ship', 'Final shipping / hand-off to logistics.'),
'Soak Clean': ('SOAK_CLEAN_STD','fa-bathtub', 'wet_process', 'Soak cleaning step at the start of the wet line.'),
'Surface Activation': ('SURF_ACT_STD', 'fa-flask', 'wet_process', 'Surface activation dip prior to plate.'),
'Water Break Test': ('WBF_TEST_STD', 'fa-tint', 'wet_process', 'Water-break test for surface cleanliness.'),
'Zincate': ('ZINCATE_STD', 'fa-flask', 'wet_process', 'Zincate immersion on aluminium prior to plate.'),
}
New templates (XML data file additions, noupdate="1"):
| Name | Code | Kind | Why add |
|---|---|---|---|
Hot Water Porosity Test (A-15) |
HWP_A15 |
inspect |
7 recipe nodes use it — should be in the library |
Final Inspection / Packaging |
FINAL_PKG_STD |
final_inspect |
3 recipe nodes use it; library has separate inspection + packaging but not the combined one |
Files:
fusion_plating/data/fp_step_template_data.xml— 2 new template records- Migration (Change 10) — TEMPLATE_BACKFILL loop, idempotent
Change 10 — Unified migration
New file: fusion_plating/migrations/19.0.21.2.0/pre-migrate.py
Pre-migrate runs BEFORE the area_kind NOT NULL constraint hits the
schema, so it fills values first.
import logging
_logger = logging.getLogger(__name__)
KIND_TO_AREA = {
'other': 'plating', # catch-all default
'wet_process': 'plating',
'receiving': 'receiving',
'contract_review':'receiving',
'gating': 'receiving',
'racking': 'racking',
'derack': 'de_racking',
'mask': 'masking',
'demask': 'de_racking', # spec §D4
'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 — seed area_kind on existing kinds BEFORE NOT NULL hits.
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))
# Anything still NULL: default to 'plating' to clear the constraint.
cr.execute("""
UPDATE fp_step_kind SET area_kind = 'plating'
WHERE area_kind IS NULL OR area_kind = ''
""")
_logger.info('[live-step-fix] kind.area_kind seeded')
# Phase 2 — activate the three inactive kinds we need (Cat A).
for code in ('derack', 'demask', 'gating'):
cr.execute("""
UPDATE fp_step_kind SET active = TRUE
WHERE code = %s AND active = FALSE
""", (code,))
_logger.info('[live-step-fix] derack/demask/gating activated')
New file: fusion_plating_jobs/migrations/19.0.10.24.0/post-migrate.py
Post-migrate runs AFTER schema sync, so all fields exist with values.
import logging
_logger = logging.getLogger(__name__)
# Library template metadata backfill — copied from spec Change 9.
TEMPLATE_BACKFILL = { ... } # full dict per Change 9
# Recipe node patterns to repoint (Cat C).
NODE_REPOINTING = [
# (name_filter_sql, current_kind_code, new_kind_code, description)
("name = 'Blasting'", 'other', 'blast', 'Blasting → blast'),
("name ILIKE 'Ready %%'", None, 'gating', 'Ready For X → gating'),
("name ILIKE '%%De-Masking%%' OR name ILIKE '%%DeMasking%%'", 'mask', 'demask', 'De-Masking → demask'),
("name = 'Scheduling'", 'other', 'gating', 'Scheduling → gating'),
("name ILIKE '%%Nickel Strip%%'", 'plate', 'wet_process', 'Nickel Strip → wet_process'),
("name ILIKE '%%Pre-Measurement%%' OR name ILIKE '%%Check Sulfamate%%'", 'other', 'inspect', 'Pre-Meas/Check Sulfamate → inspect'),
]
def migrate(cr, version):
from odoo.api import Environment, SUPERUSER_ID
env = Environment(cr, SUPERUSER_ID, {})
# Phase 1 — template metadata backfill (Cat B). Idempotent.
Tpl = env['fp.step.template']
Kind = env['fp.step.kind']
fixed = 0
for name, (code, icon, kind_code, desc) in TEMPLATE_BACKFILL.items():
tpl = Tpl.search([('name', '=', name)], limit=1)
if not tpl:
continue
vals = {}
if not tpl.code:
vals['code'] = code
if not tpl.description or tpl.description in ('', '<p><br></p>'):
vals['description'] = f'<p>{desc}</p>'
if tpl.icon == 'fa-cog':
vals['icon'] = icon
kind = Kind.search([('code', '=', kind_code)], limit=1)
if kind and tpl.kind_id.code != kind_code:
vals['kind_id'] = kind.id
if vals:
tpl.write(vals)
fixed += 1
_logger.info('[live-step-fix] template backfill: %s templates updated', fixed)
# Phase 2 — recipe node repointing (Cat C). Pattern-driven SQL.
for filter_sql, cur_code, new_code, desc in NODE_REPOINTING:
params = []
sql = f"""
UPDATE fusion_plating_process_node n
SET kind_id = (SELECT id FROM fp_step_kind WHERE code = %s LIMIT 1)
FROM fp_step_kind k
WHERE n.kind_id = k.id
AND ({filter_sql})
"""
params.append(new_code)
if cur_code is not None:
sql += " AND k.code = %s"
params.append(cur_code)
sql += " AND k.code != %s"
params.append(new_code)
cr.execute(sql, params)
_logger.info('[live-step-fix] repointed %s nodes: %s',
cr.rowcount, desc)
# Phase 3 — recompute area_kind on all fp.job.step rows.
steps = env['fp.job.step'].search([])
steps._compute_area_kind()
steps.flush_recordset(['area_kind'])
_logger.info('[live-step-fix] recomputed area_kind on %s steps', len(steps))
# Phase 4 — recompute active_step_id + card_state on in-flight jobs.
jobs = env['fp.job'].search([('state', 'in', ('confirmed', 'in_progress'))])
jobs._compute_active_step_id()
jobs._compute_card_state()
jobs.flush_recordset(['active_step_id', 'card_state'])
_logger.info('[live-step-fix] recomputed jobs: %s', len(jobs))
Idempotent across the board: phase 1 only fills NULLs / fa-cog defaults;
phase 2 includes AND k.code != %s so re-running won't re-do already
correct rows; phases 3-4 are pure recomputes.
Change 11 — Version bumps
| Module | From | To |
|---|---|---|
fusion_plating |
19.0.21.1.3 |
19.0.21.2.0 (schema change on fp.step.kind + data file additions) |
fusion_plating_jobs |
19.0.10.23.0 |
19.0.10.24.0 (compute change + migration) |
fusion_plating_shopfloor |
19.0.33.1.2 |
19.0.33.1.3 (controller filter + comment) |
What this approach replaces
| Dropped from the original (pre-restructure) spec | Why |
|---|---|
_RESOLVER_KIND_TO_AREA translation dict |
Kind self-declares its column — no translation needed |
_resolve_area_kind_from_name helper |
Kind taxonomy is authoritative; name resolution is unnecessary |
_STARTER_KIND_BY_NAME extensions for column routing |
The starter resolver is for default_kind seeding (Sub 12a library), not column routing — stays as-is for that purpose |
| Parenthetical stripping regex | Not needed when we read the kind directly |
Backfill of default_kind on existing recipe nodes via name resolver |
Recipe nodes already have kind_id populated by 19.0.20.6.0 pre-migrate |
Test plan
Manual smoke (on entech after deploy)
- Open Shop Floor tablet/desktop — confirm the 7 done jobs are GONE from the board.
- Plating → Configuration → Recipes & Steps → Step Kind catalog — confirm:
blastexists, active, area_kind=blastingderack,demask,gatingare nowactive=True, area_kinds correct- Every kind has area_kind set
- Plating → Configuration → Recipes & Steps → Step Library — confirm:
- All 38 templates now have a code, description, meaningful icon
Hot Water Porosity Test (A-15)andFinal Inspection / Packagingare listed- "Blasting" is
kind=blast, "De-Masking" iskind=demask, "Ready for ..." arekind=gating
- Open the Simple Recipe Editor; click "+ Add new kind" — confirm area_kind picker is visible/required in the inline-create flow.
- Create a fresh test job from any recipe (e.g. ENP-ALUM-BASIC):
a. Confirm it lands in Receiving column with
card_state='ready'. b. Walk through all steps — confirm column transitions follow area_kind sequence. c. Mark job done → confirm card drops off the board. - Verify a step with
state='paused'keeps the card at its column.
Spot-check existing data
-- Every node should have a kind with area_kind set.
SELECT n.id, n.name, k.code, k.area_kind
FROM fusion_plating_process_node n
JOIN fp_step_kind k ON k.id = n.kind_id
WHERE k.area_kind IS NULL OR k.area_kind = '';
-- expected: 0 rows
-- Blasting nodes should now use blast kind.
SELECT k.code, COUNT(*) FROM fusion_plating_process_node n
JOIN fp_step_kind k ON k.id = n.kind_id
WHERE n.name = 'Blasting' GROUP BY k.code;
-- expected: all rows have k.code = 'blast'
-- Ready For X gating nodes.
SELECT k.code, COUNT(*) FROM fusion_plating_process_node n
JOIN fp_step_kind k ON k.id = n.kind_id
WHERE n.name ILIKE 'Ready %' GROUP BY k.code;
-- expected: all rows have k.code = 'gating'
-- De-Masking nodes use demask.
SELECT k.code, COUNT(*) FROM fusion_plating_process_node n
JOIN fp_step_kind k ON k.id = n.kind_id
WHERE n.name ILIKE '%De-Masking%' OR n.name ILIKE '%DeMasking%'
GROUP BY k.code;
-- expected: all rows have k.code = 'demask'
-- Template code coverage.
SELECT COUNT(*) FROM fp_step_template
WHERE active = TRUE AND (code IS NULL OR code = '');
-- expected: 0
Automated battle test
New script: fusion_plating_quality/scripts/bt_s24_between_steps.py covering
the live-step priority chain end-to-end (see prior version of the spec for
full pseudocode — unchanged).
Existing tests
Existing tests in fusion_plating_shopfloor/tests/ and
fusion_plating_jobs/tests/ may need updates for:
- The new
statefilter in/fp/landing/plant_kanban. - The new
active_step_idpriority chain.
Re-run all bt_s*.py scripts to confirm no regressions in S1-S23.
Roll-out
- Implement Changes 1-11 in a single branch.
- Local dev test (
docker exec odoo-dev-app odoo -d fusion-dev -u fusion_plating,fusion_plating_jobs,fusion_plating_shopfloor --stop-after-init). - Deploy to entech using the standard
pct exec 111flow. Pre-migrate seeds run automatically. - Verify on entech with manual smoke + SQL spot-checks.
- Commit + push to GitHub.
Non-goals (explicit)
- Re-assigning historical steps to
work_centre_id. The 85+ steps with NULLwork_centre_idstay that way. The kind→area_kind lookup gives them correctarea_kindwithout needing a work_centre. - Recipe authoring UX changes beyond the kind picker hint. Required-field enforcement on
kind_idalready exists. - Removing the "Other" kind. Stays as a catch-all default mapped to
'plating'. - Card_state precedence rework. Rules 1-13 stay; only the edge-case fallback changes.
- Mini-timeline rendering. Separate compute (
mini_timeline_json), out of scope. - Hidden-but-recent done jobs. No "recent shipments" filter.
- Subjective node re-classification. "Post Plate Inspection" stays whatever the recipe author picked (
inspectvsfinal_inspect). Only the unambiguous patterns in Change 10 phase 2 are auto-migrated. - process_type_id / material_callout backfill on templates. Out of scope for this spec — those need recipe-author input per template.
Files touched (summary)
| File | Change |
|---|---|
fusion_plating/models/fp_step_kind.py |
New area_kind Selection field (Change 5) |
fusion_plating/views/fp_step_kind_views.xml |
Add area_kind to form + list (Change 7) |
fusion_plating/controllers/simple_recipe_controller.py |
Include area_kind + label in kindOptions (Change 7) |
fusion_plating/static/src/xml/simple_recipe_editor.xml |
Kind picker shows "→ Column" suffix (Change 7) |
fusion_plating/data/fp_step_kind_data.xml |
New step_kind_blast record (Change 8) |
fusion_plating/data/fp_step_template_data.xml |
New Hot Water Porosity Test + Final Inspection / Packaging templates (Change 9) |
fusion_plating/migrations/19.0.21.2.0/pre-migrate.py |
NEW — seed area_kind, activate kinds (Change 10 phase 1-2) |
fusion_plating/__manifest__.py |
Version bump (Change 11) |
fusion_plating_jobs/models/fp_job.py |
Rewrite _compute_active_step_id (Change 1) + _compute_card_state edge case (Change 2) |
fusion_plating_jobs/models/fp_job_step.py |
Simplify _compute_area_kind (Change 6); drop _STEP_KIND_TO_AREA dict |
fusion_plating_jobs/migrations/19.0.10.24.0/post-migrate.py |
NEW — template backfill + node repointing + recomputes (Change 10 phase 1-4) |
fusion_plating_jobs/__manifest__.py |
Version bump (Change 11) |
fusion_plating_shopfloor/controllers/plant_kanban.py |
Add state filter (Change 3) + comment (Change 4) |
fusion_plating_shopfloor/__manifest__.py |
Version bump (Change 11) |
fusion_plating_quality/scripts/bt_s24_between_steps.py |
NEW — battle test |
Estimated diff: ~400 lines added (most in the migration data tables), ~30 modified, ~50 deleted (the _STEP_KIND_TO_AREA dict goes away).