# FP Step Kind — User-Extensible Model
Date: 2026-05-04
Status: design + implementation
## Problem
`default_kind` on `fp.step.template` is a hardcoded `Selection` of 24 entries. Users can't add new kinds (e.g. "Shot Peen", "Passivation"). The same Selection list is duplicated on `fusion.plating.process.node`. Default-input seeding (`DEFAULT_INPUTS_BY_KIND`) is also locked in Python — adding a new kind would require a code change + module update.
## Solution
Convert `default_kind` from `Selection` → `Many2one('fp.step.kind')`. Move the default-input templates from a Python dict into seeded data records on a new `fp.step.kind.default.input` child model. Users add new kinds through the standard Odoo CRUD form.
## Models
### `fp.step.kind`
| Field | Type | Notes |
|---|---|---|
| `code` | Char, required, unique per company | Technical key (lowercase). Stable XML IDs use this. |
| `name` | Char, required, translated | UI label (e.g. "Cleaning") |
| `sequence` | Integer | Order in dropdown |
| `active` | Boolean default True | Archive instead of delete |
| `icon` | Selection (reuses 24-icon list from `fp.process.node`) | Optional |
| `description` | Html | Optional ops note |
| `company_id` | Many2one res.company | Multi-co support |
| `default_input_ids` | One2many → `fp.step.kind.default.input` | The seed list |
### `fp.step.kind.default.input`
Same shape as `fp.step.template.input`: `name`, `input_type`, `target_unit`, `sequence`, `required`, `hint`, `selection_options`. Plus `kind_id` parent FK.
## Field changes
### `fp.step.template`
- **add** `kind_id = Many2one('fp.step.kind', ondelete='restrict', string='Step Kind')` — user-facing input
- **change** existing `default_kind` from `Selection(...)` to `default_kind = fields.Char(related='kind_id.code', store=True, readonly=True)` — back-compat shim. Lets every legacy `node.default_kind == 'cleaning'` comparison keep working without touching 30+ sites. Stored=True so existing search domains (`('default_kind', '=', 'foo')`) still work.
### `fusion.plating.process.node`
- Same: add `kind_id`, convert `default_kind` to `related='kind_id.code'` stored Char.
### `fp.job.workflow.state.trigger_default_kinds`
- **No change in Phase 1.** Stays a Char of comma-separated codes. The codes still match `kind_id.code` after migration. Phase 2 deferred convert to m2m.
## DEFAULT_INPUTS_BY_KIND
Removed from `fp_step_template.py`. Lives in seed data XML. `action_seed_default_inputs` reads `tpl.kind_id.default_input_ids` instead of the dict.
## Migration `19.0.18.13.0/post-migrate.py`
1. SQL-read existing `default_kind` text values from `fp_step_template` and `fusion_plating_process_node` BEFORE Odoo recomputes the related field.
2. Build map `code → fp.step.kind.id` from seeded records.
3. SQL UPDATE `kind_id` per row.
4. Trigger recompute of stored related `default_kind` (or leave — values are already there from step 1).
5. Log counts.
## Why not drop `default_kind` entirely?
- Comma-CSV `trigger_default_kinds` on workflow state still searches it.
- 30+ comparison sites and existing search domains in views.
- Stored related Char keeps it a single source of truth (m2o-derived) without a churn-PR through every dependent.
## View changes
- `fp_step_template_views.xml` — replace `` with ``. Same for search filter and `group_by="default_kind"` → `group_by="kind_id"`.
- `fp_process_node_views.xml` — same swap on the one occurrence.
- New `fp_step_kind_views.xml` — list/form/menu so admins can manage kinds. Form embeds `default_input_ids` as inline list.
## Controller / JS changes
- `simple_recipe_controller.py`:
- Add `kind_id` (id) and `kind_name` (label) to all step-payload responses.
- Accept `kind_id` int on `_save_recipe`/template-create endpoints.
- Keep returning `default_kind` (the code Char) for back-compat with deployed editor sessions until cache flushes.
- `simple_recipe_editor.js`:
- Replace the `