chnages
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
# 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 `<field name="default_kind"/>` with `<field name="kind_id" options="{'no_create_edit': false, 'no_quick_create': false}"/>`. 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 `<select>` over selection options with a Many2one-style typeahead. Fetch list from new endpoint `/fusion_plating/recipe/kinds` returning `[{id, code, name, icon}]`.
|
||||
- Submit `kind_id` (int) instead of `default_kind` (string).
|
||||
- `simple_recipe_editor.xml`:
|
||||
- Replace `<select t-model=".default_kind">` block with a `<select t-model=".kind_id">` populated from fetched list, plus a "+ New kind…" link that opens a small inline form via `/fusion_plating/recipe/kinds/create`.
|
||||
|
||||
## ACLs (`ir.model.access.csv`)
|
||||
```
|
||||
access_fp_step_kind_operator,fp.step.kind.operator,model_fp_step_kind,group_fusion_plating_operator,1,0,0,0
|
||||
access_fp_step_kind_supervisor,fp.step.kind.supervisor,model_fp_step_kind,group_fusion_plating_supervisor,1,1,1,0
|
||||
access_fp_step_kind_manager,fp.step.kind.manager,model_fp_step_kind,group_fusion_plating_manager,1,1,1,1
|
||||
access_fp_step_kind_default_input_* … same triplet
|
||||
```
|
||||
|
||||
## Manifest
|
||||
- Bump `version` to `19.0.18.13.0`
|
||||
- Add to `data`:
|
||||
- `data/fp_step_kind_data.xml` (BEFORE `fp_step_template_data.xml` since templates reference kinds)
|
||||
- `views/fp_step_kind_views.xml`
|
||||
|
||||
## Out of scope (Phase 2)
|
||||
- Convert `trigger_default_kinds` Char-CSV to m2m on `fp.job.workflow.state`
|
||||
- Remove DEFAULT_INPUTS_BY_KIND dict from migration scripts (one-shot dev scripts — no harm leaving)
|
||||
- Migrate stored `default_kind` Char to a non-stored related once all callers refactored
|
||||
|
||||
## Test plan
|
||||
1. `odoo -d admin -u fusion_plating --stop-after-init` (entech) — install + migration runs cleanly
|
||||
2. UI smoke: open a step template, kind dropdown shows 24 seeded kinds, dropdown allows quick-create
|
||||
3. Create new kind "Passivation" with 3 default inputs → save → return to step template → "Seed defaults" button populates the 3 inputs
|
||||
4. Open recipe editor (simple): kind dropdown matches; existing recipes show correct kind label
|
||||
5. Run a step that's wired through workflow-state trigger (e.g. completion of a `receiving` step → state changes to "On Floor") to confirm CSV-trigger backwards compat still works
|
||||
@@ -32,6 +32,18 @@ def post_init_hook(env):
|
||||
_migrate_legacy_uom_columns(env)
|
||||
|
||||
|
||||
def _resolve_kind_id(env, code):
|
||||
"""Look up an fp.step.kind id by code. Returns False if not found.
|
||||
Cheap helper used during seeding so legacy code paths that referenced
|
||||
string codes can keep their semantics."""
|
||||
if not code:
|
||||
return False
|
||||
rec = env['fp.step.kind'].search(
|
||||
[('code', '=', code)], limit=1,
|
||||
)
|
||||
return rec.id or False
|
||||
|
||||
|
||||
def _backfill_contract_review_template(env):
|
||||
"""Idempotent — ensure the Contract Review library template exists.
|
||||
|
||||
@@ -45,7 +57,7 @@ def _backfill_contract_review_template(env):
|
||||
return # already there
|
||||
tpl = Tpl.create({
|
||||
'name': 'Contract Review',
|
||||
'default_kind': 'contract_review',
|
||||
'kind_id': _resolve_kind_id(env, 'contract_review'),
|
||||
})
|
||||
tpl.action_seed_default_inputs()
|
||||
_logger.info(
|
||||
@@ -236,7 +248,7 @@ def _create_template_from_node(env, node, seen):
|
||||
'process_type_id': node.process_type_id.id,
|
||||
'requires_signoff': node.requires_signoff,
|
||||
'requires_predecessor_done': node.requires_predecessor_done,
|
||||
'default_kind': kind,
|
||||
'kind_id': _resolve_kind_id(env, kind),
|
||||
}
|
||||
# Snapshot tank_ids if the node has them (added by Sub 12a;
|
||||
# existing nodes may not).
|
||||
@@ -275,7 +287,10 @@ def _seed_minimal_library(env):
|
||||
('Shipping', 'ship'),
|
||||
]
|
||||
for name, kind in minimal:
|
||||
tpl = Tpl.create({'name': name, 'default_kind': kind})
|
||||
tpl = Tpl.create({
|
||||
'name': name,
|
||||
'kind_id': _resolve_kind_id(env, kind),
|
||||
})
|
||||
tpl.action_seed_default_inputs()
|
||||
_logger.info(
|
||||
'Fusion Plating: seeded minimal step library (%s entries)',
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
{
|
||||
'name': 'Fusion Plating',
|
||||
'version': '19.0.18.12.4',
|
||||
'version': '19.0.18.13.8',
|
||||
'category': 'Manufacturing/Plating',
|
||||
'summary': 'Core plating / metal finishing ERP: facilities, processes, tanks, baths, jobs, operators.',
|
||||
'description': """
|
||||
@@ -98,6 +98,12 @@ Copyright (c) 2026 Nexa Systems Inc. All rights reserved.
|
||||
'views/fp_facility_views.xml',
|
||||
'views/fp_bath_views.xml',
|
||||
'views/fp_process_node_views.xml',
|
||||
# Sub 14b — fp.step.kind catalog. MUST load before
|
||||
# fp_step_template_data.xml (templates reference kinds via
|
||||
# kind_id) AND before fp_step_template_views.xml (the form
|
||||
# references the kind action menu).
|
||||
'views/fp_step_kind_views.xml',
|
||||
'data/fp_step_kind_data.xml',
|
||||
'views/fp_step_template_views.xml',
|
||||
'views/fp_rack_tag_views.xml',
|
||||
'views/fp_job_step_move_views.xml',
|
||||
@@ -128,10 +134,14 @@ Copyright (c) 2026 Nexa Systems Inc. All rights reserved.
|
||||
'fusion_plating/static/src/scss/recipe_tree_editor.scss',
|
||||
'fusion_plating/static/src/scss/fp_chatter_dark.scss',
|
||||
'fusion_plating/static/src/scss/simple_recipe_editor.scss',
|
||||
# Sub 14b — visual icon picker for fp.step.kind etc.
|
||||
'fusion_plating/static/src/scss/fp_icon_picker.scss',
|
||||
'fusion_plating/static/src/xml/recipe_tree_editor.xml',
|
||||
'fusion_plating/static/src/xml/simple_recipe_editor.xml',
|
||||
'fusion_plating/static/src/xml/fp_icon_picker.xml',
|
||||
'fusion_plating/static/src/js/recipe_tree_editor.js',
|
||||
'fusion_plating/static/src/js/simple_recipe_editor.js',
|
||||
'fusion_plating/static/src/js/fp_icon_picker.js',
|
||||
],
|
||||
},
|
||||
'demo': [
|
||||
|
||||
@@ -26,7 +26,7 @@ _SNAPSHOT_FIELDS = [
|
||||
'parallel_start',
|
||||
'triggers_workflow_state_id', # Sub 14 — workflow milestone trigger
|
||||
'requires_rack_assignment', 'requires_transition_form',
|
||||
'default_kind',
|
||||
'kind_id', # Sub 14b — replaces default_kind (now a related Char)
|
||||
]
|
||||
|
||||
# Fields on fp.step.template.input that copy 1:1 into
|
||||
@@ -90,6 +90,8 @@ class SimpleRecipeController(http.Controller):
|
||||
'sequence': step.sequence,
|
||||
'icon': step.icon,
|
||||
'default_kind': step.default_kind,
|
||||
'kind_id': step.kind_id.id if step.kind_id else False,
|
||||
'kind_name': step.kind_id.name if step.kind_id else '',
|
||||
'requires_signoff': step.requires_signoff,
|
||||
'requires_rack_assignment': step.requires_rack_assignment,
|
||||
'requires_transition_form': step.requires_transition_form,
|
||||
@@ -150,6 +152,8 @@ class SimpleRecipeController(http.Controller):
|
||||
'code': t.code,
|
||||
'icon': t.icon,
|
||||
'default_kind': t.default_kind,
|
||||
'kind_id': t.kind_id.id if t.kind_id else False,
|
||||
'kind_name': t.kind_id.name if t.kind_id else '',
|
||||
'station_count': len(t.tank_ids),
|
||||
}
|
||||
for t in records
|
||||
@@ -201,6 +205,8 @@ class SimpleRecipeController(http.Controller):
|
||||
'code': tpl.code or '',
|
||||
'icon': tpl.icon or 'fa-cog',
|
||||
'default_kind': tpl.default_kind or '',
|
||||
'kind_id': tpl.kind_id.id if tpl.kind_id else False,
|
||||
'kind_name': tpl.kind_id.name if tpl.kind_id else '',
|
||||
'description': tpl.description or '',
|
||||
'requires_signoff': tpl.requires_signoff,
|
||||
'requires_predecessor_done': tpl.requires_predecessor_done,
|
||||
@@ -245,8 +251,11 @@ class SimpleRecipeController(http.Controller):
|
||||
"""
|
||||
Tpl = request.env['fp.step.template']
|
||||
# Whitelist — never trust client-provided write_uid / id / etc.
|
||||
# Sub 14b: `default_kind` is now a related read-only Char. The
|
||||
# client may still send it as a string code for back-compat — we
|
||||
# translate it to kind_id below.
|
||||
allowed = {
|
||||
'name', 'code', 'icon', 'default_kind', 'description',
|
||||
'name', 'code', 'icon', 'kind_id', 'description',
|
||||
'requires_signoff', 'requires_predecessor_done',
|
||||
'parallel_start',
|
||||
'triggers_workflow_state_id', # Sub 14
|
||||
@@ -254,6 +263,11 @@ class SimpleRecipeController(http.Controller):
|
||||
'tank_ids',
|
||||
}
|
||||
clean = {k: v for k, v in (vals or {}).items() if k in allowed}
|
||||
# Back-compat: accept default_kind (string code) and resolve to kind_id.
|
||||
if 'kind_id' not in clean and (vals or {}).get('default_kind'):
|
||||
clean['kind_id'] = self._resolve_kind_id_from_code(
|
||||
vals['default_kind'],
|
||||
)
|
||||
# tank_ids comes in as a plain list of ids from the OWL form;
|
||||
# translate into the Odoo (6, 0, ids) command form.
|
||||
if 'tank_ids' in clean:
|
||||
@@ -266,6 +280,15 @@ class SimpleRecipeController(http.Controller):
|
||||
tpl = Tpl.create(clean)
|
||||
return {'ok': True, 'template': self._library_payload(tpl)}
|
||||
|
||||
def _resolve_kind_id_from_code(self, code):
|
||||
"""Look up fp.step.kind id by code. Empty string → False."""
|
||||
if not code:
|
||||
return False
|
||||
rec = request.env['fp.step.kind'].search(
|
||||
[('code', '=', code)], limit=1,
|
||||
)
|
||||
return rec.id or False
|
||||
|
||||
@http.route('/fp/simple_recipe/library/seed_defaults', type='jsonrpc', auth='user')
|
||||
def library_seed_defaults(self, template_id):
|
||||
"""Run action_seed_default_inputs on this template. Idempotent —
|
||||
@@ -340,6 +363,55 @@ class SimpleRecipeController(http.Controller):
|
||||
],
|
||||
}
|
||||
|
||||
@http.route('/fp/simple_recipe/kinds/list',
|
||||
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."""
|
||||
Kind = request.env['fp.step.kind']
|
||||
return {
|
||||
'kinds': [
|
||||
{
|
||||
'id': k.id,
|
||||
'code': k.code or '',
|
||||
'name': k.name or '',
|
||||
'icon': k.icon or '',
|
||||
'sequence': k.sequence,
|
||||
}
|
||||
for k in Kind.search(
|
||||
[('active', '=', True)], order='sequence, name',
|
||||
)
|
||||
],
|
||||
}
|
||||
|
||||
@http.route('/fp/simple_recipe/kinds/create',
|
||||
type='jsonrpc', auth='user')
|
||||
def kinds_create(self, name, code=''):
|
||||
"""Sub 14b — Inline create for "+ New kind…" in the library
|
||||
form. Auto-derives a code from the name if blank."""
|
||||
Kind = request.env['fp.step.kind']
|
||||
if not name or not name.strip():
|
||||
return {'ok': False, 'error': 'name_required'}
|
||||
# check_access via create attempt — supervisors+ allowed (ACL).
|
||||
if not code:
|
||||
code = name.strip().lower().replace(' ', '_').replace('/', '_')
|
||||
existing = Kind.search([('code', '=', code)], limit=1)
|
||||
if existing:
|
||||
return {
|
||||
'ok': True, 'id': existing.id,
|
||||
'name': existing.name, 'code': existing.code,
|
||||
'duplicate': True,
|
||||
}
|
||||
rec = Kind.create({
|
||||
'name': name.strip(),
|
||||
'code': code,
|
||||
})
|
||||
return {
|
||||
'ok': True, 'id': rec.id,
|
||||
'name': rec.name, 'code': rec.code,
|
||||
'duplicate': False,
|
||||
}
|
||||
|
||||
@http.route('/fp/simple_recipe/workflow_states/list',
|
||||
type='jsonrpc', auth='user')
|
||||
def workflow_states_list(self):
|
||||
@@ -457,7 +529,7 @@ class SimpleRecipeController(http.Controller):
|
||||
node.check_access('write')
|
||||
allowed = {
|
||||
'name', 'description', 'icon',
|
||||
'default_kind',
|
||||
'kind_id', # Sub 14b — replaces default_kind
|
||||
'requires_signoff', 'requires_predecessor_done',
|
||||
'parallel_start', # Sub 13
|
||||
'triggers_workflow_state_id', # Sub 14
|
||||
@@ -467,6 +539,11 @@ class SimpleRecipeController(http.Controller):
|
||||
'collect_measurements',
|
||||
}
|
||||
clean = {k: v for k, v in (vals or {}).items() if k in allowed}
|
||||
# Back-compat: accept default_kind (string code) and resolve.
|
||||
if 'kind_id' not in clean and (vals or {}).get('default_kind'):
|
||||
clean['kind_id'] = self._resolve_kind_id_from_code(
|
||||
vals['default_kind'],
|
||||
)
|
||||
if clean:
|
||||
node.write(clean)
|
||||
return {'ok': True}
|
||||
|
||||
928
fusion_plating/fusion_plating/data/fp_step_kind_data.xml
Normal file
928
fusion_plating/fusion_plating/data/fp_step_kind_data.xml
Normal file
@@ -0,0 +1,928 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<!-- 24 seeded Step Kinds — XML IDs use the original Selection
|
||||
keys so post-migrate can map old default_kind = 'cleaning'
|
||||
to env.ref('fusion_plating.step_kind_cleaning').
|
||||
|
||||
noupdate=1 so user edits to defaults survive `-u`. -->
|
||||
|
||||
<record id="step_kind_receiving" model="fp.step.kind">
|
||||
<field name="code">receiving</field>
|
||||
<field name="name">Receiving / Incoming Inspection</field>
|
||||
<field name="sequence">10</field>
|
||||
<field name="icon">fa-truck</field>
|
||||
</record>
|
||||
<record id="step_kind_contract_review" model="fp.step.kind">
|
||||
<field name="code">contract_review</field>
|
||||
<field name="name">Contract Review (QA-005)</field>
|
||||
<field name="sequence">20</field>
|
||||
<field name="icon">fa-file-text-o</field>
|
||||
</record>
|
||||
<record id="step_kind_racking" model="fp.step.kind">
|
||||
<field name="code">racking</field>
|
||||
<field name="name">Racking</field>
|
||||
<field name="sequence">30</field>
|
||||
<field name="icon">fa-server</field>
|
||||
</record>
|
||||
<record id="step_kind_mask" model="fp.step.kind">
|
||||
<field name="code">mask</field>
|
||||
<field name="name">Masking</field>
|
||||
<field name="sequence">40</field>
|
||||
<field name="icon">fa-eye-slash</field>
|
||||
</record>
|
||||
<record id="step_kind_cleaning" model="fp.step.kind">
|
||||
<field name="code">cleaning</field>
|
||||
<field name="name">Cleaning</field>
|
||||
<field name="sequence">50</field>
|
||||
<field name="icon">fa-tint</field>
|
||||
</record>
|
||||
<record id="step_kind_electroclean" model="fp.step.kind">
|
||||
<field name="code">electroclean</field>
|
||||
<field name="name">Electroclean</field>
|
||||
<field name="sequence">60</field>
|
||||
<field name="icon">fa-bolt</field>
|
||||
</record>
|
||||
<record id="step_kind_etch" model="fp.step.kind">
|
||||
<field name="code">etch</field>
|
||||
<field name="name">Etch / Activation</field>
|
||||
<field name="sequence">70</field>
|
||||
<field name="icon">fa-flask</field>
|
||||
</record>
|
||||
<record id="step_kind_rinse" model="fp.step.kind">
|
||||
<field name="code">rinse</field>
|
||||
<field name="name">Rinse</field>
|
||||
<field name="sequence">80</field>
|
||||
<field name="icon">fa-tint</field>
|
||||
</record>
|
||||
<record id="step_kind_strike" model="fp.step.kind">
|
||||
<field name="code">strike</field>
|
||||
<field name="name">Strike (Wood's Nickel / Activation)</field>
|
||||
<field name="sequence">90</field>
|
||||
<field name="icon">fa-bolt</field>
|
||||
</record>
|
||||
<record id="step_kind_plate" model="fp.step.kind">
|
||||
<field name="code">plate</field>
|
||||
<field name="name">Plating</field>
|
||||
<field name="sequence">100</field>
|
||||
<field name="icon">fa-shield</field>
|
||||
</record>
|
||||
<record id="step_kind_replenishment" model="fp.step.kind">
|
||||
<field name="code">replenishment</field>
|
||||
<field name="name">Tank Replenishment</field>
|
||||
<field name="sequence">110</field>
|
||||
<field name="icon">fa-plus-circle</field>
|
||||
</record>
|
||||
<record id="step_kind_wbf_test" model="fp.step.kind">
|
||||
<field name="code">wbf_test</field>
|
||||
<field name="name">Water Break Free Test</field>
|
||||
<field name="sequence">120</field>
|
||||
<field name="icon">fa-check-square-o</field>
|
||||
</record>
|
||||
<record id="step_kind_dry" model="fp.step.kind">
|
||||
<field name="code">dry</field>
|
||||
<field name="name">Drying</field>
|
||||
<field name="sequence">130</field>
|
||||
<field name="icon">fa-sun-o</field>
|
||||
</record>
|
||||
<record id="step_kind_bake" model="fp.step.kind">
|
||||
<field name="code">bake</field>
|
||||
<field name="name">Bake (HE Relief / Stress Relief)</field>
|
||||
<field name="sequence">140</field>
|
||||
<field name="icon">fa-fire</field>
|
||||
</record>
|
||||
<record id="step_kind_demask" model="fp.step.kind">
|
||||
<field name="code">demask</field>
|
||||
<field name="name">De-Masking</field>
|
||||
<field name="sequence">150</field>
|
||||
<field name="icon">fa-eye</field>
|
||||
</record>
|
||||
<record id="step_kind_derack" model="fp.step.kind">
|
||||
<field name="code">derack</field>
|
||||
<field name="name">De-Racking</field>
|
||||
<field name="sequence">160</field>
|
||||
<field name="icon">fa-server</field>
|
||||
</record>
|
||||
<record id="step_kind_inspect" model="fp.step.kind">
|
||||
<field name="code">inspect</field>
|
||||
<field name="name">Inspection</field>
|
||||
<field name="sequence">170</field>
|
||||
<field name="icon">fa-search</field>
|
||||
</record>
|
||||
<record id="step_kind_hardness_test" model="fp.step.kind">
|
||||
<field name="code">hardness_test</field>
|
||||
<field name="name">Hardness Test (HV / HK / HRC)</field>
|
||||
<field name="sequence">180</field>
|
||||
<field name="icon">fa-tachometer</field>
|
||||
</record>
|
||||
<record id="step_kind_adhesion_test" model="fp.step.kind">
|
||||
<field name="code">adhesion_test</field>
|
||||
<field name="name">Adhesion Test</field>
|
||||
<field name="sequence">190</field>
|
||||
<field name="icon">fa-link</field>
|
||||
</record>
|
||||
<record id="step_kind_salt_spray" model="fp.step.kind">
|
||||
<field name="code">salt_spray</field>
|
||||
<field name="name">Salt Spray / Corrosion Test</field>
|
||||
<field name="sequence">200</field>
|
||||
<field name="icon">fa-cloud</field>
|
||||
</record>
|
||||
<record id="step_kind_final_inspect" model="fp.step.kind">
|
||||
<field name="code">final_inspect</field>
|
||||
<field name="name">Final Inspection</field>
|
||||
<field name="sequence">210</field>
|
||||
<field name="icon">fa-check-circle</field>
|
||||
</record>
|
||||
<record id="step_kind_packaging" model="fp.step.kind">
|
||||
<field name="code">packaging</field>
|
||||
<field name="name">Packaging / Pre-Ship</field>
|
||||
<field name="sequence">220</field>
|
||||
<field name="icon">fa-archive</field>
|
||||
</record>
|
||||
<record id="step_kind_ship" model="fp.step.kind">
|
||||
<field name="code">ship</field>
|
||||
<field name="name">Shipping</field>
|
||||
<field name="sequence">230</field>
|
||||
<field name="icon">fa-paper-plane</field>
|
||||
</record>
|
||||
<record id="step_kind_gating" model="fp.step.kind">
|
||||
<field name="code">gating</field>
|
||||
<field name="name">Gating</field>
|
||||
<field name="sequence">240</field>
|
||||
<field name="icon">fa-pause-circle</field>
|
||||
</record>
|
||||
|
||||
<!-- ============================================================
|
||||
Default inputs per kind — 1:1 port of DEFAULT_INPUTS_BY_KIND
|
||||
============================================================ -->
|
||||
|
||||
<!-- receiving -->
|
||||
<record id="step_kind_input_receiving_qty_received" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_receiving"/>
|
||||
<field name="name">Qty Received</field>
|
||||
<field name="input_type">number</field>
|
||||
<field name="target_unit">each</field>
|
||||
<field name="sequence">10</field>
|
||||
<field name="required">True</field>
|
||||
</record>
|
||||
<record id="step_kind_input_receiving_qty_rejected" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_receiving"/>
|
||||
<field name="name">Qty Rejected</field>
|
||||
<field name="input_type">number</field>
|
||||
<field name="target_unit">each</field>
|
||||
<field name="sequence">20</field>
|
||||
</record>
|
||||
<record id="step_kind_input_receiving_po_verified" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_receiving"/>
|
||||
<field name="name">Customer PO# Verified</field>
|
||||
<field name="input_type">boolean</field>
|
||||
<field name="sequence">30</field>
|
||||
</record>
|
||||
<record id="step_kind_input_receiving_packing_slip" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_receiving"/>
|
||||
<field name="name">Packing Slip #</field>
|
||||
<field name="input_type">text</field>
|
||||
<field name="sequence">40</field>
|
||||
</record>
|
||||
<record id="step_kind_input_receiving_condition" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_receiving"/>
|
||||
<field name="name">Condition Notes</field>
|
||||
<field name="input_type">text</field>
|
||||
<field name="sequence">50</field>
|
||||
</record>
|
||||
<record id="step_kind_input_receiving_damage_photo" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_receiving"/>
|
||||
<field name="name">Damage Photo</field>
|
||||
<field name="input_type">photo</field>
|
||||
<field name="sequence">60</field>
|
||||
</record>
|
||||
<record id="step_kind_input_receiving_inspector_init" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_receiving"/>
|
||||
<field name="name">Inspector Initials</field>
|
||||
<field name="input_type">signature</field>
|
||||
<field name="sequence">70</field>
|
||||
<field name="required">True</field>
|
||||
</record>
|
||||
|
||||
<!-- cleaning -->
|
||||
<record id="step_kind_input_cleaning_actual_time" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_cleaning"/>
|
||||
<field name="name">Actual Time</field>
|
||||
<field name="input_type">time_seconds</field>
|
||||
<field name="target_unit">s</field>
|
||||
<field name="sequence">10</field>
|
||||
</record>
|
||||
<record id="step_kind_input_cleaning_actual_temp" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_cleaning"/>
|
||||
<field name="name">Actual Temperature</field>
|
||||
<field name="input_type">temperature</field>
|
||||
<field name="target_unit">f</field>
|
||||
<field name="sequence">20</field>
|
||||
</record>
|
||||
<record id="step_kind_input_cleaning_bath_id" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_cleaning"/>
|
||||
<field name="name">Bath ID</field>
|
||||
<field name="input_type">text</field>
|
||||
<field name="sequence">30</field>
|
||||
</record>
|
||||
<record id="step_kind_input_cleaning_us_on" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_cleaning"/>
|
||||
<field name="name">Ultrasonic On</field>
|
||||
<field name="input_type">boolean</field>
|
||||
<field name="sequence">40</field>
|
||||
</record>
|
||||
<record id="step_kind_input_cleaning_titration" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_cleaning"/>
|
||||
<field name="name">Titration Done</field>
|
||||
<field name="input_type">boolean</field>
|
||||
<field name="sequence">50</field>
|
||||
</record>
|
||||
|
||||
<!-- electroclean -->
|
||||
<record id="step_kind_input_eclean_time" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_electroclean"/>
|
||||
<field name="name">Actual Time</field>
|
||||
<field name="input_type">time_seconds</field>
|
||||
<field name="target_unit">s</field>
|
||||
<field name="sequence">10</field>
|
||||
</record>
|
||||
<record id="step_kind_input_eclean_temp" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_electroclean"/>
|
||||
<field name="name">Actual Temperature</field>
|
||||
<field name="input_type">temperature</field>
|
||||
<field name="target_unit">f</field>
|
||||
<field name="sequence">20</field>
|
||||
</record>
|
||||
<record id="step_kind_input_eclean_amp" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_electroclean"/>
|
||||
<field name="name">Amperage</field>
|
||||
<field name="input_type">number</field>
|
||||
<field name="hint">A</field>
|
||||
<field name="sequence">30</field>
|
||||
</record>
|
||||
<record id="step_kind_input_eclean_volt" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_electroclean"/>
|
||||
<field name="name">Voltage</field>
|
||||
<field name="input_type">number</field>
|
||||
<field name="hint">V</field>
|
||||
<field name="sequence">40</field>
|
||||
</record>
|
||||
<record id="step_kind_input_eclean_cd" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_electroclean"/>
|
||||
<field name="name">Current Density</field>
|
||||
<field name="input_type">number</field>
|
||||
<field name="hint">ASF (A per sq ft)</field>
|
||||
<field name="sequence">50</field>
|
||||
</record>
|
||||
<record id="step_kind_input_eclean_pol" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_electroclean"/>
|
||||
<field name="name">Polarity</field>
|
||||
<field name="input_type">selection</field>
|
||||
<field name="selection_options">anodic,cathodic,periodic</field>
|
||||
<field name="sequence">60</field>
|
||||
</record>
|
||||
<record id="step_kind_input_eclean_bath" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_electroclean"/>
|
||||
<field name="name">Bath ID</field>
|
||||
<field name="input_type">text</field>
|
||||
<field name="sequence">70</field>
|
||||
</record>
|
||||
|
||||
<!-- etch -->
|
||||
<record id="step_kind_input_etch_time" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_etch"/>
|
||||
<field name="name">Actual Time</field>
|
||||
<field name="input_type">time_seconds</field>
|
||||
<field name="target_unit">s</field>
|
||||
<field name="sequence">10</field>
|
||||
</record>
|
||||
<record id="step_kind_input_etch_temp" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_etch"/>
|
||||
<field name="name">Actual Temperature</field>
|
||||
<field name="input_type">temperature</field>
|
||||
<field name="target_unit">f</field>
|
||||
<field name="sequence">20</field>
|
||||
</record>
|
||||
<record id="step_kind_input_etch_acid" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_etch"/>
|
||||
<field name="name">Acid Concentration</field>
|
||||
<field name="input_type">number</field>
|
||||
<field name="hint">% or g/L</field>
|
||||
<field name="sequence">30</field>
|
||||
</record>
|
||||
<record id="step_kind_input_etch_bath" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_etch"/>
|
||||
<field name="name">Bath ID</field>
|
||||
<field name="input_type">text</field>
|
||||
<field name="sequence">40</field>
|
||||
</record>
|
||||
<record id="step_kind_input_etch_he" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_etch"/>
|
||||
<field name="name">HE Risk Flag</field>
|
||||
<field name="input_type">boolean</field>
|
||||
<field name="hint">Hydrogen Embrittlement risk for high-strength steel</field>
|
||||
<field name="sequence">50</field>
|
||||
</record>
|
||||
|
||||
<!-- rinse -->
|
||||
<record id="step_kind_input_rinse_type" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_rinse"/>
|
||||
<field name="name">Rinse Type</field>
|
||||
<field name="input_type">selection</field>
|
||||
<field name="selection_options">cascade,spray,DI,city</field>
|
||||
<field name="sequence">10</field>
|
||||
</record>
|
||||
<record id="step_kind_input_rinse_cond" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_rinse"/>
|
||||
<field name="name">Conductivity</field>
|
||||
<field name="input_type">number</field>
|
||||
<field name="hint">µS/cm — required for DI rinses</field>
|
||||
<field name="sequence">20</field>
|
||||
</record>
|
||||
<record id="step_kind_input_rinse_time" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_rinse"/>
|
||||
<field name="name">Actual Time</field>
|
||||
<field name="input_type">time_seconds</field>
|
||||
<field name="target_unit">s</field>
|
||||
<field name="sequence">30</field>
|
||||
</record>
|
||||
|
||||
<!-- strike -->
|
||||
<record id="step_kind_input_strike_time" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_strike"/>
|
||||
<field name="name">Actual Time</field>
|
||||
<field name="input_type">time_seconds</field>
|
||||
<field name="target_unit">s</field>
|
||||
<field name="sequence">10</field>
|
||||
</record>
|
||||
<record id="step_kind_input_strike_temp" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_strike"/>
|
||||
<field name="name">Actual Temperature</field>
|
||||
<field name="input_type">temperature</field>
|
||||
<field name="target_unit">f</field>
|
||||
<field name="sequence">20</field>
|
||||
</record>
|
||||
<record id="step_kind_input_strike_amp" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_strike"/>
|
||||
<field name="name">Amperage</field>
|
||||
<field name="input_type">number</field>
|
||||
<field name="hint">A</field>
|
||||
<field name="sequence">30</field>
|
||||
</record>
|
||||
<record id="step_kind_input_strike_volt" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_strike"/>
|
||||
<field name="name">Voltage</field>
|
||||
<field name="input_type">number</field>
|
||||
<field name="hint">V</field>
|
||||
<field name="sequence">40</field>
|
||||
</record>
|
||||
<record id="step_kind_input_strike_cd" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_strike"/>
|
||||
<field name="name">Current Density</field>
|
||||
<field name="input_type">number</field>
|
||||
<field name="hint">ASF</field>
|
||||
<field name="sequence">50</field>
|
||||
</record>
|
||||
<record id="step_kind_input_strike_bath" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_strike"/>
|
||||
<field name="name">Bath ID</field>
|
||||
<field name="input_type">text</field>
|
||||
<field name="sequence">60</field>
|
||||
</record>
|
||||
|
||||
<!-- plate -->
|
||||
<record id="step_kind_input_plate_time" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_plate"/>
|
||||
<field name="name">Actual Time</field>
|
||||
<field name="input_type">time_hms</field>
|
||||
<field name="target_unit">min</field>
|
||||
<field name="sequence">10</field>
|
||||
</record>
|
||||
<record id="step_kind_input_plate_temp" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_plate"/>
|
||||
<field name="name">Actual Temperature</field>
|
||||
<field name="input_type">temperature</field>
|
||||
<field name="target_unit">f</field>
|
||||
<field name="sequence">20</field>
|
||||
</record>
|
||||
<record id="step_kind_input_plate_bath" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_plate"/>
|
||||
<field name="name">Bath ID</field>
|
||||
<field name="input_type">text</field>
|
||||
<field name="sequence">30</field>
|
||||
</record>
|
||||
<record id="step_kind_input_plate_ph" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_plate"/>
|
||||
<field name="name">pH</field>
|
||||
<field name="input_type">ph</field>
|
||||
<field name="sequence">40</field>
|
||||
</record>
|
||||
<record id="step_kind_input_plate_conc" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_plate"/>
|
||||
<field name="name">Bath Concentration</field>
|
||||
<field name="input_type">number</field>
|
||||
<field name="hint">g/L</field>
|
||||
<field name="sequence">50</field>
|
||||
</record>
|
||||
<record id="step_kind_input_plate_cd" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_plate"/>
|
||||
<field name="name">Current Density</field>
|
||||
<field name="input_type">number</field>
|
||||
<field name="hint">ASF — electroplate only</field>
|
||||
<field name="sequence">60</field>
|
||||
</record>
|
||||
<record id="step_kind_input_plate_thick" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_plate"/>
|
||||
<field name="name">Plating Thickness</field>
|
||||
<field name="input_type">multi_point_thickness</field>
|
||||
<field name="target_unit">in</field>
|
||||
<field name="sequence">70</field>
|
||||
</record>
|
||||
|
||||
<!-- replenishment -->
|
||||
<record id="step_kind_input_replen_bath" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_replenishment"/>
|
||||
<field name="name">Bath ID</field>
|
||||
<field name="input_type">text</field>
|
||||
<field name="sequence">10</field>
|
||||
<field name="required">True</field>
|
||||
</record>
|
||||
<record id="step_kind_input_replen_chem" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_replenishment"/>
|
||||
<field name="name">Chemistry Added</field>
|
||||
<field name="input_type">text</field>
|
||||
<field name="hint">name + amount, e.g. "Nickel sulfamate 500mL"</field>
|
||||
<field name="sequence">20</field>
|
||||
</record>
|
||||
<record id="step_kind_input_replen_ph_b" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_replenishment"/>
|
||||
<field name="name">pH Before</field>
|
||||
<field name="input_type">ph</field>
|
||||
<field name="sequence">30</field>
|
||||
</record>
|
||||
<record id="step_kind_input_replen_ph_a" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_replenishment"/>
|
||||
<field name="name">pH After</field>
|
||||
<field name="input_type">ph</field>
|
||||
<field name="sequence">40</field>
|
||||
</record>
|
||||
<record id="step_kind_input_replen_conc_b" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_replenishment"/>
|
||||
<field name="name">Concentration Before</field>
|
||||
<field name="input_type">number</field>
|
||||
<field name="sequence">50</field>
|
||||
</record>
|
||||
<record id="step_kind_input_replen_conc_a" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_replenishment"/>
|
||||
<field name="name">Concentration After</field>
|
||||
<field name="input_type">number</field>
|
||||
<field name="sequence">60</field>
|
||||
</record>
|
||||
<record id="step_kind_input_replen_op" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_replenishment"/>
|
||||
<field name="name">Operator Initials</field>
|
||||
<field name="input_type">signature</field>
|
||||
<field name="sequence">70</field>
|
||||
<field name="required">True</field>
|
||||
</record>
|
||||
|
||||
<!-- wbf_test -->
|
||||
<record id="step_kind_input_wbf_result" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_wbf_test"/>
|
||||
<field name="name">Result</field>
|
||||
<field name="input_type">pass_fail</field>
|
||||
<field name="sequence">10</field>
|
||||
<field name="required">True</field>
|
||||
</record>
|
||||
<record id="step_kind_input_wbf_retest" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_wbf_test"/>
|
||||
<field name="name">Retest Count</field>
|
||||
<field name="input_type">number</field>
|
||||
<field name="sequence">20</field>
|
||||
</record>
|
||||
<record id="step_kind_input_wbf_photo" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_wbf_test"/>
|
||||
<field name="name">Photo on FAIL</field>
|
||||
<field name="input_type">photo</field>
|
||||
<field name="sequence">30</field>
|
||||
</record>
|
||||
|
||||
<!-- dry -->
|
||||
<record id="step_kind_input_dry_method" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_dry"/>
|
||||
<field name="name">Dry Method</field>
|
||||
<field name="input_type">selection</field>
|
||||
<field name="selection_options">hot air,oven,spin</field>
|
||||
<field name="sequence">10</field>
|
||||
</record>
|
||||
<record id="step_kind_input_dry_time" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_dry"/>
|
||||
<field name="name">Actual Time</field>
|
||||
<field name="input_type">time_seconds</field>
|
||||
<field name="target_unit">s</field>
|
||||
<field name="sequence">20</field>
|
||||
</record>
|
||||
<record id="step_kind_input_dry_temp" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_dry"/>
|
||||
<field name="name">Actual Temperature</field>
|
||||
<field name="input_type">temperature</field>
|
||||
<field name="target_unit">f</field>
|
||||
<field name="sequence">30</field>
|
||||
</record>
|
||||
|
||||
<!-- bake -->
|
||||
<record id="step_kind_input_bake_in" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_bake"/>
|
||||
<field name="name">Time In</field>
|
||||
<field name="input_type">date</field>
|
||||
<field name="sequence">10</field>
|
||||
</record>
|
||||
<record id="step_kind_input_bake_out" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_bake"/>
|
||||
<field name="name">Time Out</field>
|
||||
<field name="input_type">date</field>
|
||||
<field name="sequence">20</field>
|
||||
</record>
|
||||
<record id="step_kind_input_bake_temp" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_bake"/>
|
||||
<field name="name">Actual Temperature</field>
|
||||
<field name="input_type">temperature</field>
|
||||
<field name="target_unit">f</field>
|
||||
<field name="sequence">30</field>
|
||||
</record>
|
||||
<record id="step_kind_input_bake_oven" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_bake"/>
|
||||
<field name="name">Oven ID</field>
|
||||
<field name="input_type">text</field>
|
||||
<field name="sequence">40</field>
|
||||
</record>
|
||||
<record id="step_kind_input_bake_chart" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_bake"/>
|
||||
<field name="name">Chart Recorder File</field>
|
||||
<field name="input_type">photo</field>
|
||||
<field name="hint">Attach AMS-2759 chart-recorder file</field>
|
||||
<field name="sequence">50</field>
|
||||
</record>
|
||||
|
||||
<!-- racking -->
|
||||
<record id="step_kind_input_racking_qty" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_racking"/>
|
||||
<field name="name">Actual Qty</field>
|
||||
<field name="input_type">number</field>
|
||||
<field name="target_unit">each</field>
|
||||
<field name="sequence">10</field>
|
||||
<field name="required">True</field>
|
||||
</record>
|
||||
<record id="step_kind_input_racking_rack" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_racking"/>
|
||||
<field name="name">Rack ID</field>
|
||||
<field name="input_type">text</field>
|
||||
<field name="sequence">20</field>
|
||||
</record>
|
||||
<record id="step_kind_input_racking_mask" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_racking"/>
|
||||
<field name="name">Masking Applied</field>
|
||||
<field name="input_type">boolean</field>
|
||||
<field name="sequence">30</field>
|
||||
</record>
|
||||
<record id="step_kind_input_racking_photo" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_racking"/>
|
||||
<field name="name">Photo of Racked Load</field>
|
||||
<field name="input_type">photo</field>
|
||||
<field name="sequence">40</field>
|
||||
</record>
|
||||
|
||||
<!-- derack -->
|
||||
<record id="step_kind_input_derack_qty" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_derack"/>
|
||||
<field name="name">Actual Qty</field>
|
||||
<field name="input_type">number</field>
|
||||
<field name="target_unit">each</field>
|
||||
<field name="sequence">10</field>
|
||||
</record>
|
||||
<record id="step_kind_input_derack_method" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_derack"/>
|
||||
<field name="name">Mask Removal Method</field>
|
||||
<field name="input_type">selection</field>
|
||||
<field name="selection_options">mechanical,solvent,thermal,not applicable</field>
|
||||
<field name="sequence">20</field>
|
||||
</record>
|
||||
<record id="step_kind_input_derack_residue" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_derack"/>
|
||||
<field name="name">Residue Check</field>
|
||||
<field name="input_type">pass_fail</field>
|
||||
<field name="sequence">30</field>
|
||||
</record>
|
||||
|
||||
<!-- mask -->
|
||||
<record id="step_kind_input_mask_qty" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_mask"/>
|
||||
<field name="name">Actual Qty</field>
|
||||
<field name="input_type">number</field>
|
||||
<field name="target_unit">each</field>
|
||||
<field name="sequence">10</field>
|
||||
</record>
|
||||
<record id="step_kind_input_mask_material" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_mask"/>
|
||||
<field name="name">Mask Material</field>
|
||||
<field name="input_type">selection</field>
|
||||
<field name="selection_options">Microshield,latex tape,vinyl plugs,wax,other</field>
|
||||
<field name="sequence">20</field>
|
||||
</record>
|
||||
<record id="step_kind_input_mask_photo" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_mask"/>
|
||||
<field name="name">Photo of Masked Parts</field>
|
||||
<field name="input_type">photo</field>
|
||||
<field name="sequence">30</field>
|
||||
</record>
|
||||
|
||||
<!-- demask -->
|
||||
<record id="step_kind_input_demask_residue" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_demask"/>
|
||||
<field name="name">Residue Check</field>
|
||||
<field name="input_type">pass_fail</field>
|
||||
<field name="sequence">10</field>
|
||||
</record>
|
||||
<record id="step_kind_input_demask_surface" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_demask"/>
|
||||
<field name="name">Surface Condition</field>
|
||||
<field name="input_type">selection</field>
|
||||
<field name="selection_options">clean,marks,needs rework</field>
|
||||
<field name="sequence">20</field>
|
||||
</record>
|
||||
|
||||
<!-- inspect -->
|
||||
<record id="step_kind_input_inspect_result" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_inspect"/>
|
||||
<field name="name">Result</field>
|
||||
<field name="input_type">pass_fail</field>
|
||||
<field name="sequence">10</field>
|
||||
<field name="required">True</field>
|
||||
</record>
|
||||
<record id="step_kind_input_inspect_defect" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_inspect"/>
|
||||
<field name="name">Defect Type</field>
|
||||
<field name="input_type">selection</field>
|
||||
<field name="selection_options">pitting,burn,blister,peel,missing coverage,none</field>
|
||||
<field name="sequence">20</field>
|
||||
</record>
|
||||
<record id="step_kind_input_inspect_thick" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_inspect"/>
|
||||
<field name="name">Thickness Sample</field>
|
||||
<field name="input_type">thickness</field>
|
||||
<field name="target_unit">in</field>
|
||||
<field name="sequence">30</field>
|
||||
</record>
|
||||
<record id="step_kind_input_inspect_photo" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_inspect"/>
|
||||
<field name="name">Photo</field>
|
||||
<field name="input_type">photo</field>
|
||||
<field name="sequence">40</field>
|
||||
</record>
|
||||
<record id="step_kind_input_inspect_sig" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_inspect"/>
|
||||
<field name="name">Inspector Signature</field>
|
||||
<field name="input_type">signature</field>
|
||||
<field name="sequence">50</field>
|
||||
</record>
|
||||
|
||||
<!-- hardness_test -->
|
||||
<record id="step_kind_input_hard_load" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_hardness_test"/>
|
||||
<field name="name">Test Load</field>
|
||||
<field name="input_type">number</field>
|
||||
<field name="hint">gf</field>
|
||||
<field name="sequence">10</field>
|
||||
</record>
|
||||
<record id="step_kind_input_hard_readings" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_hardness_test"/>
|
||||
<field name="name">Readings (HV/HK/HRC)</field>
|
||||
<field name="input_type">multi_point_thickness</field>
|
||||
<field name="hint">Three indents minimum</field>
|
||||
<field name="sequence">20</field>
|
||||
</record>
|
||||
<record id="step_kind_input_hard_eq" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_hardness_test"/>
|
||||
<field name="name">Equipment ID</field>
|
||||
<field name="input_type">text</field>
|
||||
<field name="sequence">30</field>
|
||||
</record>
|
||||
<record id="step_kind_input_hard_cal" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_hardness_test"/>
|
||||
<field name="name">Last Calibration Date</field>
|
||||
<field name="input_type">date</field>
|
||||
<field name="sequence">40</field>
|
||||
</record>
|
||||
|
||||
<!-- adhesion_test -->
|
||||
<record id="step_kind_input_adh_method" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_adhesion_test"/>
|
||||
<field name="name">Test Method</field>
|
||||
<field name="input_type">selection</field>
|
||||
<field name="selection_options">bend,tape,burnish,file</field>
|
||||
<field name="sequence">10</field>
|
||||
</record>
|
||||
<record id="step_kind_input_adh_result" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_adhesion_test"/>
|
||||
<field name="name">Result</field>
|
||||
<field name="input_type">pass_fail</field>
|
||||
<field name="sequence">20</field>
|
||||
<field name="required">True</field>
|
||||
</record>
|
||||
<record id="step_kind_input_adh_photo" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_adhesion_test"/>
|
||||
<field name="name">Photo of Coupon</field>
|
||||
<field name="input_type">photo</field>
|
||||
<field name="sequence">30</field>
|
||||
</record>
|
||||
|
||||
<!-- salt_spray -->
|
||||
<record id="step_kind_input_salt_dur" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_salt_spray"/>
|
||||
<field name="name">Test Duration</field>
|
||||
<field name="input_type">number</field>
|
||||
<field name="hint">hours</field>
|
||||
<field name="sequence">10</field>
|
||||
</record>
|
||||
<record id="step_kind_input_salt_result" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_salt_spray"/>
|
||||
<field name="name">Result</field>
|
||||
<field name="input_type">pass_fail</field>
|
||||
<field name="sequence">20</field>
|
||||
<field name="required">True</field>
|
||||
</record>
|
||||
<record id="step_kind_input_salt_red" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_salt_spray"/>
|
||||
<field name="name">Red Rust %</field>
|
||||
<field name="input_type">number</field>
|
||||
<field name="sequence">30</field>
|
||||
</record>
|
||||
<record id="step_kind_input_salt_white" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_salt_spray"/>
|
||||
<field name="name">White Corrosion %</field>
|
||||
<field name="input_type">number</field>
|
||||
<field name="sequence">40</field>
|
||||
</record>
|
||||
<record id="step_kind_input_salt_lab" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_salt_spray"/>
|
||||
<field name="name">Lab Report</field>
|
||||
<field name="input_type">photo</field>
|
||||
<field name="hint">Attach scanned lab report</field>
|
||||
<field name="sequence">50</field>
|
||||
</record>
|
||||
|
||||
<!-- final_inspect -->
|
||||
<record id="step_kind_input_fin_count" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_final_inspect"/>
|
||||
<field name="name">Outgoing Part Count Verified</field>
|
||||
<field name="input_type">boolean</field>
|
||||
<field name="sequence">10</field>
|
||||
</record>
|
||||
<record id="step_kind_input_fin_qty_acc" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_final_inspect"/>
|
||||
<field name="name">Qty Accepted</field>
|
||||
<field name="input_type">number</field>
|
||||
<field name="target_unit">each</field>
|
||||
<field name="sequence">20</field>
|
||||
</record>
|
||||
<record id="step_kind_input_fin_qty_rej" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_final_inspect"/>
|
||||
<field name="name">Qty Rejected</field>
|
||||
<field name="input_type">number</field>
|
||||
<field name="target_unit">each</field>
|
||||
<field name="sequence">30</field>
|
||||
</record>
|
||||
<record id="step_kind_input_fin_defect" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_final_inspect"/>
|
||||
<field name="name">Defect Categorization</field>
|
||||
<field name="input_type">selection</field>
|
||||
<field name="selection_options">pitting,burn,blister,peel,missing coverage,dimensional,none</field>
|
||||
<field name="sequence">35</field>
|
||||
</record>
|
||||
<record id="step_kind_input_fin_thick" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_final_inspect"/>
|
||||
<field name="name">Actual Coating Thickness</field>
|
||||
<field name="input_type">multi_point_thickness</field>
|
||||
<field name="target_unit">in</field>
|
||||
<field name="sequence">40</field>
|
||||
</record>
|
||||
<record id="step_kind_input_fin_dim" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_final_inspect"/>
|
||||
<field name="name">Dimensional Verification</field>
|
||||
<field name="input_type">pass_fail</field>
|
||||
<field name="sequence">45</field>
|
||||
</record>
|
||||
<record id="step_kind_input_fin_ra" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_final_inspect"/>
|
||||
<field name="name">Surface Finish (Ra)</field>
|
||||
<field name="input_type">number</field>
|
||||
<field name="hint">µin</field>
|
||||
<field name="sequence">47</field>
|
||||
</record>
|
||||
<record id="step_kind_input_fin_pass" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_final_inspect"/>
|
||||
<field name="name">Pass/Fail</field>
|
||||
<field name="input_type">pass_fail</field>
|
||||
<field name="sequence">50</field>
|
||||
<field name="required">True</field>
|
||||
</record>
|
||||
<record id="step_kind_input_fin_sig" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_final_inspect"/>
|
||||
<field name="name">Inspector Signature</field>
|
||||
<field name="input_type">signature</field>
|
||||
<field name="sequence">60</field>
|
||||
</record>
|
||||
|
||||
<!-- packaging -->
|
||||
<record id="step_kind_input_pkg_type" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_packaging"/>
|
||||
<field name="name">Packaging Type</field>
|
||||
<field name="input_type">selection</field>
|
||||
<field name="selection_options">VCI bag,bubble wrap,separator paper,custom crate,other</field>
|
||||
<field name="sequence">10</field>
|
||||
</record>
|
||||
<record id="step_kind_input_pkg_qty" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_packaging"/>
|
||||
<field name="name">Qty Per Package</field>
|
||||
<field name="input_type">number</field>
|
||||
<field name="target_unit">each</field>
|
||||
<field name="sequence">20</field>
|
||||
</record>
|
||||
<record id="step_kind_input_pkg_count" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_packaging"/>
|
||||
<field name="name">Package Count</field>
|
||||
<field name="input_type">number</field>
|
||||
<field name="sequence">30</field>
|
||||
</record>
|
||||
<record id="step_kind_input_pkg_cert" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_packaging"/>
|
||||
<field name="name">Cert Package Included</field>
|
||||
<field name="input_type">boolean</field>
|
||||
<field name="sequence">40</field>
|
||||
</record>
|
||||
<record id="step_kind_input_pkg_cust" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_packaging"/>
|
||||
<field name="name">Customer-Supplied Packaging</field>
|
||||
<field name="input_type">boolean</field>
|
||||
<field name="sequence">50</field>
|
||||
</record>
|
||||
|
||||
<!-- ship -->
|
||||
<record id="step_kind_input_ship_qty" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_ship"/>
|
||||
<field name="name">Outgoing Qty</field>
|
||||
<field name="input_type">number</field>
|
||||
<field name="target_unit">each</field>
|
||||
<field name="sequence">10</field>
|
||||
<field name="required">True</field>
|
||||
</record>
|
||||
<record id="step_kind_input_ship_carrier" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_ship"/>
|
||||
<field name="name">Carrier</field>
|
||||
<field name="input_type">selection</field>
|
||||
<field name="selection_options">UPS,FedEx,Purolator,Customer Pickup,Other</field>
|
||||
<field name="sequence">20</field>
|
||||
</record>
|
||||
<record id="step_kind_input_ship_track" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_ship"/>
|
||||
<field name="name">Tracking #</field>
|
||||
<field name="input_type">text</field>
|
||||
<field name="sequence">30</field>
|
||||
</record>
|
||||
<record id="step_kind_input_ship_bol" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_ship"/>
|
||||
<field name="name">BoL #</field>
|
||||
<field name="input_type">text</field>
|
||||
<field name="sequence">40</field>
|
||||
</record>
|
||||
<record id="step_kind_input_ship_photo" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_ship"/>
|
||||
<field name="name">Photo of Sealed Shipment</field>
|
||||
<field name="input_type">photo</field>
|
||||
<field name="sequence">50</field>
|
||||
</record>
|
||||
|
||||
<!-- contract_review -->
|
||||
<record id="step_kind_input_cr_init" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_contract_review"/>
|
||||
<field name="name">Reviewer Initials</field>
|
||||
<field name="input_type">signature</field>
|
||||
<field name="sequence">10</field>
|
||||
</record>
|
||||
<record id="step_kind_input_cr_date" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_contract_review"/>
|
||||
<field name="name">Date Reviewed</field>
|
||||
<field name="input_type">date</field>
|
||||
<field name="sequence">20</field>
|
||||
</record>
|
||||
<record id="step_kind_input_cr_qa" model="fp.step.kind.default.input">
|
||||
<field name="kind_id" ref="step_kind_contract_review"/>
|
||||
<field name="name">QA-005 Approved</field>
|
||||
<field name="input_type">pass_fail</field>
|
||||
<field name="sequence">30</field>
|
||||
</record>
|
||||
|
||||
<!-- gating: intentionally no default inputs -->
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -13,7 +13,7 @@
|
||||
<record id="fp_step_template_receiving_std" model="fp.step.template">
|
||||
<field name="name">Incoming Inspection (Standard)</field>
|
||||
<field name="code">RECV_STD</field>
|
||||
<field name="default_kind">receiving</field>
|
||||
<field name="kind_id" ref="step_kind_receiving"/>
|
||||
<field name="icon">fa-inbox</field>
|
||||
<field name="description"><![CDATA[
|
||||
<p>Verify quantity received against packing slip. Visually inspect
|
||||
@@ -25,7 +25,7 @@
|
||||
<record id="fp_step_template_electroclean_std" model="fp.step.template">
|
||||
<field name="name">Electroclean (Standard)</field>
|
||||
<field name="code">ELEC_CLEAN_STD</field>
|
||||
<field name="default_kind">electroclean</field>
|
||||
<field name="kind_id" ref="step_kind_electroclean"/>
|
||||
<field name="icon">fa-bolt</field>
|
||||
<field name="description"><![CDATA[
|
||||
<p>Submerge rack and energise. Record actual amperage, voltage,
|
||||
@@ -36,7 +36,7 @@
|
||||
<record id="fp_step_template_strike_std" model="fp.step.template">
|
||||
<field name="name">Wood's Nickel Strike (Standard)</field>
|
||||
<field name="code">STRIKE_STD</field>
|
||||
<field name="default_kind">strike</field>
|
||||
<field name="kind_id" ref="step_kind_strike"/>
|
||||
<field name="icon">fa-flash</field>
|
||||
<field name="description"><![CDATA[
|
||||
<p>Apply thin nickel strike to ensure adhesion before main plate.
|
||||
@@ -47,7 +47,7 @@
|
||||
<record id="fp_step_template_salt_spray_std" model="fp.step.template">
|
||||
<field name="name">Salt Spray Test (ASTM B117)</field>
|
||||
<field name="code">SALT_SPRAY_STD</field>
|
||||
<field name="default_kind">salt_spray</field>
|
||||
<field name="kind_id" ref="step_kind_salt_spray"/>
|
||||
<field name="icon">fa-tint</field>
|
||||
<field name="description"><![CDATA[
|
||||
<p>Submit test panel to salt spray cabinet for the specified
|
||||
@@ -59,7 +59,7 @@
|
||||
<record id="fp_step_template_adhesion_std" model="fp.step.template">
|
||||
<field name="name">Adhesion Test (Bend / Tape)</field>
|
||||
<field name="code">ADHESION_STD</field>
|
||||
<field name="default_kind">adhesion_test</field>
|
||||
<field name="kind_id" ref="step_kind_adhesion_test"/>
|
||||
<field name="icon">fa-link</field>
|
||||
<field name="description"><![CDATA[
|
||||
<p>Perform adhesion test per spec (bend, tape, burnish, or file).
|
||||
@@ -70,7 +70,7 @@
|
||||
<record id="fp_step_template_hardness_std" model="fp.step.template">
|
||||
<field name="name">Microhardness Test</field>
|
||||
<field name="code">HARDNESS_STD</field>
|
||||
<field name="default_kind">hardness_test</field>
|
||||
<field name="kind_id" ref="step_kind_hardness_test"/>
|
||||
<field name="icon">fa-cube</field>
|
||||
<field name="description"><![CDATA[
|
||||
<p>Take three indentations minimum on the test coupon. Record
|
||||
@@ -82,7 +82,7 @@
|
||||
<record id="fp_step_template_packaging_std" model="fp.step.template">
|
||||
<field name="name">Packaging (Standard)</field>
|
||||
<field name="code">PKG_STD</field>
|
||||
<field name="default_kind">packaging</field>
|
||||
<field name="kind_id" ref="step_kind_packaging"/>
|
||||
<field name="icon">fa-archive</field>
|
||||
<field name="description"><![CDATA[
|
||||
<p>Wrap parts per customer spec (VCI bag, bubble wrap, separator
|
||||
@@ -94,7 +94,7 @@
|
||||
<record id="fp_step_template_replenishment_std" model="fp.step.template">
|
||||
<field name="name">Tank Replenishment</field>
|
||||
<field name="code">REPL_STD</field>
|
||||
<field name="default_kind">replenishment</field>
|
||||
<field name="kind_id" ref="step_kind_replenishment"/>
|
||||
<field name="icon">fa-flask</field>
|
||||
<field name="description"><![CDATA[
|
||||
<p>Mid-shift bath top-up. Record bath ID, chemistry added (name
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2026 Nexa Systems Inc.
|
||||
# License OPL-1 (Odoo Proprietary License v1.0)
|
||||
# Part of the Fusion Plating product family.
|
||||
"""19.0.18.13.0 — Backfill kind_id from legacy default_kind text values.
|
||||
|
||||
Sub 14b — `default_kind` was a Selection on fp.step.template and
|
||||
fusion.plating.process.node. It is now a stored related Char that
|
||||
reads from kind_id.code on a new fp.step.kind catalog.
|
||||
|
||||
When a Selection field is converted into a Many2one in code, Odoo's
|
||||
ORM does NOT auto-migrate the data — the new column starts empty. This
|
||||
script reads the (still-present) text values out of the OLD `default_kind`
|
||||
column and points kind_id at the seeded fp.step.kind record whose code
|
||||
matches.
|
||||
|
||||
Idempotent — running it twice is a no-op.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def migrate(cr, version):
|
||||
"""Run by the Odoo migration framework on -u to 19.0.18.13.0."""
|
||||
# ---- Build code → kind_id map from seeded data --------------------
|
||||
cr.execute("SELECT id, code FROM fp_step_kind")
|
||||
code_to_id = {code: kid for kid, code in cr.fetchall()}
|
||||
if not code_to_id:
|
||||
_logger.warning(
|
||||
'19.0.18.13.0: fp_step_kind is empty — seed data did not '
|
||||
'load before post-migrate. Skipping backfill.',
|
||||
)
|
||||
return
|
||||
|
||||
# ---- 1. fp.step.template.kind_id ----------------------------------
|
||||
_backfill(
|
||||
cr,
|
||||
table='fp_step_template',
|
||||
text_col='x_default_kind_legacy',
|
||||
m2o_col='kind_id',
|
||||
code_to_id=code_to_id,
|
||||
)
|
||||
|
||||
# ---- 2. fusion.plating.process.node.kind_id -----------------------
|
||||
_backfill(
|
||||
cr,
|
||||
table='fusion_plating_process_node',
|
||||
text_col='x_default_kind_legacy',
|
||||
m2o_col='kind_id',
|
||||
code_to_id=code_to_id,
|
||||
)
|
||||
|
||||
# ---- 3. Force recompute of stored related default_kind --------------
|
||||
# Now that kind_id is populated, the related Char will read the right
|
||||
# value on next access. Trigger an explicit recompute via SQL so the
|
||||
# column reflects current state immediately (faster than waiting for
|
||||
# ORM to do it lazily on each row).
|
||||
for table in ('fp_step_template', 'fusion_plating_process_node'):
|
||||
cr.execute(f"""
|
||||
UPDATE {table} t
|
||||
SET default_kind = k.code
|
||||
FROM fp_step_kind k
|
||||
WHERE t.kind_id = k.id
|
||||
""")
|
||||
_logger.info(
|
||||
'19.0.18.13.0: synced default_kind from kind_id.code on '
|
||||
'%s rows of %s', cr.rowcount, table,
|
||||
)
|
||||
|
||||
|
||||
def _backfill(cr, table, text_col, m2o_col, code_to_id):
|
||||
"""Generic backfill helper. Reads `text_col` text values, updates
|
||||
`m2o_col` per row using the supplied code→id lookup table.
|
||||
"""
|
||||
# Defensive — if either column is missing (fresh install, never had
|
||||
# default_kind data) skip silently.
|
||||
cr.execute("""
|
||||
SELECT column_name FROM information_schema.columns
|
||||
WHERE table_name = %s AND column_name IN (%s, %s)
|
||||
""", (table, text_col, m2o_col))
|
||||
cols = {row[0] for row in cr.fetchall()}
|
||||
if text_col not in cols or m2o_col not in cols:
|
||||
_logger.info(
|
||||
'19.0.18.13.0: %s missing %s or %s, skipping backfill',
|
||||
table, text_col, m2o_col,
|
||||
)
|
||||
return
|
||||
|
||||
# Pull every row with a non-null default_kind that doesn't yet have
|
||||
# a kind_id — leaves manually-edited rows alone if migration is
|
||||
# rerun.
|
||||
cr.execute(f"""
|
||||
SELECT id, {text_col} FROM {table}
|
||||
WHERE {text_col} IS NOT NULL AND {m2o_col} IS NULL
|
||||
""")
|
||||
rows = cr.fetchall()
|
||||
if not rows:
|
||||
_logger.info(
|
||||
'19.0.18.13.0: %s has no rows needing backfill', table,
|
||||
)
|
||||
return
|
||||
|
||||
updated = 0
|
||||
skipped = 0
|
||||
by_kind = {}
|
||||
for rec_id, code in rows:
|
||||
kid = code_to_id.get(code)
|
||||
if not kid:
|
||||
skipped += 1
|
||||
continue
|
||||
by_kind.setdefault(kid, []).append(rec_id)
|
||||
|
||||
for kid, ids in by_kind.items():
|
||||
cr.execute(
|
||||
f"UPDATE {table} SET {m2o_col} = %s WHERE id = ANY(%s)",
|
||||
(kid, ids),
|
||||
)
|
||||
updated += len(ids)
|
||||
|
||||
_logger.info(
|
||||
'19.0.18.13.0: backfilled %s.%s on %s rows (%s skipped — code '
|
||||
'not found in fp_step_kind seed data)',
|
||||
table, m2o_col, updated, skipped,
|
||||
)
|
||||
@@ -0,0 +1,57 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2026 Nexa Systems Inc.
|
||||
# License OPL-1 (Odoo Proprietary License v1.0)
|
||||
# Part of the Fusion Plating product family.
|
||||
"""19.0.18.13.0 — Snapshot legacy default_kind values BEFORE the field
|
||||
type change wipes them.
|
||||
|
||||
Sub 14b — `default_kind` was a Selection on fp.step.template and
|
||||
fusion.plating.process.node. The new model code defines it as a
|
||||
stored related Char (`related='kind_id.code', store=True`). On first
|
||||
ORM access after upgrade, Odoo recomputes the stored related from
|
||||
kind_id (which is NULL → wipes the column). We must snapshot the
|
||||
original text values now, so post-migrate can read them and resolve
|
||||
kind_id.
|
||||
|
||||
Idempotent — running it twice is a no-op (snapshot column gets
|
||||
re-written with the same data).
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def migrate(cr, version):
|
||||
for table in ('fp_step_template', 'fusion_plating_process_node'):
|
||||
_snapshot(cr, table)
|
||||
|
||||
|
||||
def _snapshot(cr, table):
|
||||
cr.execute("""
|
||||
SELECT column_name FROM information_schema.columns
|
||||
WHERE table_name = %s AND column_name = 'default_kind'
|
||||
""", (table,))
|
||||
if not cr.fetchone():
|
||||
_logger.info(
|
||||
'19.0.18.13.0/pre: %s has no default_kind column, skip',
|
||||
table,
|
||||
)
|
||||
return
|
||||
|
||||
# Add the snapshot column if missing.
|
||||
cr.execute(f"""
|
||||
ALTER TABLE {table}
|
||||
ADD COLUMN IF NOT EXISTS x_default_kind_legacy VARCHAR
|
||||
""")
|
||||
# Copy the live data.
|
||||
cr.execute(f"""
|
||||
UPDATE {table}
|
||||
SET x_default_kind_legacy = default_kind
|
||||
WHERE default_kind IS NOT NULL
|
||||
""")
|
||||
_logger.info(
|
||||
'19.0.18.13.0/pre: snapshotted %s rows from %s.default_kind '
|
||||
'to x_default_kind_legacy',
|
||||
cr.rowcount, table,
|
||||
)
|
||||
@@ -36,6 +36,7 @@ from . import hr_employee
|
||||
from . import fp_process_node_inherit
|
||||
|
||||
# Sub 12a — Simple Recipe Editor + Step Library
|
||||
from . import fp_step_kind # MUST load before fp_step_template (dependency)
|
||||
from . import fp_step_template
|
||||
from . import fp_step_template_input
|
||||
from . import fp_step_template_transition_input
|
||||
|
||||
@@ -373,34 +373,16 @@ class FpProcessNode(models.Model):
|
||||
string='Requires Transition Form',
|
||||
help='Sub 12b — opens the transition form before Mark Done.',
|
||||
)
|
||||
default_kind = fields.Selection(
|
||||
[
|
||||
('receiving', 'Receiving / Incoming Inspection'),
|
||||
('contract_review', 'Contract Review (QA-005)'),
|
||||
('racking', 'Racking'),
|
||||
('mask', 'Masking'),
|
||||
('cleaning', 'Cleaning'),
|
||||
('electroclean', 'Electroclean'),
|
||||
('etch', 'Etch / Activation'),
|
||||
('rinse', 'Rinse'),
|
||||
('strike', 'Strike (Wood\'s Nickel / Activation)'),
|
||||
('plate', 'Plating'),
|
||||
('replenishment', 'Tank Replenishment'),
|
||||
('wbf_test', 'Water Break Free Test'),
|
||||
('dry', 'Drying'),
|
||||
('bake', 'Bake (HE Relief / Stress Relief)'),
|
||||
('demask', 'De-Masking'),
|
||||
('derack', 'De-Racking'),
|
||||
('inspect', 'Inspection'),
|
||||
('hardness_test', 'Hardness Test (HV / HK / HRC)'),
|
||||
('adhesion_test', 'Adhesion Test'),
|
||||
('salt_spray', 'Salt Spray / Corrosion Test'),
|
||||
('final_inspect', 'Final Inspection'),
|
||||
('packaging', 'Packaging / Pre-Ship'),
|
||||
('ship', 'Shipping'),
|
||||
('gating', 'Gating'),
|
||||
],
|
||||
string='Step Kind',
|
||||
# Sub 14b — User-extensible Step Kinds (was Selection of 24).
|
||||
kind_id = fields.Many2one(
|
||||
'fp.step.kind', string='Step Kind', ondelete='set null', index=True,
|
||||
help='Pick from the catalog or create a new kind.',
|
||||
)
|
||||
# Back-compat: code-string accessor that all legacy
|
||||
# `node.default_kind == "cleaning"` comparisons keep using.
|
||||
default_kind = fields.Char(
|
||||
related='kind_id.code', store=True, readonly=True, index=True,
|
||||
string='Step Kind Code',
|
||||
)
|
||||
preferred_editor = fields.Selection(
|
||||
[
|
||||
|
||||
282
fusion_plating/fusion_plating/models/fp_step_kind.py
Normal file
282
fusion_plating/fusion_plating/models/fp_step_kind.py
Normal file
@@ -0,0 +1,282 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2026 Nexa Systems Inc.
|
||||
# License OPL-1 (Odoo Proprietary License v1.0)
|
||||
# Part of the Fusion Plating product family.
|
||||
|
||||
from odoo import _, api, fields, models
|
||||
|
||||
from ._fp_uom_selection import FP_UOM_SELECTION
|
||||
|
||||
|
||||
class FpStepKind(models.Model):
|
||||
"""User-extensible Step Kind catalog.
|
||||
|
||||
Replaces the hardcoded `default_kind` Selection on fp.step.template
|
||||
and fusion.plating.process.node. Each kind carries a list of default
|
||||
inputs that get seeded onto a step template when the kind is picked.
|
||||
"""
|
||||
_name = 'fp.step.kind'
|
||||
_description = 'Fusion Plating — Step Kind'
|
||||
_order = 'sequence, name'
|
||||
|
||||
code = fields.Char(
|
||||
string='Code', required=True, index=True,
|
||||
help='Stable lowercase technical key. Used by automation rules and '
|
||||
'workflow-state triggers (e.g. "cleaning", "plate"). Lower-case'
|
||||
' enforced; underscores allowed.',
|
||||
)
|
||||
name = fields.Char(string='Name', required=True, translate=True)
|
||||
sequence = fields.Integer(string='Sequence', default=10)
|
||||
active = fields.Boolean(string='Active', default=True)
|
||||
description = fields.Html(string='Description')
|
||||
icon = fields.Selection(
|
||||
selection='_get_icon_selection',
|
||||
string='Icon',
|
||||
default='fa-cog',
|
||||
)
|
||||
company_id = fields.Many2one(
|
||||
'res.company', string='Company',
|
||||
default=lambda self: self.env.company,
|
||||
)
|
||||
default_input_ids = fields.One2many(
|
||||
'fp.step.kind.default.input', 'kind_id',
|
||||
string='Default Inputs', copy=True,
|
||||
help='Auto-seeded onto a step template when this kind is picked '
|
||||
'(via the "Seed Defaults" action).',
|
||||
)
|
||||
template_count = fields.Integer(
|
||||
string='Templates', compute='_compute_template_count')
|
||||
|
||||
_sql_constraints = [
|
||||
('fp_step_kind_code_company_uniq',
|
||||
'unique(code, company_id)',
|
||||
'Step kind code must be unique within a company.'),
|
||||
]
|
||||
|
||||
# Curated FontAwesome 4 icon catalog for the visual icon picker.
|
||||
# Bigger than the 24 historical fp.process.node list — covers
|
||||
# manufacturing, lab, quality, shipping, safety, time, status etc.
|
||||
# FA4 ships with Odoo (no extra deps). Key = CSS class, Value = label.
|
||||
_ICON_SELECTION = [
|
||||
# Process / chemistry
|
||||
('fa-flask', 'Flask / Chemistry'),
|
||||
('fa-tint', 'Drop / Liquid'),
|
||||
('fa-tachometer', 'Gauge / Measure'),
|
||||
('fa-thermometer-half', 'Temp / Heat'),
|
||||
('fa-fire', 'Fire / Bake'),
|
||||
('fa-snowflake-o', 'Cold / Freeze'),
|
||||
('fa-bolt', 'Bolt / Electric'),
|
||||
('fa-plug', 'Plug / Power'),
|
||||
('fa-magnet', 'Magnet'),
|
||||
('fa-bullseye', 'Target / Blast'),
|
||||
('fa-shower', 'Shower / Clean'),
|
||||
('fa-bathtub', 'Bathtub / Soak'),
|
||||
('fa-tachometer', 'Tachometer'),
|
||||
|
||||
# Equipment / shop
|
||||
('fa-industry', 'Industry / Line'),
|
||||
('fa-wrench', 'Wrench / Operation'),
|
||||
('fa-cog', 'Gear / General'),
|
||||
('fa-cogs', 'Gears / System'),
|
||||
('fa-sitemap', 'Sitemap / Process'),
|
||||
('fa-cubes', 'Cubes / Batch'),
|
||||
('fa-cube', 'Cube / Part'),
|
||||
('fa-th', 'Grid / Racking'),
|
||||
('fa-th-large', 'Large Grid'),
|
||||
('fa-server', 'Server / Rack'),
|
||||
('fa-database', 'Database / Tank'),
|
||||
('fa-archive', 'Archive / Box'),
|
||||
('fa-recycle', 'Recycle / Reuse'),
|
||||
('fa-balance-scale', 'Scale / Balance'),
|
||||
|
||||
# Masking / surface
|
||||
('fa-paint-brush', 'Paint / Masking'),
|
||||
('fa-eraser', 'Eraser / De-Masking'),
|
||||
('fa-shield', 'Shield / Protect'),
|
||||
('fa-diamond', 'Diamond / Plating'),
|
||||
('fa-circle-o-notch', 'Coating Layer'),
|
||||
|
||||
# Inspection / quality
|
||||
('fa-search', 'Search / Inspect'),
|
||||
('fa-search-plus', 'Search Plus'),
|
||||
('fa-eye', 'Eye / Visual'),
|
||||
('fa-eye-slash', 'Eye Slash / Hidden'),
|
||||
('fa-camera', 'Camera / Photo'),
|
||||
('fa-check', 'Check'),
|
||||
('fa-check-circle', 'Check / Approve'),
|
||||
('fa-check-square-o', 'Checkbox'),
|
||||
('fa-times', 'Reject / Cancel'),
|
||||
('fa-times-circle', 'Reject Circle'),
|
||||
('fa-exclamation-triangle', 'Warning'),
|
||||
('fa-exclamation-circle', 'Alert'),
|
||||
('fa-info-circle', 'Info'),
|
||||
('fa-question-circle', 'Question'),
|
||||
('fa-bug', 'Bug / Defect'),
|
||||
('fa-flag', 'Flag'),
|
||||
('fa-flag-checkered', 'Finish / Done'),
|
||||
('fa-trophy', 'Trophy / Pass'),
|
||||
('fa-thumbs-up', 'Thumbs Up'),
|
||||
('fa-thumbs-down', 'Thumbs Down'),
|
||||
('fa-star', 'Star'),
|
||||
('fa-bookmark', 'Bookmark'),
|
||||
('fa-certificate', 'Certificate'),
|
||||
|
||||
# Time
|
||||
('fa-clock-o', 'Clock / Wait'),
|
||||
('fa-hourglass-half', 'Hourglass'),
|
||||
('fa-hourglass-end', 'Hourglass End'),
|
||||
('fa-calendar', 'Calendar'),
|
||||
('fa-calendar-check-o', 'Scheduled'),
|
||||
('fa-history', 'History'),
|
||||
|
||||
# Safety / handling
|
||||
('fa-hand-paper-o', 'Hand / Manual'),
|
||||
('fa-hand-stop-o', 'Stop Hand'),
|
||||
('fa-life-ring', 'Safety / Life Ring'),
|
||||
('fa-medkit', 'First Aid'),
|
||||
('fa-user-md', 'Inspector'),
|
||||
('fa-lock', 'Lock / Hold'),
|
||||
('fa-unlock', 'Unlock / Release'),
|
||||
('fa-key', 'Key'),
|
||||
|
||||
# Documentation / certs
|
||||
('fa-file-text-o', 'Document'),
|
||||
('fa-file-pdf-o', 'PDF'),
|
||||
('fa-file-image-o', 'Image File'),
|
||||
('fa-clipboard', 'Clipboard'),
|
||||
('fa-list-alt', 'Checklist'),
|
||||
('fa-list-ul', 'List'),
|
||||
('fa-tags', 'Tags'),
|
||||
('fa-tag', 'Tag'),
|
||||
('fa-barcode', 'Barcode'),
|
||||
('fa-qrcode', 'QR Code'),
|
||||
('fa-pencil', 'Pencil'),
|
||||
('fa-edit', 'Edit'),
|
||||
('fa-print', 'Print'),
|
||||
('fa-paperclip', 'Attach'),
|
||||
|
||||
# Shipping / logistics
|
||||
('fa-truck', 'Truck / Receiving'),
|
||||
('fa-paper-plane', 'Ship / Send'),
|
||||
('fa-plane', 'Plane / Airfreight'),
|
||||
('fa-ship', 'Ship'),
|
||||
('fa-shopping-cart', 'Cart'),
|
||||
('fa-shopping-bag', 'Bag / Pack'),
|
||||
('fa-gift', 'Gift / Package'),
|
||||
('fa-suitcase', 'Suitcase'),
|
||||
('fa-globe', 'Global'),
|
||||
('fa-map-marker', 'Location'),
|
||||
('fa-road', 'In Transit'),
|
||||
|
||||
# Status / process flow
|
||||
('fa-play-circle', 'Start'),
|
||||
('fa-pause-circle', 'Pause / Hold'),
|
||||
('fa-stop-circle', 'Stop'),
|
||||
('fa-step-forward', 'Step Forward'),
|
||||
('fa-fast-forward', 'Fast Forward'),
|
||||
('fa-refresh', 'Refresh / Repeat'),
|
||||
('fa-undo', 'Undo / Rework'),
|
||||
('fa-share', 'Hand-off'),
|
||||
('fa-arrow-right', 'Arrow Right'),
|
||||
('fa-arrow-down', 'Arrow Down'),
|
||||
('fa-long-arrow-right', 'Long Arrow Right'),
|
||||
('fa-random', 'Random / Mix'),
|
||||
('fa-exchange', 'Exchange'),
|
||||
('fa-sort-amount-asc', 'Sort Asc'),
|
||||
('fa-sort-amount-desc', 'Sort Desc'),
|
||||
('fa-tasks', 'Tasks'),
|
||||
|
||||
# Misc useful
|
||||
('fa-sun-o', 'Sun / Dry'),
|
||||
('fa-moon-o', 'Moon / Night'),
|
||||
('fa-cloud', 'Cloud'),
|
||||
('fa-leaf', 'Leaf / Eco'),
|
||||
('fa-tree', 'Tree'),
|
||||
('fa-bell', 'Bell / Alert'),
|
||||
('fa-bullhorn', 'Announce'),
|
||||
('fa-trash', 'Trash / Discard'),
|
||||
('fa-plus-circle', 'Add'),
|
||||
('fa-minus-circle', 'Remove'),
|
||||
('fa-circle', 'Circle'),
|
||||
('fa-square', 'Square'),
|
||||
('fa-asterisk', 'Asterisk'),
|
||||
('fa-cutlery', 'Cutlery / Bend'),
|
||||
('fa-link', 'Link / Adhesion'),
|
||||
('fa-chain-broken', 'Broken Chain'),
|
||||
('fa-anchor', 'Anchor'),
|
||||
('fa-ban', 'Ban / Forbidden'),
|
||||
]
|
||||
|
||||
@api.model
|
||||
def _get_icon_selection(self):
|
||||
return self._ICON_SELECTION
|
||||
|
||||
@api.depends()
|
||||
def _compute_template_count(self):
|
||||
Tpl = self.env['fp.step.template']
|
||||
for k in self:
|
||||
k.template_count = Tpl.search_count([('kind_id', '=', k.id)])
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
for v in vals_list:
|
||||
if v.get('code'):
|
||||
v['code'] = v['code'].lower().strip().replace(' ', '_')
|
||||
return super().create(vals_list)
|
||||
|
||||
def write(self, vals):
|
||||
if vals.get('code'):
|
||||
vals['code'] = vals['code'].lower().strip().replace(' ', '_')
|
||||
return super().write(vals)
|
||||
|
||||
def action_open_templates(self):
|
||||
self.ensure_one()
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _('Step Templates — %s') % self.name,
|
||||
'res_model': 'fp.step.template',
|
||||
'view_mode': 'list,form',
|
||||
'domain': [('kind_id', '=', self.id)],
|
||||
'context': {'default_kind_id': self.id},
|
||||
}
|
||||
|
||||
|
||||
class FpStepKindDefaultInput(models.Model):
|
||||
"""Default input prototype attached to a step kind.
|
||||
|
||||
When a recipe author picks a kind on a step template and clicks
|
||||
'Seed Defaults', these get copied into the template's input list
|
||||
(idempotent — skips by name).
|
||||
"""
|
||||
_name = 'fp.step.kind.default.input'
|
||||
_description = 'Fusion Plating — Step Kind Default Input'
|
||||
_order = 'sequence, name'
|
||||
|
||||
name = fields.Char(string='Name', required=True, translate=True)
|
||||
kind_id = fields.Many2one(
|
||||
'fp.step.kind', string='Kind',
|
||||
required=True, ondelete='cascade', index=True,
|
||||
)
|
||||
input_type = fields.Selection([
|
||||
('text', 'Text'),
|
||||
('number', 'Number'),
|
||||
('boolean', 'Yes/No'),
|
||||
('selection', 'Selection'),
|
||||
('date', 'Date / Time'),
|
||||
('signature', 'Signature'),
|
||||
('time_hms', 'Time (HH:MM:SS)'),
|
||||
('time_seconds', 'Time (seconds)'),
|
||||
('temperature', 'Temperature'),
|
||||
('thickness', 'Thickness'),
|
||||
('pass_fail', 'Pass / Fail'),
|
||||
('photo', 'Photo'),
|
||||
('multi_point_thickness', 'Multi-Point Thickness (avg)'),
|
||||
('bath_chemistry_panel', 'Bath Chemistry Panel'),
|
||||
('ph', 'pH'),
|
||||
], string='Input Type', required=True, default='text')
|
||||
target_unit = fields.Selection(FP_UOM_SELECTION, string='Target Unit')
|
||||
required = fields.Boolean(string='Required', default=False)
|
||||
hint = fields.Char(string='Hint')
|
||||
selection_options = fields.Text(string='Selection Options',
|
||||
help='Comma-separated when input_type is "selection".')
|
||||
sequence = fields.Integer(string='Sequence', default=10)
|
||||
@@ -88,32 +88,21 @@ class FpStepTemplate(models.Model):
|
||||
requires_transition_form = fields.Boolean(string='Requires Transition Form',
|
||||
help='Opens the transition form before Mark Done (Sub 12b).')
|
||||
|
||||
default_kind = fields.Selection([
|
||||
('receiving', 'Receiving / Incoming Inspection'),
|
||||
('contract_review', 'Contract Review (QA-005)'),
|
||||
('racking', 'Racking'),
|
||||
('mask', 'Masking'),
|
||||
('cleaning', 'Cleaning'),
|
||||
('electroclean', 'Electroclean'),
|
||||
('etch', 'Etch / Activation'),
|
||||
('rinse', 'Rinse'),
|
||||
('strike', 'Strike (Wood\'s Nickel / Activation)'),
|
||||
('plate', 'Plating'),
|
||||
('replenishment', 'Tank Replenishment'),
|
||||
('wbf_test', 'Water Break Free Test'),
|
||||
('dry', 'Drying'),
|
||||
('bake', 'Bake (HE Relief / Stress Relief)'),
|
||||
('demask', 'De-Masking'),
|
||||
('derack', 'De-Racking'),
|
||||
('inspect', 'Inspection'),
|
||||
('hardness_test', 'Hardness Test (HV / HK / HRC)'),
|
||||
('adhesion_test', 'Adhesion Test'),
|
||||
('salt_spray', 'Salt Spray / Corrosion Test'),
|
||||
('final_inspect', 'Final Inspection'),
|
||||
('packaging', 'Packaging / Pre-Ship'),
|
||||
('ship', 'Shipping'),
|
||||
('gating', 'Gating'),
|
||||
], string='Step Kind', help='Drives sane-default input seeding.')
|
||||
# Sub 14b — User-extensible Step Kinds (was Selection of 24).
|
||||
kind_id = fields.Many2one(
|
||||
'fp.step.kind', string='Step Kind', ondelete='restrict',
|
||||
index=True, tracking=True,
|
||||
help='Pick from the catalog or create a new kind. Drives sane-'
|
||||
'default input seeding.',
|
||||
)
|
||||
# Back-compat shim — every legacy `tpl.default_kind == "cleaning"`
|
||||
# call site keeps working without a refactor. Stored=True so existing
|
||||
# search domains [('default_kind', '=', 'cleaning')] still hit an
|
||||
# indexed column.
|
||||
default_kind = fields.Char(
|
||||
related='kind_id.code', store=True, readonly=True, index=True,
|
||||
string='Step Kind Code',
|
||||
)
|
||||
|
||||
input_template_ids = fields.One2many(
|
||||
'fp.step.template.input', 'template_id',
|
||||
@@ -152,13 +141,11 @@ class FpStepTemplate(models.Model):
|
||||
return super().write(vals)
|
||||
|
||||
# ----- Sane defaults seeding ---------------------------------------------
|
||||
|
||||
# NB target_unit must be a valid FP_UOM_SELECTION key — it became a
|
||||
# Selection in 19.0.12.1.0 (uom cleanup). Free-text values like
|
||||
# 'HH:MM', '°F', 'sec', 'in', 'each' raise ValueError on create.
|
||||
# Mapping cheatsheet: sec → 's', °F → 'f', °C → 'c', in → 'in',
|
||||
# each → 'each', min → 'min'. Format-only strings ('HH:MM') get
|
||||
# left blank since they're not units.
|
||||
# Sub 14b — moved from a Python dict into seeded fp.step.kind records
|
||||
# so users can add new kinds + their default inputs through the
|
||||
# standard UI. The dict below is preserved as a fallback only for
|
||||
# codes that don't have a matching kind_id record (legacy data after
|
||||
# migration). It will be removed in a future version.
|
||||
DEFAULT_INPUTS_BY_KIND = {
|
||||
'receiving': [
|
||||
{'name': 'Qty Received', 'input_type': 'number',
|
||||
@@ -419,19 +406,37 @@ class FpStepTemplate(models.Model):
|
||||
)
|
||||
return True
|
||||
|
||||
# Mapping from fp.step.kind.default.input fields → fp.step.template.input
|
||||
# spec dict. Keep narrow — copy only the columns both models share.
|
||||
_KIND_DEFAULT_INPUT_FIELDS = (
|
||||
'name', 'input_type', 'target_unit', 'required',
|
||||
'hint', 'selection_options', 'sequence',
|
||||
)
|
||||
|
||||
def action_seed_default_inputs(self):
|
||||
"""Seed input_template_ids based on default_kind. Idempotent —
|
||||
only adds inputs whose names don't already exist on this template.
|
||||
"""Seed input_template_ids from kind_id.default_input_ids.
|
||||
Idempotent — only adds inputs whose names don't already exist on
|
||||
this template.
|
||||
|
||||
Falls back to the legacy DEFAULT_INPUTS_BY_KIND dict if the
|
||||
template has no kind_id but still carries a default_kind code
|
||||
(defensive — shouldn't happen post-migration).
|
||||
|
||||
Public method (Odoo 19 requires non-underscore-prefixed names
|
||||
for methods called from a view button).
|
||||
"""
|
||||
Input = self.env['fp.step.template.input']
|
||||
for tpl in self:
|
||||
if not tpl.default_kind:
|
||||
continue
|
||||
existing_names = set(tpl.input_template_ids.mapped('name'))
|
||||
for spec in self.DEFAULT_INPUTS_BY_KIND.get(tpl.default_kind, []):
|
||||
specs = []
|
||||
if tpl.kind_id:
|
||||
for d in tpl.kind_id.default_input_ids:
|
||||
spec = {f: d[f] for f in self._KIND_DEFAULT_INPUT_FIELDS}
|
||||
specs.append(spec)
|
||||
elif tpl.default_kind:
|
||||
# Legacy fallback — kind_id never got linked.
|
||||
specs = self.DEFAULT_INPUTS_BY_KIND.get(tpl.default_kind, [])
|
||||
for spec in specs:
|
||||
if spec['name'] in existing_names:
|
||||
continue
|
||||
Input.create({
|
||||
|
||||
@@ -70,6 +70,12 @@ access_fp_work_role_manager,fp.work.role.manager,model_fp_work_role,group_fusion
|
||||
access_fp_proficiency_operator,fp.operator.proficiency.operator,model_fp_operator_proficiency,group_fusion_plating_operator,1,0,0,0
|
||||
access_fp_proficiency_supervisor,fp.operator.proficiency.supervisor,model_fp_operator_proficiency,group_fusion_plating_supervisor,1,1,1,0
|
||||
access_fp_proficiency_manager,fp.operator.proficiency.manager,model_fp_operator_proficiency,group_fusion_plating_manager,1,1,1,1
|
||||
access_fp_step_kind_operator,fp.step.kind.operator,model_fp_step_kind,group_fusion_plating_operator,1,0,0,0
|
||||
access_fp_step_kind_supervisor,fp.step.kind.supervisor,model_fp_step_kind,group_fusion_plating_supervisor,1,1,1,0
|
||||
access_fp_step_kind_manager,fp.step.kind.manager,model_fp_step_kind,group_fusion_plating_manager,1,1,1,1
|
||||
access_fp_step_kind_default_input_operator,fp.step.kind.default.input.operator,model_fp_step_kind_default_input,group_fusion_plating_operator,1,0,0,0
|
||||
access_fp_step_kind_default_input_supervisor,fp.step.kind.default.input.supervisor,model_fp_step_kind_default_input,group_fusion_plating_supervisor,1,1,1,1
|
||||
access_fp_step_kind_default_input_manager,fp.step.kind.default.input.manager,model_fp_step_kind_default_input,group_fusion_plating_manager,1,1,1,1
|
||||
access_fp_step_template_operator,fp.step.template.operator,model_fp_step_template,group_fusion_plating_operator,1,0,0,0
|
||||
access_fp_step_template_supervisor,fp.step.template.supervisor,model_fp_step_template,group_fusion_plating_supervisor,1,1,1,0
|
||||
access_fp_step_template_manager,fp.step.template.manager,model_fp_step_template,group_fusion_plating_manager,1,1,1,1
|
||||
|
||||
|
@@ -0,0 +1,58 @@
|
||||
/** @odoo-module **/
|
||||
// Sub 14b — visual FontAwesome icon picker for fp.step.kind.icon and any
|
||||
// other Selection field whose values are FA classes (e.g. 'fa-flask').
|
||||
//
|
||||
// Always-visible compact grid with a Search box. Glyph-only tiles
|
||||
// (label appears on hover via title attribute). Designed for the form
|
||||
// view; the list shows raw text and opens the form for editing.
|
||||
|
||||
import { Component, useState } from "@odoo/owl";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { standardFieldProps } from "@web/views/fields/standard_field_props";
|
||||
|
||||
class FpIconPickerField extends Component {
|
||||
static template = "fusion_plating.FpIconPicker";
|
||||
static props = { ...standardFieldProps };
|
||||
|
||||
setup() {
|
||||
this.state = useState({ filter: "" });
|
||||
}
|
||||
|
||||
get options() {
|
||||
const field = this.props.record.fields[this.props.name];
|
||||
return (field && field.selection) || [];
|
||||
}
|
||||
|
||||
get filteredOptions() {
|
||||
const q = (this.state.filter || "").trim().toLowerCase();
|
||||
if (!q) return this.options;
|
||||
return this.options.filter(([code, label]) =>
|
||||
code.toLowerCase().includes(q) ||
|
||||
(label || "").toLowerCase().includes(q)
|
||||
);
|
||||
}
|
||||
|
||||
get currentValue() {
|
||||
return this.props.record.data[this.props.name] || "";
|
||||
}
|
||||
|
||||
get currentLabel() {
|
||||
const opt = this.options.find((o) => o[0] === this.currentValue);
|
||||
return opt ? opt[1] : "";
|
||||
}
|
||||
|
||||
async onPick(value, ev) {
|
||||
if (this.props.readonly) return;
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
await this.props.record.update({ [this.props.name]: value });
|
||||
}
|
||||
}
|
||||
|
||||
export const fpIconPickerField = {
|
||||
component: FpIconPickerField,
|
||||
displayName: "Icon Picker",
|
||||
supportedTypes: ["selection"],
|
||||
};
|
||||
|
||||
registry.category("fields").add("fp_icon_picker", fpIconPickerField);
|
||||
@@ -243,6 +243,7 @@ export class FpSimpleRecipeEditor extends Component {
|
||||
*/
|
||||
async onOpenLibraryCreate() {
|
||||
await this._fpEnsureWorkflowStatesLoaded();
|
||||
await this._fpEnsureKindOptionsLoaded();
|
||||
this.state.libraryEditor = {
|
||||
id: null, // null = create
|
||||
name: "",
|
||||
@@ -282,9 +283,67 @@ export class FpSimpleRecipeEditor extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sub 14b — fetch the user-extensible Step Kind catalog once per
|
||||
* editor session, cache on this.state.kindOptions. Used by both
|
||||
* create + edit flows to populate the "Step Kind" dropdown so
|
||||
* user-added kinds appear without a page reload.
|
||||
*/
|
||||
async _fpEnsureKindOptionsLoaded() {
|
||||
if (this.state.kindOptions && this.state.kindOptions.length) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const data = await rpc("/fp/simple_recipe/kinds/list", {});
|
||||
this.state.kindOptions = data.kinds || [];
|
||||
} catch (err) {
|
||||
this.state.kindOptions = [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sub 14b — handler for Step Kind dropdown change. Special-cases
|
||||
* the "+ Add a new kind…" sentinel: prompt the user for a name,
|
||||
* round-trip to /kinds/create, refresh the cached options, then
|
||||
* select the newly-created kind.
|
||||
*/
|
||||
async onKindChange(ev) {
|
||||
const code = ev.target.value;
|
||||
if (code !== "__new__") {
|
||||
this.state.libraryEditor.default_kind = code || "";
|
||||
return;
|
||||
}
|
||||
// Reset the dropdown so it doesn't stay on the sentinel if the
|
||||
// user cancels the prompt.
|
||||
ev.target.value = this.state.libraryEditor.default_kind || "";
|
||||
const name = window.prompt(
|
||||
"Name your new Step Kind (e.g. 'Passivation', 'Shot Peen')",
|
||||
""
|
||||
);
|
||||
if (!name || !name.trim()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const data = await rpc("/fp/simple_recipe/kinds/create", {
|
||||
name: name.trim(),
|
||||
});
|
||||
if (!data.ok) {
|
||||
alert(data.error || "Could not create Step Kind.");
|
||||
return;
|
||||
}
|
||||
// Drop the cached list so the next ensure() refetches it.
|
||||
this.state.kindOptions = null;
|
||||
await this._fpEnsureKindOptionsLoaded();
|
||||
this.state.libraryEditor.default_kind = data.code;
|
||||
} catch (err) {
|
||||
alert("Could not create Step Kind: " + (err.message || err));
|
||||
}
|
||||
}
|
||||
|
||||
async onOpenLibraryEdit(templateId) {
|
||||
this.state.libraryEditorBusy = true;
|
||||
await this._fpEnsureWorkflowStatesLoaded();
|
||||
await this._fpEnsureKindOptionsLoaded();
|
||||
const data = await rpc("/fp/simple_recipe/library/load", {
|
||||
template_id: templateId,
|
||||
});
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
// Sub 14b — Visual icon picker for fp.step.kind.icon and similar
|
||||
// Selection fields whose values are FontAwesome class names.
|
||||
//
|
||||
// Compact 12-column grid with Search filter. Glyph-only tiles
|
||||
// (label appears on hover via the browser tooltip). Capped at ~280px
|
||||
// scrollable height so it doesn't dominate the form.
|
||||
//
|
||||
// Dark-mode aware via $o-webclient-color-scheme branch (see CLAUDE.md).
|
||||
|
||||
$o-webclient-color-scheme: bright !default;
|
||||
|
||||
$_fp-icon-picker-bg-hex: #ffffff;
|
||||
$_fp-icon-picker-border-hex: #d8dadd;
|
||||
$_fp-icon-picker-hover-hex: #f3f4f6;
|
||||
$_fp-icon-picker-active-hex: #2c89e9;
|
||||
$_fp-icon-picker-text-hex: #21252b;
|
||||
$_fp-icon-picker-muted-hex: #6c757d;
|
||||
|
||||
@if $o-webclient-color-scheme == dark {
|
||||
$_fp-icon-picker-bg-hex: #22262d !global;
|
||||
$_fp-icon-picker-border-hex: #3a3f47 !global;
|
||||
$_fp-icon-picker-hover-hex: #2c313a !global;
|
||||
$_fp-icon-picker-active-hex: #4ea3ff !global;
|
||||
$_fp-icon-picker-text-hex: #e6e9ef !global;
|
||||
$_fp-icon-picker-muted-hex: #9aa3ad !global;
|
||||
}
|
||||
|
||||
$fp-icon-picker-bg: var(--fp-icon-picker-bg, $_fp-icon-picker-bg-hex);
|
||||
$fp-icon-picker-border: var(--fp-icon-picker-border, $_fp-icon-picker-border-hex);
|
||||
$fp-icon-picker-hover: var(--fp-icon-picker-hover, $_fp-icon-picker-hover-hex);
|
||||
$fp-icon-picker-active: var(--fp-icon-picker-active, $_fp-icon-picker-active-hex);
|
||||
$fp-icon-picker-text: var(--fp-icon-picker-text, $_fp-icon-picker-text-hex);
|
||||
$fp-icon-picker-muted: var(--fp-icon-picker-muted, $_fp-icon-picker-muted-hex);
|
||||
|
||||
// Force full sheet width even when Odoo wraps the field in a fixed-width
|
||||
// .o_field_widget cell. Selecting both the wrapper and the inline root
|
||||
// belt-and-suspenders any group container that tries to clip us.
|
||||
.o_field_widget[name="icon"]:has(.o_fp_icon_picker_inline) {
|
||||
width: 100% !important;
|
||||
max-width: none !important;
|
||||
flex: 1 1 100% !important;
|
||||
}
|
||||
|
||||
.o_fp_icon_picker_inline {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.4rem;
|
||||
width: 100%;
|
||||
|
||||
.o_fp_icon_picker_top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.o_fp_icon_picker_current {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
padding: 0.25rem 0.6rem;
|
||||
background-color: $fp-icon-picker-bg;
|
||||
border: 1px solid $fp-icon-picker-border;
|
||||
border-radius: 4px;
|
||||
color: $fp-icon-picker-text;
|
||||
font-size: 0.85rem;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.o_fp_icon_picker_current_glyph {
|
||||
font-size: 1rem;
|
||||
color: $fp-icon-picker-active;
|
||||
width: 1.2em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.o_fp_icon_picker_current_empty {
|
||||
color: $fp-icon-picker-muted;
|
||||
}
|
||||
|
||||
.o_fp_icon_picker_current_label {
|
||||
flex: 1;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.o_fp_icon_picker_filter {
|
||||
flex: 0 0 140px;
|
||||
height: 28px;
|
||||
font-size: 0.8rem;
|
||||
padding: 0.2rem 0.5rem;
|
||||
}
|
||||
|
||||
.o_fp_icon_picker_inline_grid {
|
||||
// Auto-fill tracks of ~36px each → grid expands to fill
|
||||
// whatever width the form column gives it.
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(36px, 1fr));
|
||||
gap: 2px;
|
||||
padding: 0.4rem;
|
||||
background-color: $fp-icon-picker-bg;
|
||||
border: 1px solid $fp-icon-picker-border;
|
||||
border-radius: 4px;
|
||||
max-height: 240px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.o_fp_icon_picker_tile {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
color: $fp-icon-picker-text;
|
||||
height: 30px;
|
||||
width: 100%;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background-color: $fp-icon-picker-hover;
|
||||
border-color: $fp-icon-picker-border;
|
||||
}
|
||||
|
||||
&.o_fp_icon_picker_active {
|
||||
border-color: $fp-icon-picker-active;
|
||||
color: $fp-icon-picker-active;
|
||||
background-color: rgba(44, 137, 233, 0.10);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
cursor: default;
|
||||
opacity: 0.55;
|
||||
}
|
||||
}
|
||||
|
||||
.o_fp_icon_picker_tile_glyph {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.o_fp_icon_picker_empty_results {
|
||||
grid-column: 1 / -1;
|
||||
padding: 0.5rem;
|
||||
text-align: center;
|
||||
color: $fp-icon-picker-muted;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="fusion_plating.FpIconPicker">
|
||||
<div class="o_fp_icon_picker_inline">
|
||||
<div class="o_fp_icon_picker_top">
|
||||
<div class="o_fp_icon_picker_current">
|
||||
<i t-if="currentValue" t-att-class="'fa ' + currentValue + ' o_fp_icon_picker_current_glyph'"/>
|
||||
<i t-if="!currentValue" class="fa fa-square-o o_fp_icon_picker_current_glyph o_fp_icon_picker_current_empty"/>
|
||||
<span class="o_fp_icon_picker_current_label" t-esc="currentLabel || 'Pick an icon'"/>
|
||||
</div>
|
||||
<input type="text" class="o_fp_icon_picker_filter form-control"
|
||||
placeholder="Search…"
|
||||
t-att-value="state.filter"
|
||||
t-on-input="(ev) => state.filter = ev.target.value"
|
||||
t-att-disabled="props.readonly"/>
|
||||
</div>
|
||||
<div class="o_fp_icon_picker_inline_grid">
|
||||
<button type="button"
|
||||
t-foreach="filteredOptions" t-as="opt" t-key="opt[0]"
|
||||
class="o_fp_icon_picker_tile"
|
||||
t-att-class="{ 'o_fp_icon_picker_active': opt[0] === currentValue }"
|
||||
t-att-title="opt[1]"
|
||||
t-att-disabled="props.readonly"
|
||||
t-on-click="(ev) => this.onPick(opt[0], ev)">
|
||||
<i t-att-class="'fa ' + opt[0] + ' o_fp_icon_picker_tile_glyph'"/>
|
||||
</button>
|
||||
<div t-if="filteredOptions.length === 0" class="o_fp_icon_picker_empty_results">
|
||||
No icons match "<t t-esc="state.filter"/>"
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
@@ -414,32 +414,15 @@
|
||||
title="Picking a kind auto-seeds prompts and turns on workflow gates (Contract Review, Racking, Bake). Leave blank for plain generic steps."/>
|
||||
</label>
|
||||
<select class="form-select"
|
||||
t-model="state.libraryEditor.default_kind">
|
||||
t-on-change="(ev) => this.onKindChange(ev)"
|
||||
t-att-value="state.libraryEditor.default_kind">
|
||||
<option value="">Generic — no automatic behaviour</option>
|
||||
<option value="receiving">Receiving / Incoming Inspection</option>
|
||||
<option value="contract_review">Contract Review (QA-005)</option>
|
||||
<option value="racking">Racking</option>
|
||||
<option value="mask">Masking</option>
|
||||
<option value="cleaning">Cleaning</option>
|
||||
<option value="electroclean">Electroclean</option>
|
||||
<option value="etch">Etch / Activation</option>
|
||||
<option value="rinse">Rinse</option>
|
||||
<option value="strike">Strike (Wood's Nickel / Activation)</option>
|
||||
<option value="plate">Plating</option>
|
||||
<option value="replenishment">Tank Replenishment</option>
|
||||
<option value="wbf_test">Water Break Free Test</option>
|
||||
<option value="dry">Drying</option>
|
||||
<option value="bake">Bake (HE Relief / Stress Relief)</option>
|
||||
<option value="demask">De-Masking</option>
|
||||
<option value="derack">De-Racking</option>
|
||||
<option value="inspect">Inspection</option>
|
||||
<option value="hardness_test">Hardness Test</option>
|
||||
<option value="adhesion_test">Adhesion Test</option>
|
||||
<option value="salt_spray">Salt Spray / Corrosion Test</option>
|
||||
<option value="final_inspect">Final Inspection</option>
|
||||
<option value="packaging">Packaging / Pre-Ship</option>
|
||||
<option value="ship">Shipping</option>
|
||||
<option value="gating">Gating</option>
|
||||
<t t-foreach="state.kindOptions || []" t-as="k" t-key="k.id">
|
||||
<option t-att-value="k.code" t-att-selected="k.code === state.libraryEditor.default_kind">
|
||||
<t t-esc="k.name"/>
|
||||
</option>
|
||||
</t>
|
||||
<option value="__new__">+ Add a new kind…</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="o_fp_le_field">
|
||||
|
||||
@@ -179,7 +179,8 @@
|
||||
<group>
|
||||
<group string="Stations">
|
||||
<field name="tank_ids" widget="many2many_tags"/>
|
||||
<field name="default_kind"/>
|
||||
<field name="kind_id"
|
||||
options="{'no_create_edit': false, 'no_quick_create': false}"/>
|
||||
<field name="material_callout"/>
|
||||
</group>
|
||||
<group string="Flags">
|
||||
|
||||
127
fusion_plating/fusion_plating/views/fp_step_kind_views.xml
Normal file
127
fusion_plating/fusion_plating/views/fp_step_kind_views.xml
Normal file
@@ -0,0 +1,127 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright 2026 Nexa Systems Inc.
|
||||
License OPL-1 (Odoo Proprietary License v1.0)
|
||||
Part of the Fusion Plating product family.
|
||||
|
||||
Sub 14b — User-extensible Step Kind catalog. Replaces the hardcoded
|
||||
`default_kind` Selection on fp.step.template / fusion.plating.process.node.
|
||||
-->
|
||||
<odoo>
|
||||
|
||||
<record id="view_fp_step_kind_list" model="ir.ui.view">
|
||||
<field name="name">fp.step.kind.list</field>
|
||||
<field name="model">fp.step.kind</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="Step Kinds">
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="name"/>
|
||||
<field name="code"/>
|
||||
<field name="icon"/>
|
||||
<field name="template_count"/>
|
||||
<field name="active" widget="boolean_toggle"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_fp_step_kind_form" model="ir.ui.view">
|
||||
<field name="name">fp.step.kind.form</field>
|
||||
<field name="model">fp.step.kind</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Step Kind">
|
||||
<header>
|
||||
<button name="action_open_templates" type="object"
|
||||
string="View Templates" class="btn-secondary"
|
||||
invisible="template_count == 0"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<widget name="web_ribbon" title="Archived"
|
||||
bg_color="bg-danger"
|
||||
invisible="active"/>
|
||||
<div class="oe_title">
|
||||
<label for="name"/>
|
||||
<h1><field name="name" placeholder="e.g. Passivation"/></h1>
|
||||
<div class="text-muted">
|
||||
<field name="code" placeholder="passivation"/>
|
||||
</div>
|
||||
</div>
|
||||
<group>
|
||||
<group>
|
||||
<field name="sequence"/>
|
||||
<field name="company_id"
|
||||
groups="base.group_multi_company"/>
|
||||
<field name="active" invisible="1"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="template_count"/>
|
||||
</group>
|
||||
</group>
|
||||
<h3 class="mt-3 mb-2">Icon</h3>
|
||||
<field name="icon" widget="fp_icon_picker" nolabel="1"/>
|
||||
<notebook>
|
||||
<page string="Default Inputs" name="defaults">
|
||||
<div class="alert alert-info" role="alert">
|
||||
These inputs auto-seed onto a Step Template when an
|
||||
author picks this kind and clicks "Seed Default Inputs".
|
||||
Idempotent — won't duplicate prompts that already exist.
|
||||
</div>
|
||||
<field name="default_input_ids">
|
||||
<list editable="bottom">
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="name"/>
|
||||
<field name="input_type"/>
|
||||
<field name="target_unit"/>
|
||||
<field name="required" widget="boolean_toggle"/>
|
||||
<field name="hint"/>
|
||||
<field name="selection_options"/>
|
||||
</list>
|
||||
</field>
|
||||
</page>
|
||||
<page string="Description" name="description">
|
||||
<field name="description"
|
||||
placeholder="Optional ops note shown to recipe authors when they pick this kind."/>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_fp_step_kind_search" model="ir.ui.view">
|
||||
<field name="name">fp.step.kind.search</field>
|
||||
<field name="model">fp.step.kind</field>
|
||||
<field name="arch" type="xml">
|
||||
<search>
|
||||
<field name="name"/>
|
||||
<field name="code"/>
|
||||
<separator/>
|
||||
<filter string="Archived" name="inactive" domain="[('active','=',False)]"/>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_fp_step_kind" model="ir.actions.act_window">
|
||||
<field name="name">Step Kinds</field>
|
||||
<field name="res_model">fp.step.kind</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
<field name="search_view_id" ref="view_fp_step_kind_search"/>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Add a new Step Kind
|
||||
</p>
|
||||
<p>
|
||||
Step Kinds drive the dropdown shown when a recipe author
|
||||
picks the type of a step (Cleaning, Plating, Bake…). Each
|
||||
kind can carry default inputs that get auto-seeded onto
|
||||
templates.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_fp_step_kind"
|
||||
name="Step Kinds"
|
||||
parent="menu_fp_config_recipes_steps"
|
||||
action="action_fp_step_kind"
|
||||
sequence="50"/>
|
||||
|
||||
</odoo>
|
||||
@@ -14,7 +14,7 @@
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="name"/>
|
||||
<field name="code"/>
|
||||
<field name="default_kind"/>
|
||||
<field name="kind_id"/>
|
||||
<field name="tank_ids" widget="many2many_tags" optional="show"/>
|
||||
<field name="requires_signoff" optional="hide"/>
|
||||
<field name="requires_rack_assignment" optional="hide"/>
|
||||
@@ -32,7 +32,7 @@
|
||||
<header>
|
||||
<button name="action_seed_default_inputs" type="object"
|
||||
string="Seed Default Inputs" class="btn-secondary"
|
||||
invisible="not default_kind"/>
|
||||
invisible="not kind_id"/>
|
||||
<button name="action_add_common_audit_fields" type="object"
|
||||
string="+ Common Audit Fields"
|
||||
class="btn-secondary"
|
||||
@@ -48,7 +48,8 @@
|
||||
</div>
|
||||
<group>
|
||||
<group string="Classification">
|
||||
<field name="default_kind"/>
|
||||
<field name="kind_id"
|
||||
options="{'no_create_edit': false, 'no_quick_create': false}"/>
|
||||
<field name="icon"/>
|
||||
<field name="process_type_id"/>
|
||||
<field name="material_callout"/>
|
||||
@@ -133,13 +134,13 @@
|
||||
<search>
|
||||
<field name="name"/>
|
||||
<field name="code"/>
|
||||
<field name="default_kind"/>
|
||||
<field name="kind_id"/>
|
||||
<field name="tank_ids"/>
|
||||
<separator/>
|
||||
<filter string="Archived" name="inactive" domain="[('active','=',False)]"/>
|
||||
<group>
|
||||
<filter string="Step Kind" name="group_kind"
|
||||
context="{'group_by':'default_kind'}"/>
|
||||
context="{'group_by':'kind_id'}"/>
|
||||
<filter string="Process Type" name="group_proc"
|
||||
context="{'group_by':'process_type_id'}"/>
|
||||
</group>
|
||||
|
||||
Reference in New Issue
Block a user