This commit is contained in:
gsinghpal
2026-04-20 01:16:12 -04:00
parent 8217bb0ff6
commit 54e56ed0e6
39 changed files with 5600 additions and 1131 deletions

View File

@@ -96,6 +96,11 @@ Copyright (c) 2026 Nexa Systems Inc. All rights reserved.
'views/res_config_settings_views.xml',
'views/fp_menu.xml',
'data/fp_recipe_enp_alum_basic.xml',
'data/fp_recipe_enp_steel_basic.xml',
'data/fp_recipe_enp_sp.xml',
'data/fp_recipe_general_processing.xml',
'data/fp_recipe_anodize.xml',
'data/fp_recipe_chem_conversion.xml',
],
'post_init_hook': 'post_init_hook',
'assets': {

View File

@@ -0,0 +1,386 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2026 Nexa Systems Inc.
License OPL-1 (Odoo Proprietary License v1.0)
Recipe: ANODIZE (Sulfuric Anodize — Type II)
Source: Client's Steelhead export (April 2026 transcription).
Anodize is an umbrella workflow covering pre-treatment, the wet
anodize line (alkaline clean → etch → deoxidize → sulfuric anodize
→ hot water seal), and post-treatment (dry, unrack, de-mask).
Tree:
ANODIZE (recipe)
Ready for Solvent Clean (1) (sub-process, auto)
Solvent Clean (1) (operation, customer-visible)
Blasting (sub-process, signoff, auto)
Ready For Blast (operation)
Blast (operation, customer-visible)
Masking (sub-process, signoff, auto)
Ready For Masking (operation)
Masking (operation, customer-visible)
Racking (sub-process, auto)
Ready for Racking (operation)
Racking (operation, customer-visible)
Anodize Line (sub-process, auto)
Ready For Anodize (operation)
Alkaline Clean (Tank A1) (operation, customer-visible)
Primary Rinse (Tank A2)
Secondary Rinse (Tank A4)
Etch (Tank A3) (operation, customer-visible)
Primary Rinse (Tank A4)
Secondary Rinse (Tank A6)
Deoxidize (Tank A5) (operation, customer-visible)
Primary Rinse (Tank A6)
Secondary Rinse (Tank A8)
Sulfuric Anodize (operation, auto)
Sulfuric Anodize Ramp (Tank A9)
Sulfuric Anodize (Tank A9) (operation, signoff, customer-visible)
Primary Rinse (Tank A8)
Secondary Rinse (Tank A12)
Hot Rinse (Tank A17)
Hot Water Seal (Tank A16) (operation, customer-visible)
Primary Rinse (Tank A12)
Hot Rinse (Tank A17) (customer-visible)
Anodize Dry (sub-process)
Unracking (sub-process, auto)
Ready for Unrack (operation)
Unracking (operation, customer-visible)
De-Masking (sub-process, signoff, auto)
Ready for De-Masking (operation)
De-Masking (operation, customer-visible)
-->
<odoo>
<data noupdate="0">
<!-- ========================= ROOT ========================= -->
<record id="recipe_anodize" model="fusion.plating.process.node">
<field name="name">Anodize</field>
<field name="code">ANODIZE</field>
<field name="node_type">recipe</field>
<field name="icon">fa-flask</field>
<field name="sequence">50</field>
<field name="auto_complete">True</field>
<field name="customer_visible">False</field>
</record>
<!-- ========================= 1. Ready for Solvent Clean (1) ========================= -->
<record id="anodize_ready_solvent_clean" model="fusion.plating.process.node">
<field name="name">Ready for Solvent Clean (1)</field>
<field name="node_type">sub_process</field>
<field name="parent_id" ref="recipe_anodize"/>
<field name="icon">fa-shower</field>
<field name="sequence">10</field>
<field name="auto_complete">True</field>
</record>
<record id="anodize_solvent_clean" model="fusion.plating.process.node">
<field name="name">Solvent Clean (1)</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="anodize_ready_solvent_clean"/>
<field name="icon">fa-shower</field>
<field name="sequence">10</field>
<field name="customer_visible">True</field>
</record>
<!-- ========================= 2. Blasting ========================= -->
<record id="anodize_blasting" model="fusion.plating.process.node">
<field name="name">Blasting</field>
<field name="node_type">sub_process</field>
<field name="parent_id" ref="recipe_anodize"/>
<field name="icon">fa-bullseye</field>
<field name="sequence">20</field>
<field name="auto_complete">True</field>
<field name="requires_signoff">True</field>
</record>
<record id="anodize_blasting_ready" model="fusion.plating.process.node">
<field name="name">Ready For Blast</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="anodize_blasting"/>
<field name="icon">fa-clock-o</field>
<field name="sequence">10</field>
</record>
<record id="anodize_blasting_do" model="fusion.plating.process.node">
<field name="name">Blast</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="anodize_blasting"/>
<field name="icon">fa-bullseye</field>
<field name="sequence">20</field>
<field name="customer_visible">True</field>
</record>
<!-- ========================= 3. Masking ========================= -->
<record id="anodize_masking" model="fusion.plating.process.node">
<field name="name">Masking</field>
<field name="node_type">sub_process</field>
<field name="parent_id" ref="recipe_anodize"/>
<field name="icon">fa-paint-brush</field>
<field name="sequence">30</field>
<field name="auto_complete">True</field>
<field name="requires_signoff">True</field>
</record>
<record id="anodize_masking_ready" model="fusion.plating.process.node">
<field name="name">Ready For Masking</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="anodize_masking"/>
<field name="icon">fa-clock-o</field>
<field name="sequence">10</field>
</record>
<record id="anodize_masking_do" model="fusion.plating.process.node">
<field name="name">Masking</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="anodize_masking"/>
<field name="icon">fa-paint-brush</field>
<field name="sequence">20</field>
<field name="customer_visible">True</field>
</record>
<!-- ========================= 4. Racking ========================= -->
<record id="anodize_racking" model="fusion.plating.process.node">
<field name="name">Racking</field>
<field name="node_type">sub_process</field>
<field name="parent_id" ref="recipe_anodize"/>
<field name="icon">fa-th</field>
<field name="sequence">40</field>
<field name="auto_complete">True</field>
</record>
<record id="anodize_racking_ready" model="fusion.plating.process.node">
<field name="name">Ready for Racking</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="anodize_racking"/>
<field name="icon">fa-clock-o</field>
<field name="sequence">10</field>
</record>
<record id="anodize_racking_do" model="fusion.plating.process.node">
<field name="name">Racking</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="anodize_racking"/>
<field name="icon">fa-th</field>
<field name="sequence">20</field>
<field name="customer_visible">True</field>
</record>
<!-- ========================= 5. Anodize Line (sub-process) ========================= -->
<record id="anodize_line" model="fusion.plating.process.node">
<field name="name">Anodize Line</field>
<field name="node_type">sub_process</field>
<field name="parent_id" ref="recipe_anodize"/>
<field name="icon">fa-industry</field>
<field name="sequence">50</field>
<field name="auto_complete">True</field>
</record>
<!-- 5a. Ready For Anodize -->
<record id="anodize_line_ready" model="fusion.plating.process.node">
<field name="name">Ready For Anodize</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="anodize_line"/>
<field name="icon">fa-clock-o</field>
<field name="sequence">10</field>
</record>
<!-- 5b. Alkaline Clean (Tank A-1) -->
<record id="anodize_alkaline_clean" model="fusion.plating.process.node">
<field name="name">Alkaline Clean (Tank A-1)</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="anodize_line"/>
<field name="icon">fa-shower</field>
<field name="sequence">20</field>
<field name="customer_visible">True</field>
</record>
<record id="anodize_alkaline_primary_rinse" model="fusion.plating.process.node">
<field name="name">Primary Rinse (Tank A-2)</field>
<field name="node_type">step</field>
<field name="parent_id" ref="anodize_alkaline_clean"/>
<field name="icon">fa-tint</field>
<field name="sequence">10</field>
</record>
<record id="anodize_alkaline_secondary_rinse" model="fusion.plating.process.node">
<field name="name">Secondary Rinse (Tank A-4)</field>
<field name="node_type">step</field>
<field name="parent_id" ref="anodize_alkaline_clean"/>
<field name="icon">fa-tint</field>
<field name="sequence">20</field>
</record>
<!-- 5c. Etch (Tank A-3) -->
<record id="anodize_etch" model="fusion.plating.process.node">
<field name="name">Etch (Tank A-3)</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="anodize_line"/>
<field name="icon">fa-flask</field>
<field name="sequence">30</field>
<field name="customer_visible">True</field>
</record>
<record id="anodize_etch_primary_rinse" model="fusion.plating.process.node">
<field name="name">Primary Rinse (Tank A-4)</field>
<field name="node_type">step</field>
<field name="parent_id" ref="anodize_etch"/>
<field name="icon">fa-tint</field>
<field name="sequence">10</field>
</record>
<record id="anodize_etch_secondary_rinse" model="fusion.plating.process.node">
<field name="name">Secondary Rinse (Tank A-6)</field>
<field name="node_type">step</field>
<field name="parent_id" ref="anodize_etch"/>
<field name="icon">fa-tint</field>
<field name="sequence">20</field>
</record>
<!-- 5d. Deoxidize (Tank A-5) -->
<record id="anodize_deoxidize" model="fusion.plating.process.node">
<field name="name">Deoxidize (Tank A-5)</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="anodize_line"/>
<field name="icon">fa-flask</field>
<field name="sequence">40</field>
<field name="customer_visible">True</field>
</record>
<record id="anodize_deoxidize_primary_rinse" model="fusion.plating.process.node">
<field name="name">Primary Rinse (Tank A-6)</field>
<field name="node_type">step</field>
<field name="parent_id" ref="anodize_deoxidize"/>
<field name="icon">fa-tint</field>
<field name="sequence">10</field>
</record>
<record id="anodize_deoxidize_secondary_rinse" model="fusion.plating.process.node">
<field name="name">Secondary Rinse (Tank A-8)</field>
<field name="node_type">step</field>
<field name="parent_id" ref="anodize_deoxidize"/>
<field name="icon">fa-tint</field>
<field name="sequence">20</field>
</record>
<!-- 5e. Sulfuric Anodize (operation container) -->
<record id="anodize_sulfuric" model="fusion.plating.process.node">
<field name="name">Sulfuric Anodize</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="anodize_line"/>
<field name="icon">fa-bolt</field>
<field name="sequence">50</field>
<field name="auto_complete">True</field>
</record>
<record id="anodize_sulfuric_ramp" model="fusion.plating.process.node">
<field name="name">Sulfuric Anodize Ramp (Tank A-9)</field>
<field name="node_type">step</field>
<field name="parent_id" ref="anodize_sulfuric"/>
<field name="icon">fa-bolt</field>
<field name="sequence">10</field>
</record>
<record id="anodize_sulfuric_do" model="fusion.plating.process.node">
<field name="name">Sulfuric Anodize (Tank A-9)</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="anodize_sulfuric"/>
<field name="icon">fa-bolt</field>
<field name="sequence">20</field>
<field name="requires_signoff">True</field>
<field name="customer_visible">True</field>
</record>
<record id="anodize_sulfuric_do_primary_rinse" model="fusion.plating.process.node">
<field name="name">Primary Rinse (Tank A-8)</field>
<field name="node_type">step</field>
<field name="parent_id" ref="anodize_sulfuric_do"/>
<field name="icon">fa-tint</field>
<field name="sequence">10</field>
</record>
<record id="anodize_sulfuric_do_secondary_rinse" model="fusion.plating.process.node">
<field name="name">Secondary Rinse (Tank A-12)</field>
<field name="node_type">step</field>
<field name="parent_id" ref="anodize_sulfuric_do"/>
<field name="icon">fa-tint</field>
<field name="sequence">20</field>
</record>
<record id="anodize_sulfuric_do_hot_rinse" model="fusion.plating.process.node">
<field name="name">Hot Rinse (Tank A-17)</field>
<field name="node_type">step</field>
<field name="parent_id" ref="anodize_sulfuric_do"/>
<field name="icon">fa-thermometer-half</field>
<field name="sequence">30</field>
</record>
<!-- 5f. Hot Water Seal (Tank A-16) -->
<record id="anodize_hot_water_seal" model="fusion.plating.process.node">
<field name="name">Hot Water Seal (Tank A-16)</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="anodize_line"/>
<field name="icon">fa-tint</field>
<field name="sequence">60</field>
<field name="customer_visible">True</field>
</record>
<record id="anodize_hot_water_seal_primary_rinse" model="fusion.plating.process.node">
<field name="name">Primary Rinse (Tank A-12)</field>
<field name="node_type">step</field>
<field name="parent_id" ref="anodize_hot_water_seal"/>
<field name="icon">fa-tint</field>
<field name="sequence">10</field>
</record>
<record id="anodize_hot_water_seal_hot_rinse" model="fusion.plating.process.node">
<field name="name">Hot Rinse (Tank A-17)</field>
<field name="node_type">step</field>
<field name="parent_id" ref="anodize_hot_water_seal"/>
<field name="icon">fa-thermometer-half</field>
<field name="sequence">20</field>
<field name="customer_visible">True</field>
</record>
<!-- ========================= 6. Anodize Dry ========================= -->
<record id="anodize_dry" model="fusion.plating.process.node">
<field name="name">Anodize Dry</field>
<field name="node_type">sub_process</field>
<field name="parent_id" ref="recipe_anodize"/>
<field name="icon">fa-sun-o</field>
<field name="sequence">60</field>
</record>
<!-- ========================= 7. Unracking ========================= -->
<record id="anodize_unracking" model="fusion.plating.process.node">
<field name="name">Unracking</field>
<field name="node_type">sub_process</field>
<field name="parent_id" ref="recipe_anodize"/>
<field name="icon">fa-th</field>
<field name="sequence">70</field>
<field name="auto_complete">True</field>
</record>
<record id="anodize_unracking_ready" model="fusion.plating.process.node">
<field name="name">Ready for Unrack</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="anodize_unracking"/>
<field name="icon">fa-clock-o</field>
<field name="sequence">10</field>
</record>
<record id="anodize_unracking_do" model="fusion.plating.process.node">
<field name="name">Unracking</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="anodize_unracking"/>
<field name="icon">fa-th</field>
<field name="sequence">20</field>
<field name="customer_visible">True</field>
</record>
<!-- ========================= 8. De-Masking ========================= -->
<record id="anodize_demasking" model="fusion.plating.process.node">
<field name="name">De-Masking</field>
<field name="node_type">sub_process</field>
<field name="parent_id" ref="recipe_anodize"/>
<field name="icon">fa-eraser</field>
<field name="sequence">80</field>
<field name="auto_complete">True</field>
<field name="requires_signoff">True</field>
</record>
<record id="anodize_demasking_ready" model="fusion.plating.process.node">
<field name="name">Ready for De-Masking</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="anodize_demasking"/>
<field name="icon">fa-clock-o</field>
<field name="sequence">10</field>
</record>
<record id="anodize_demasking_do" model="fusion.plating.process.node">
<field name="name">De-Masking</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="anodize_demasking"/>
<field name="icon">fa-eraser</field>
<field name="sequence">20</field>
<field name="customer_visible">True</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,266 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2026 Nexa Systems Inc.
License OPL-1 (Odoo Proprietary License v1.0)
Recipe: CHEM_CONVERSION (Chemical Conversion Process - Trivalent Chromate)
Source: Client's Steelhead export (April 2026 transcription).
Chemical conversion coating for aluminium (MIL-DTL-5541 Type II /
Trivalent chromate). Optional Nickel Strip lane handles stripping
re-work parts that had previous EN plating.
Tree:
CHEM_CONVERSION (recipe)
Racking (sub-process, auto)
Ready for Racking (operation)
Racking (operation, customer-visible)
Chemical Conversion (sub-process, auto)
Ready For Chemical Conversion (operation)
Drying (operation)
Soak Clean (A-1) (operation, customer-visible)
Rinse (A-2)
Etch (A-3) (operation, customer-visible)
Rinse (A-2)
Rinse (A-4)
Desmutter (A-5) (operation, customer-visible)
Rinse (A-4)
Rinse (A-6)
Trivalent Chromate Conversion (A-14 / A) (operation, signoff, customer-visible)
Rinse (A-15)
Plug The Threaded Holes (sub-process, signoff)
Nickel Strip - Aluminum Line (sub-process, signoff, auto)
Strip Process - AL (operation, auto)
Nitric Acid (A-13 / SP-10)
Final Rinse (A-14 / SP-11)
Drying (operation)
Post Stripping Inspection (operation, auto)
Ready for Post Stripping Inspection
Post Stripping Inspection (customer-visible)
-->
<odoo>
<data noupdate="0">
<!-- ========================= ROOT ========================= -->
<record id="recipe_chem_conversion" model="fusion.plating.process.node">
<field name="name">Chemical Conversion Process</field>
<field name="code">CHEM_CONVERSION</field>
<field name="node_type">recipe</field>
<field name="icon">fa-flask</field>
<field name="sequence">60</field>
<field name="auto_complete">True</field>
<field name="customer_visible">False</field>
</record>
<!-- ========================= 1. Racking ========================= -->
<record id="cc_racking" model="fusion.plating.process.node">
<field name="name">Racking</field>
<field name="node_type">sub_process</field>
<field name="parent_id" ref="recipe_chem_conversion"/>
<field name="icon">fa-th</field>
<field name="sequence">10</field>
<field name="auto_complete">True</field>
</record>
<record id="cc_racking_ready" model="fusion.plating.process.node">
<field name="name">Ready for Racking</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="cc_racking"/>
<field name="icon">fa-clock-o</field>
<field name="sequence">10</field>
</record>
<record id="cc_racking_do" model="fusion.plating.process.node">
<field name="name">Racking</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="cc_racking"/>
<field name="icon">fa-th</field>
<field name="sequence">20</field>
<field name="customer_visible">True</field>
</record>
<!-- ========================= 2. Chemical Conversion (sub-process) ========================= -->
<record id="cc_chem_conversion" model="fusion.plating.process.node">
<field name="name">Chemical Conversion</field>
<field name="node_type">sub_process</field>
<field name="parent_id" ref="recipe_chem_conversion"/>
<field name="icon">fa-industry</field>
<field name="sequence">20</field>
<field name="auto_complete">True</field>
</record>
<record id="cc_chem_ready" model="fusion.plating.process.node">
<field name="name">Ready For Chemical Conversion</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="cc_chem_conversion"/>
<field name="icon">fa-clock-o</field>
<field name="sequence">10</field>
</record>
<record id="cc_chem_drying" model="fusion.plating.process.node">
<field name="name">Drying</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="cc_chem_conversion"/>
<field name="icon">fa-sun-o</field>
<field name="sequence">20</field>
</record>
<!-- 2a. Soak Clean (A-1) -->
<record id="cc_soak_clean" model="fusion.plating.process.node">
<field name="name">Soak Clean (A-1)</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="cc_chem_conversion"/>
<field name="icon">fa-shower</field>
<field name="sequence">30</field>
<field name="customer_visible">True</field>
</record>
<record id="cc_soak_rinse" model="fusion.plating.process.node">
<field name="name">Rinse (A-2)</field>
<field name="node_type">step</field>
<field name="parent_id" ref="cc_soak_clean"/>
<field name="icon">fa-tint</field>
<field name="sequence">10</field>
</record>
<!-- 2b. Etch (A-3) -->
<record id="cc_etch" model="fusion.plating.process.node">
<field name="name">Etch (A-3)</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="cc_chem_conversion"/>
<field name="icon">fa-flask</field>
<field name="sequence">40</field>
<field name="customer_visible">True</field>
</record>
<record id="cc_etch_rinse1" model="fusion.plating.process.node">
<field name="name">Rinse (A-2)</field>
<field name="node_type">step</field>
<field name="parent_id" ref="cc_etch"/>
<field name="icon">fa-tint</field>
<field name="sequence">10</field>
</record>
<record id="cc_etch_rinse2" model="fusion.plating.process.node">
<field name="name">Rinse (A-4)</field>
<field name="node_type">step</field>
<field name="parent_id" ref="cc_etch"/>
<field name="icon">fa-tint</field>
<field name="sequence">20</field>
</record>
<!-- 2c. Desmutter (A-5) -->
<record id="cc_desmutter" model="fusion.plating.process.node">
<field name="name">Desmutter (A-5)</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="cc_chem_conversion"/>
<field name="icon">fa-flask</field>
<field name="sequence">50</field>
<field name="customer_visible">True</field>
</record>
<record id="cc_desmutter_rinse1" model="fusion.plating.process.node">
<field name="name">Rinse (A-4)</field>
<field name="node_type">step</field>
<field name="parent_id" ref="cc_desmutter"/>
<field name="icon">fa-tint</field>
<field name="sequence">10</field>
</record>
<record id="cc_desmutter_rinse2" model="fusion.plating.process.node">
<field name="name">Rinse (A-6)</field>
<field name="node_type">step</field>
<field name="parent_id" ref="cc_desmutter"/>
<field name="icon">fa-tint</field>
<field name="sequence">20</field>
</record>
<!-- 2d. Trivalent Chromate Conversion (A-14 / A) -->
<record id="cc_trivalent" model="fusion.plating.process.node">
<field name="name">Trivalent Chromate Conversion (A-14 / A)</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="cc_chem_conversion"/>
<field name="icon">fa-diamond</field>
<field name="sequence">60</field>
<field name="requires_signoff">True</field>
<field name="customer_visible">True</field>
</record>
<record id="cc_trivalent_rinse" model="fusion.plating.process.node">
<field name="name">Rinse (A-15)</field>
<field name="node_type">step</field>
<field name="parent_id" ref="cc_trivalent"/>
<field name="icon">fa-tint</field>
<field name="sequence">10</field>
</record>
<!-- ========================= 3. Plug The Threaded Holes ========================= -->
<record id="cc_plug_threaded" model="fusion.plating.process.node">
<field name="name">Plug The Threaded Holes</field>
<field name="node_type">sub_process</field>
<field name="parent_id" ref="recipe_chem_conversion"/>
<field name="icon">fa-shield</field>
<field name="sequence">30</field>
<field name="requires_signoff">True</field>
</record>
<!-- ========================= 4. Nickel Strip - Aluminum Line ========================= -->
<record id="cc_ni_strip" model="fusion.plating.process.node">
<field name="name">Nickel Strip - Aluminum Line</field>
<field name="node_type">sub_process</field>
<field name="parent_id" ref="recipe_chem_conversion"/>
<field name="icon">fa-eraser</field>
<field name="sequence">40</field>
<field name="auto_complete">True</field>
<field name="requires_signoff">True</field>
</record>
<!-- 4a. Strip Process - AL -->
<record id="cc_ni_strip_process" model="fusion.plating.process.node">
<field name="name">Strip Process - AL</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="cc_ni_strip"/>
<field name="icon">fa-flask</field>
<field name="sequence">10</field>
<field name="auto_complete">True</field>
</record>
<record id="cc_ni_strip_nitric" model="fusion.plating.process.node">
<field name="name">Nitric Acid (A-13 / SP-10)</field>
<field name="node_type">step</field>
<field name="parent_id" ref="cc_ni_strip_process"/>
<field name="icon">fa-flask</field>
<field name="sequence">10</field>
</record>
<record id="cc_ni_strip_final_rinse" model="fusion.plating.process.node">
<field name="name">Final Rinse (A-14 / SP-11)</field>
<field name="node_type">step</field>
<field name="parent_id" ref="cc_ni_strip_process"/>
<field name="icon">fa-tint</field>
<field name="sequence">20</field>
</record>
<!-- 4b. Drying -->
<record id="cc_ni_strip_drying" model="fusion.plating.process.node">
<field name="name">Drying</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="cc_ni_strip"/>
<field name="icon">fa-sun-o</field>
<field name="sequence">20</field>
</record>
<!-- 4c. Post Stripping Inspection -->
<record id="cc_ni_strip_inspect" model="fusion.plating.process.node">
<field name="name">Post Stripping Inspection</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="cc_ni_strip"/>
<field name="icon">fa-search</field>
<field name="sequence">30</field>
<field name="auto_complete">True</field>
</record>
<record id="cc_ni_strip_inspect_ready" model="fusion.plating.process.node">
<field name="name">Ready for Post Stripping Inspection</field>
<field name="node_type">step</field>
<field name="parent_id" ref="cc_ni_strip_inspect"/>
<field name="icon">fa-clock-o</field>
<field name="sequence">10</field>
</record>
<record id="cc_ni_strip_inspect_do" model="fusion.plating.process.node">
<field name="name">Post Stripping Inspection</field>
<field name="node_type">step</field>
<field name="parent_id" ref="cc_ni_strip_inspect"/>
<field name="icon">fa-eye</field>
<field name="sequence">20</field>
<field name="customer_visible">True</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,577 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2026 Nexa Systems Inc.
License OPL-1 (Odoo Proprietary License v1.0)
Recipe: ENP-SP (Electroless Nickel Plating - Special Process)
Source: Client's Steelhead export (April 2026 transcription).
Notes:
- Steelhead allows a node to appear at multiple positions in the
same recipe ("occurrence #2"). Our parent_id model is strict
single-parent so we duplicate the node — see "Oven baking"
appearing first as #1 and again later in the flow.
- The Electroless Nickel Plating sub-process holds the wet line;
everything inside it is a separate plating step (cleaner →
activation → strike → plate → drying).
Tree:
ENP-SP (recipe)
├── Oven baking (op, signoff, auto) — first oven cycle
│ ├── Ready for bake
│ └── Bake (customer-visible)
├── Adhesion Test Coupon (op, opt-out)
├── Blasting (op, signoff, auto)
│ ├── Ready For Blast
│ └── Blast (customer-visible)
├── Masking (op, signoff, auto)
│ ├── Ready For Masking
│ └── Masking (customer-visible)
├── Racking (op, auto)
│ ├── Ready for racking
│ └── Racking (customer-visible)
├── Ready For Plating (op)
├── Electroless Nickel Plating (sub-process, auto)
│ ├── Soak Clean (SP-1)
│ │ ├── ElectroClean (SP-1)
│ │ └── Rinse (SP-2)
│ ├── HCl Activation (SP-3)
│ │ └── Rinse (SP-6)
│ ├── Woods Nickel Strike (SP-5) (signoff)
│ │ └── Rinse (SP-6)
│ ├── E-Nickel Plate (Mid-Phos) (SP-7) (signoff)
│ │ └── Rinse (SP-11)
│ ├── E-Nickel Plate (Hi-Phos) (SP-8) (opt-out)
│ │ └── Rinse (SP-11)
│ └── Drying
├── Oven baking (op, signoff, auto) — second oven cycle (#2)
│ ├── Ready for bake
│ └── Bake (customer-visible)
├── De-racking (op, auto)
│ ├── Ready For DeRacking
│ └── DeRacking (customer-visible)
├── De-Masking (op, signoff, auto)
│ ├── Ready for De-Masking
│ └── De-Masking
├── Oven bake (Post de-rack) (op, opt-out, auto)
│ ├── Ready for bake
│ └── Bake (customer-visible)
├── Adhesion Testing (op, opt-out)
├── Post Plate Inspection (op, auto)
│ ├── Ready For Post Plate Inspection
│ └── Post Plate Inspection (customer-visible)
├── Salt Spray Masking (op, signoff, auto)
│ ├── Ready for Salt Spray Masking
│ └── Salt Spray Masking
├── Corrosion Testing (op, signoff, auto)
│ ├── Corrosion Testing
│ └── Corrosion Test Inspection
└── Lab Testing (op, signoff, auto)
├── Lab Testing
└── Lab Testing Results
-->
<odoo>
<data noupdate="0">
<!-- ========================= ROOT ========================= -->
<record id="recipe_enp_sp" model="fusion.plating.process.node">
<field name="name">ENP-SP</field>
<field name="code">ENP_SP_BASIC</field>
<field name="node_type">recipe</field>
<field name="icon">fa-flask</field>
<field name="sequence">30</field>
<field name="auto_complete">True</field>
<field name="customer_visible">False</field>
</record>
<!-- ========================= 1. Pre-plate Bake (Stress Relief) =========================
Done before plating on high-strength steel to relieve residual
machining/forming stresses (ASTM B850 / AMS 2759). -->
<record id="enp_sp_oven_baking_1" model="fusion.plating.process.node">
<field name="name">Pre-plate Bake (Stress Relief)</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="recipe_enp_sp"/>
<field name="icon">fa-fire</field>
<field name="sequence">10</field>
<field name="auto_complete">True</field>
<field name="requires_signoff">True</field>
<field name="is_manual">False</field>
</record>
<record id="enp_sp_oven_baking_1_ready" model="fusion.plating.process.node">
<field name="name">Ready for bake</field>
<field name="node_type">step</field>
<field name="parent_id" ref="enp_sp_oven_baking_1"/>
<field name="icon">fa-clock-o</field>
<field name="sequence">10</field>
<field name="customer_visible">False</field>
</record>
<record id="enp_sp_oven_baking_1_do" model="fusion.plating.process.node">
<field name="name">Bake</field>
<field name="node_type">step</field>
<field name="parent_id" ref="enp_sp_oven_baking_1"/>
<field name="icon">fa-fire</field>
<field name="sequence">20</field>
<field name="customer_visible">True</field>
</record>
<!-- ========================= 2. Adhesion Test Coupon ========================= -->
<record id="enp_sp_adhesion_coupon" model="fusion.plating.process.node">
<field name="name">Adhesion Test Coupon</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="recipe_enp_sp"/>
<field name="icon">fa-cube</field>
<field name="sequence">20</field>
<field name="opt_in_out">opt_out</field>
<field name="customer_visible">False</field>
</record>
<!-- ========================= 3. Blasting ========================= -->
<record id="enp_sp_blasting" model="fusion.plating.process.node">
<field name="name">Blasting</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="recipe_enp_sp"/>
<field name="icon">fa-bullseye</field>
<field name="sequence">30</field>
<field name="auto_complete">True</field>
<field name="requires_signoff">True</field>
<field name="is_manual">True</field>
</record>
<record id="enp_sp_blasting_ready" model="fusion.plating.process.node">
<field name="name">Ready For Blast</field>
<field name="node_type">step</field>
<field name="parent_id" ref="enp_sp_blasting"/>
<field name="icon">fa-clock-o</field>
<field name="sequence">10</field>
<field name="customer_visible">False</field>
</record>
<record id="enp_sp_blasting_do" model="fusion.plating.process.node">
<field name="name">Blast</field>
<field name="node_type">step</field>
<field name="parent_id" ref="enp_sp_blasting"/>
<field name="icon">fa-bullseye</field>
<field name="sequence">20</field>
<field name="customer_visible">True</field>
</record>
<!-- ========================= 4. Masking ========================= -->
<record id="enp_sp_masking" model="fusion.plating.process.node">
<field name="name">Masking</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="recipe_enp_sp"/>
<field name="icon">fa-paint-brush</field>
<field name="sequence">40</field>
<field name="auto_complete">True</field>
<field name="requires_signoff">True</field>
<field name="is_manual">True</field>
</record>
<record id="enp_sp_masking_ready" model="fusion.plating.process.node">
<field name="name">Ready For Masking</field>
<field name="node_type">step</field>
<field name="parent_id" ref="enp_sp_masking"/>
<field name="icon">fa-clock-o</field>
<field name="sequence">10</field>
<field name="customer_visible">False</field>
</record>
<record id="enp_sp_masking_do" model="fusion.plating.process.node">
<field name="name">Masking</field>
<field name="node_type">step</field>
<field name="parent_id" ref="enp_sp_masking"/>
<field name="icon">fa-paint-brush</field>
<field name="sequence">20</field>
<field name="customer_visible">True</field>
</record>
<!-- ========================= 5. Racking ========================= -->
<record id="enp_sp_racking" model="fusion.plating.process.node">
<field name="name">Racking</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="recipe_enp_sp"/>
<field name="icon">fa-th</field>
<field name="sequence">50</field>
<field name="auto_complete">True</field>
<field name="is_manual">True</field>
</record>
<record id="enp_sp_racking_ready" model="fusion.plating.process.node">
<field name="name">Ready for racking</field>
<field name="node_type">step</field>
<field name="parent_id" ref="enp_sp_racking"/>
<field name="icon">fa-clock-o</field>
<field name="sequence">10</field>
<field name="customer_visible">False</field>
</record>
<record id="enp_sp_racking_do" model="fusion.plating.process.node">
<field name="name">Racking</field>
<field name="node_type">step</field>
<field name="parent_id" ref="enp_sp_racking"/>
<field name="icon">fa-th</field>
<field name="sequence">20</field>
<field name="customer_visible">True</field>
</record>
<!-- ========================= 6. Ready For Plating ========================= -->
<record id="enp_sp_ready_plating" model="fusion.plating.process.node">
<field name="name">Ready For Plating</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="recipe_enp_sp"/>
<field name="icon">fa-clock-o</field>
<field name="sequence">60</field>
<field name="customer_visible">False</field>
</record>
<!-- ========================= 7. Electroless Nickel Plating (sub-process) ========================= -->
<record id="enp_sp_enp_line" model="fusion.plating.process.node">
<field name="name">Electroless Nickel Plating</field>
<field name="node_type">sub_process</field>
<field name="parent_id" ref="recipe_enp_sp"/>
<field name="icon">fa-industry</field>
<field name="sequence">70</field>
<field name="auto_complete">True</field>
</record>
<!-- 7a. Soak Clean (SP-1) -->
<record id="enp_sp_soak_clean" model="fusion.plating.process.node">
<field name="name">Soak Clean (SP-1)</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="enp_sp_enp_line"/>
<field name="icon">fa-shower</field>
<field name="sequence">10</field>
<field name="customer_visible">True</field>
</record>
<record id="enp_sp_electroclean" model="fusion.plating.process.node">
<field name="name">ElectroClean (SP-1)</field>
<field name="node_type">step</field>
<field name="parent_id" ref="enp_sp_soak_clean"/>
<field name="icon">fa-bolt</field>
<field name="sequence">10</field>
<field name="customer_visible">False</field>
</record>
<record id="enp_sp_soak_rinse" model="fusion.plating.process.node">
<field name="name">Rinse (SP-2)</field>
<field name="node_type">step</field>
<field name="parent_id" ref="enp_sp_soak_clean"/>
<field name="icon">fa-tint</field>
<field name="sequence">20</field>
<field name="customer_visible">True</field>
</record>
<!-- 7b. HCl Activation (SP-3) -->
<record id="enp_sp_hcl_activation" model="fusion.plating.process.node">
<field name="name">HCl Activation (SP-3)</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="enp_sp_enp_line"/>
<field name="icon">fa-flask</field>
<field name="sequence">20</field>
<field name="customer_visible">True</field>
</record>
<record id="enp_sp_hcl_rinse" model="fusion.plating.process.node">
<field name="name">Rinse (SP-6)</field>
<field name="node_type">step</field>
<field name="parent_id" ref="enp_sp_hcl_activation"/>
<field name="icon">fa-tint</field>
<field name="sequence">10</field>
<field name="customer_visible">True</field>
</record>
<!-- 7c. Woods Nickel Strike (SP-5) -->
<record id="enp_sp_woods_strike" model="fusion.plating.process.node">
<field name="name">Woods Nickel Strike (SP-5)</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="enp_sp_enp_line"/>
<field name="icon">fa-bolt</field>
<field name="sequence">30</field>
<field name="requires_signoff">True</field>
<field name="customer_visible">True</field>
</record>
<record id="enp_sp_woods_rinse" model="fusion.plating.process.node">
<field name="name">Rinse (SP-6)</field>
<field name="node_type">step</field>
<field name="parent_id" ref="enp_sp_woods_strike"/>
<field name="icon">fa-tint</field>
<field name="sequence">10</field>
<field name="customer_visible">True</field>
</record>
<!-- 7d. E-Nickel Plate (Mid-Phos) (SP-7) -->
<record id="enp_sp_enp_mid_phos" model="fusion.plating.process.node">
<field name="name">E-Nickel Plate (Mid-Phos) (SP-7)</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="enp_sp_enp_line"/>
<field name="icon">fa-diamond</field>
<field name="sequence">40</field>
<field name="requires_signoff">True</field>
<field name="customer_visible">True</field>
<field name="is_manual">False</field>
</record>
<record id="enp_sp_enp_mp_rinse" model="fusion.plating.process.node">
<field name="name">Rinse (SP-11)</field>
<field name="node_type">step</field>
<field name="parent_id" ref="enp_sp_enp_mid_phos"/>
<field name="icon">fa-tint</field>
<field name="sequence">10</field>
<field name="customer_visible">True</field>
</record>
<!-- 7e. E-Nickel Plate (Hi-Phos) (SP-8) — opt-out -->
<record id="enp_sp_enp_hi_phos" model="fusion.plating.process.node">
<field name="name">E-Nickel Plate (Hi-Phos) (SP-8)</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="enp_sp_enp_line"/>
<field name="icon">fa-diamond</field>
<field name="sequence">50</field>
<field name="opt_in_out">opt_out</field>
<field name="customer_visible">True</field>
<field name="is_manual">False</field>
</record>
<record id="enp_sp_enp_hp_rinse" model="fusion.plating.process.node">
<field name="name">Rinse (SP-11)</field>
<field name="node_type">step</field>
<field name="parent_id" ref="enp_sp_enp_hi_phos"/>
<field name="icon">fa-tint</field>
<field name="sequence">10</field>
<field name="customer_visible">True</field>
</record>
<!-- 7f. Drying -->
<record id="enp_sp_drying" model="fusion.plating.process.node">
<field name="name">Drying</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="enp_sp_enp_line"/>
<field name="icon">fa-sun-o</field>
<field name="sequence">60</field>
<field name="customer_visible">True</field>
</record>
<!-- ========================= 8. Post-plate Bake (H2 Embrittlement Relief) =========================
Drives out hydrogen absorbed during plating. Must START within
~4 hours of plate exit (ASTM B850 / AMS 2759). Parts still on
rack — de-rack happens after this bake. -->
<record id="enp_sp_oven_baking_2" model="fusion.plating.process.node">
<field name="name">Post-plate Bake (H2 Embrittlement Relief)</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="recipe_enp_sp"/>
<field name="icon">fa-fire</field>
<field name="sequence">80</field>
<field name="auto_complete">True</field>
<field name="requires_signoff">True</field>
<field name="is_manual">False</field>
</record>
<record id="enp_sp_oven_baking_2_ready" model="fusion.plating.process.node">
<field name="name">Ready for bake</field>
<field name="node_type">step</field>
<field name="parent_id" ref="enp_sp_oven_baking_2"/>
<field name="icon">fa-clock-o</field>
<field name="sequence">10</field>
<field name="customer_visible">False</field>
</record>
<record id="enp_sp_oven_baking_2_do" model="fusion.plating.process.node">
<field name="name">Bake</field>
<field name="node_type">step</field>
<field name="parent_id" ref="enp_sp_oven_baking_2"/>
<field name="icon">fa-fire</field>
<field name="sequence">20</field>
<field name="customer_visible">True</field>
</record>
<!-- ========================= 9. De-racking ========================= -->
<record id="enp_sp_deracking" model="fusion.plating.process.node">
<field name="name">De-racking</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="recipe_enp_sp"/>
<field name="icon">fa-th</field>
<field name="sequence">90</field>
<field name="auto_complete">True</field>
<field name="is_manual">True</field>
</record>
<record id="enp_sp_deracking_ready" model="fusion.plating.process.node">
<field name="name">Ready For DeRacking</field>
<field name="node_type">step</field>
<field name="parent_id" ref="enp_sp_deracking"/>
<field name="icon">fa-clock-o</field>
<field name="sequence">10</field>
<field name="customer_visible">False</field>
</record>
<record id="enp_sp_deracking_do" model="fusion.plating.process.node">
<field name="name">DeRacking</field>
<field name="node_type">step</field>
<field name="parent_id" ref="enp_sp_deracking"/>
<field name="icon">fa-hand-paper-o</field>
<field name="sequence">20</field>
<field name="customer_visible">True</field>
</record>
<!-- ========================= 10. De-Masking ========================= -->
<record id="enp_sp_demasking" model="fusion.plating.process.node">
<field name="name">De-Masking</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="recipe_enp_sp"/>
<field name="icon">fa-eraser</field>
<field name="sequence">100</field>
<field name="auto_complete">True</field>
<field name="requires_signoff">True</field>
<field name="is_manual">True</field>
</record>
<record id="enp_sp_demasking_ready" model="fusion.plating.process.node">
<field name="name">Ready for De-Masking</field>
<field name="node_type">step</field>
<field name="parent_id" ref="enp_sp_demasking"/>
<field name="icon">fa-clock-o</field>
<field name="sequence">10</field>
<field name="customer_visible">False</field>
</record>
<record id="enp_sp_demasking_do" model="fusion.plating.process.node">
<field name="name">De-Masking</field>
<field name="node_type">step</field>
<field name="parent_id" ref="enp_sp_demasking"/>
<field name="icon">fa-eraser</field>
<field name="sequence">20</field>
<field name="customer_visible">False</field>
</record>
<!-- ========================= 11. Oven bake (Post de-rack) ========================= -->
<record id="enp_sp_oven_post" model="fusion.plating.process.node">
<field name="name">Oven bake (Post de-rack)</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="recipe_enp_sp"/>
<field name="icon">fa-fire</field>
<field name="sequence">110</field>
<field name="auto_complete">True</field>
<field name="opt_in_out">opt_out</field>
<field name="is_manual">False</field>
</record>
<record id="enp_sp_oven_post_ready" model="fusion.plating.process.node">
<field name="name">Ready for bake</field>
<field name="node_type">step</field>
<field name="parent_id" ref="enp_sp_oven_post"/>
<field name="icon">fa-clock-o</field>
<field name="sequence">10</field>
<field name="customer_visible">False</field>
</record>
<record id="enp_sp_oven_post_do" model="fusion.plating.process.node">
<field name="name">Bake</field>
<field name="node_type">step</field>
<field name="parent_id" ref="enp_sp_oven_post"/>
<field name="icon">fa-fire</field>
<field name="sequence">20</field>
<field name="customer_visible">True</field>
</record>
<!-- ========================= 12. Adhesion Testing ========================= -->
<record id="enp_sp_adhesion_testing" model="fusion.plating.process.node">
<field name="name">Adhesion Testing</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="recipe_enp_sp"/>
<field name="icon">fa-check-circle</field>
<field name="sequence">120</field>
<field name="opt_in_out">opt_out</field>
<field name="customer_visible">True</field>
</record>
<!-- ========================= 13. Post Plate Inspection ========================= -->
<record id="enp_sp_post_inspection" model="fusion.plating.process.node">
<field name="name">Post Plate Inspection</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="recipe_enp_sp"/>
<field name="icon">fa-search</field>
<field name="sequence">130</field>
<field name="auto_complete">True</field>
</record>
<record id="enp_sp_post_inspection_ready" model="fusion.plating.process.node">
<field name="name">Ready For Post Plate Inspection</field>
<field name="node_type">step</field>
<field name="parent_id" ref="enp_sp_post_inspection"/>
<field name="icon">fa-clock-o</field>
<field name="sequence">10</field>
<field name="customer_visible">False</field>
</record>
<record id="enp_sp_post_inspection_do" model="fusion.plating.process.node">
<field name="name">Post Plate Inspection</field>
<field name="node_type">step</field>
<field name="parent_id" ref="enp_sp_post_inspection"/>
<field name="icon">fa-search</field>
<field name="sequence">20</field>
<field name="customer_visible">True</field>
</record>
<!-- ========================= 14. Salt Spray Masking ========================= -->
<record id="enp_sp_salt_masking" model="fusion.plating.process.node">
<field name="name">Salt Spray Masking</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="recipe_enp_sp"/>
<field name="icon">fa-paint-brush</field>
<field name="sequence">140</field>
<field name="auto_complete">True</field>
<field name="requires_signoff">True</field>
<field name="is_manual">True</field>
</record>
<record id="enp_sp_salt_masking_ready" model="fusion.plating.process.node">
<field name="name">Ready for Salt Spray Masking</field>
<field name="node_type">step</field>
<field name="parent_id" ref="enp_sp_salt_masking"/>
<field name="icon">fa-clock-o</field>
<field name="sequence">10</field>
<field name="customer_visible">False</field>
</record>
<record id="enp_sp_salt_masking_do" model="fusion.plating.process.node">
<field name="name">Salt Spray Masking</field>
<field name="node_type">step</field>
<field name="parent_id" ref="enp_sp_salt_masking"/>
<field name="icon">fa-paint-brush</field>
<field name="sequence">20</field>
<field name="customer_visible">False</field>
</record>
<!-- ========================= 15. Corrosion Testing ========================= -->
<record id="enp_sp_corrosion_testing" model="fusion.plating.process.node">
<field name="name">Corrosion Testing</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="recipe_enp_sp"/>
<field name="icon">fa-search</field>
<field name="sequence">150</field>
<field name="auto_complete">True</field>
<field name="requires_signoff">True</field>
</record>
<record id="enp_sp_corrosion_test_do" model="fusion.plating.process.node">
<field name="name">Corrosion Testing</field>
<field name="node_type">step</field>
<field name="parent_id" ref="enp_sp_corrosion_testing"/>
<field name="icon">fa-flask</field>
<field name="sequence">10</field>
<field name="customer_visible">True</field>
</record>
<record id="enp_sp_corrosion_test_inspect" model="fusion.plating.process.node">
<field name="name">Corrosion Test Inspection</field>
<field name="node_type">step</field>
<field name="parent_id" ref="enp_sp_corrosion_testing"/>
<field name="icon">fa-search</field>
<field name="sequence">20</field>
<field name="customer_visible">True</field>
</record>
<!-- ========================= 16. Lab Testing ========================= -->
<record id="enp_sp_lab_testing" model="fusion.plating.process.node">
<field name="name">Lab Testing</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="recipe_enp_sp"/>
<field name="icon">fa-flask</field>
<field name="sequence">160</field>
<field name="auto_complete">True</field>
<field name="requires_signoff">True</field>
</record>
<record id="enp_sp_lab_test_do" model="fusion.plating.process.node">
<field name="name">Lab Testing</field>
<field name="node_type">step</field>
<field name="parent_id" ref="enp_sp_lab_testing"/>
<field name="icon">fa-flask</field>
<field name="sequence">10</field>
<field name="customer_visible">True</field>
</record>
<record id="enp_sp_lab_test_results" model="fusion.plating.process.node">
<field name="name">Lab Testing Results</field>
<field name="node_type">step</field>
<field name="parent_id" ref="enp_sp_lab_testing"/>
<field name="icon">fa-search</field>
<field name="sequence">20</field>
<field name="customer_visible">True</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,469 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2026 Nexa Systems Inc.
License OPL-1 (Odoo Proprietary License v1.0)
Recipe: ENP-STEEL-BASIC (Electroless Nickel Plating - Steel Line)
Source: Client's Steelhead export (April 2026 transcription).
Tree:
ENP-STEEL-BASIC (recipe)
├── Blasting (op, signoff, auto)
│ ├── Ready For Blast
│ └── Blast (signoff, customer-visible)
├── Masking (op, signoff, auto)
│ ├── Ready For Masking
│ └── Masking (customer-visible)
├── Racking (op, auto)
│ ├── Ready for Racking
│ └── Racking (customer-visible)
├── Ready For Steel Line (op)
├── Steel Line (sub-process)
│ ├── Cleaner (op, auto)
│ │ ├── Soak Clean (S-3) (customer-visible)
│ │ ├── Electroclean (S-3)
│ │ └── Primary Rinse (S-4) (customer-visible)
│ ├── Acid Dip (S-5) (op, customer-visible)
│ │ ├── Primary Rinse (S-6) (customer-visible)
│ │ └── Secondary Rinse (S-8) (customer-visible)
│ ├── Nickel Strike (S-7 / SP-5) (op, signoff, customer-visible)
│ │ └── Rinse (S-8 / SP-6) (customer-visible)
│ ├── E-Nickel Plate (Mid Phos)(S-9) (op, opt-out, customer-visible)
│ │ ├── Primary Rinse (S-11) (customer-visible)
│ │ └── Hot Rinse (S-13) (customer-visible)
│ ├── E-Nickel Plate (S-10) (op, signoff, customer-visible)
│ │ ├── Primary Rinse (S-11) (customer-visible)
│ │ └── Hot Rinse (S-13) (customer-visible)
│ ├── Hot Water Porosity (A-15) (op, signoff, customer-visible)
│ └── Dry (op, customer-visible)
├── Oven baking (op, signoff, auto)
│ ├── Ready for bake
│ └── Bake (customer-visible)
├── De-racking (op, auto)
│ ├── Ready For DeRacking
│ └── DeRacking (customer-visible)
├── De-Masking (op, signoff, auto)
│ ├── Ready for De-Masking
│ └── De-Masking
├── Oven bake (Post de-rack) (op, opt-out, auto)
│ ├── Ready for bake
│ └── Bake (customer-visible)
└── Post Plate Inspection (op, auto)
├── Ready For Post Plate Inspection
└── Post Plate Inspection (customer-visible)
-->
<odoo>
<data noupdate="0">
<!-- ========================= ROOT ========================= -->
<record id="recipe_enp_steel_basic" model="fusion.plating.process.node">
<field name="name">ENP-STEEL-BASIC</field>
<field name="code">ENP_STEEL_BASIC</field>
<field name="node_type">recipe</field>
<field name="icon">fa-flask</field>
<field name="sequence">20</field>
<field name="auto_complete">True</field>
<field name="customer_visible">False</field>
</record>
<!-- ========================= 1. Blasting ========================= -->
<record id="enp_sb_blasting" model="fusion.plating.process.node">
<field name="name">Blasting</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="recipe_enp_steel_basic"/>
<field name="icon">fa-bullseye</field>
<field name="sequence">10</field>
<field name="auto_complete">True</field>
<field name="requires_signoff">True</field>
<field name="is_manual">True</field>
</record>
<record id="enp_sb_blasting_ready" model="fusion.plating.process.node">
<field name="name">Ready For Blast</field>
<field name="node_type">step</field>
<field name="parent_id" ref="enp_sb_blasting"/>
<field name="icon">fa-clock-o</field>
<field name="sequence">10</field>
<field name="customer_visible">False</field>
</record>
<record id="enp_sb_blasting_do" model="fusion.plating.process.node">
<field name="name">Blast</field>
<field name="node_type">step</field>
<field name="parent_id" ref="enp_sb_blasting"/>
<field name="icon">fa-bullseye</field>
<field name="sequence">20</field>
<field name="requires_signoff">True</field>
<field name="customer_visible">True</field>
</record>
<!-- ========================= 2. Masking ========================= -->
<record id="enp_sb_masking" model="fusion.plating.process.node">
<field name="name">Masking</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="recipe_enp_steel_basic"/>
<field name="icon">fa-paint-brush</field>
<field name="sequence">20</field>
<field name="auto_complete">True</field>
<field name="requires_signoff">True</field>
<field name="is_manual">True</field>
</record>
<record id="enp_sb_masking_ready" model="fusion.plating.process.node">
<field name="name">Ready For Masking</field>
<field name="node_type">step</field>
<field name="parent_id" ref="enp_sb_masking"/>
<field name="icon">fa-clock-o</field>
<field name="sequence">10</field>
<field name="customer_visible">False</field>
</record>
<record id="enp_sb_masking_do" model="fusion.plating.process.node">
<field name="name">Masking</field>
<field name="node_type">step</field>
<field name="parent_id" ref="enp_sb_masking"/>
<field name="icon">fa-paint-brush</field>
<field name="sequence">20</field>
<field name="customer_visible">True</field>
</record>
<!-- ========================= 3. Racking ========================= -->
<record id="enp_sb_racking" model="fusion.plating.process.node">
<field name="name">Racking</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="recipe_enp_steel_basic"/>
<field name="icon">fa-th</field>
<field name="sequence">30</field>
<field name="auto_complete">True</field>
<field name="is_manual">True</field>
</record>
<record id="enp_sb_racking_ready" model="fusion.plating.process.node">
<field name="name">Ready for Racking</field>
<field name="node_type">step</field>
<field name="parent_id" ref="enp_sb_racking"/>
<field name="icon">fa-clock-o</field>
<field name="sequence">10</field>
<field name="customer_visible">False</field>
</record>
<record id="enp_sb_racking_do" model="fusion.plating.process.node">
<field name="name">Racking</field>
<field name="node_type">step</field>
<field name="parent_id" ref="enp_sb_racking"/>
<field name="icon">fa-th</field>
<field name="sequence">20</field>
<field name="customer_visible">True</field>
</record>
<!-- ========================= 4. Ready For Steel Line ========================= -->
<record id="enp_sb_ready_steel_line" model="fusion.plating.process.node">
<field name="name">Ready For Steel Line</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="recipe_enp_steel_basic"/>
<field name="icon">fa-clock-o</field>
<field name="sequence">40</field>
<field name="customer_visible">False</field>
</record>
<!-- ========================= 5. Steel Line (sub-process) ========================= -->
<record id="enp_sb_steel_line" model="fusion.plating.process.node">
<field name="name">Steel Line</field>
<field name="node_type">sub_process</field>
<field name="parent_id" ref="recipe_enp_steel_basic"/>
<field name="icon">fa-industry</field>
<field name="sequence">50</field>
<field name="auto_complete">True</field>
</record>
<!-- 5a. Cleaner -->
<record id="enp_sb_sl_cleaner" model="fusion.plating.process.node">
<field name="name">Cleaner</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="enp_sb_steel_line"/>
<field name="icon">fa-shower</field>
<field name="sequence">10</field>
<field name="auto_complete">True</field>
</record>
<record id="enp_sb_sl_soak_clean" model="fusion.plating.process.node">
<field name="name">Soak Clean (S-3)</field>
<field name="node_type">step</field>
<field name="parent_id" ref="enp_sb_sl_cleaner"/>
<field name="icon">fa-tint</field>
<field name="sequence">10</field>
<field name="customer_visible">True</field>
</record>
<record id="enp_sb_sl_electroclean" model="fusion.plating.process.node">
<field name="name">Electroclean (S-3)</field>
<field name="node_type">step</field>
<field name="parent_id" ref="enp_sb_sl_cleaner"/>
<field name="icon">fa-bolt</field>
<field name="sequence">20</field>
<field name="customer_visible">False</field>
</record>
<record id="enp_sb_sl_primary_rinse_s4" model="fusion.plating.process.node">
<field name="name">Primary Rinse (S-4)</field>
<field name="node_type">step</field>
<field name="parent_id" ref="enp_sb_sl_cleaner"/>
<field name="icon">fa-tint</field>
<field name="sequence">30</field>
<field name="customer_visible">True</field>
</record>
<!-- 5b. Acid Dip (S-5) -->
<record id="enp_sb_sl_acid_dip" model="fusion.plating.process.node">
<field name="name">Acid Dip (S-5)</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="enp_sb_steel_line"/>
<field name="icon">fa-flask</field>
<field name="sequence">20</field>
<field name="customer_visible">True</field>
</record>
<record id="enp_sb_sl_acid_rinse_s6" model="fusion.plating.process.node">
<field name="name">Primary Rinse (S-6)</field>
<field name="node_type">step</field>
<field name="parent_id" ref="enp_sb_sl_acid_dip"/>
<field name="icon">fa-tint</field>
<field name="sequence">10</field>
<field name="customer_visible">True</field>
</record>
<record id="enp_sb_sl_acid_rinse_s8" model="fusion.plating.process.node">
<field name="name">Secondary Rinse (S-8)</field>
<field name="node_type">step</field>
<field name="parent_id" ref="enp_sb_sl_acid_dip"/>
<field name="icon">fa-tint</field>
<field name="sequence">20</field>
<field name="customer_visible">True</field>
</record>
<!-- 5c. Nickel Strike (S-7 / SP-5) -->
<record id="enp_sb_sl_nickel_strike" model="fusion.plating.process.node">
<field name="name">Nickel Strike (S-7 / SP-5)</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="enp_sb_steel_line"/>
<field name="icon">fa-bolt</field>
<field name="sequence">30</field>
<field name="requires_signoff">True</field>
<field name="customer_visible">True</field>
</record>
<record id="enp_sb_sl_strike_rinse" model="fusion.plating.process.node">
<field name="name">Rinse (S-8 / SP-6)</field>
<field name="node_type">step</field>
<field name="parent_id" ref="enp_sb_sl_nickel_strike"/>
<field name="icon">fa-tint</field>
<field name="sequence">10</field>
<field name="customer_visible">True</field>
</record>
<!-- 5d. E-Nickel Plate (Mid Phos)(S-9) — opt-out (chosen per job) -->
<record id="enp_sb_sl_enp_mid_phos" model="fusion.plating.process.node">
<field name="name">E-Nickel Plate (Mid Phos)(S-9)</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="enp_sb_steel_line"/>
<field name="icon">fa-diamond</field>
<field name="sequence">40</field>
<field name="opt_in_out">opt_out</field>
<field name="customer_visible">True</field>
<field name="is_manual">False</field>
</record>
<record id="enp_sb_sl_enp_mp_rinse_s11" model="fusion.plating.process.node">
<field name="name">Primary Rinse (S-11)</field>
<field name="node_type">step</field>
<field name="parent_id" ref="enp_sb_sl_enp_mid_phos"/>
<field name="icon">fa-tint</field>
<field name="sequence">10</field>
<field name="customer_visible">True</field>
</record>
<record id="enp_sb_sl_enp_mp_hot_rinse" model="fusion.plating.process.node">
<field name="name">Hot Rinse (S-13)</field>
<field name="node_type">step</field>
<field name="parent_id" ref="enp_sb_sl_enp_mid_phos"/>
<field name="icon">fa-thermometer-half</field>
<field name="sequence">20</field>
<field name="customer_visible">True</field>
</record>
<!-- 5e. E-Nickel Plate (S-10) — primary plating -->
<record id="enp_sb_sl_enp_s10" model="fusion.plating.process.node">
<field name="name">E-Nickel Plate (S-10)</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="enp_sb_steel_line"/>
<field name="icon">fa-diamond</field>
<field name="sequence">50</field>
<field name="requires_signoff">True</field>
<field name="customer_visible">True</field>
<field name="is_manual">False</field>
</record>
<record id="enp_sb_sl_enp_s10_rinse_s11" model="fusion.plating.process.node">
<field name="name">Primary Rinse (S-11)</field>
<field name="node_type">step</field>
<field name="parent_id" ref="enp_sb_sl_enp_s10"/>
<field name="icon">fa-tint</field>
<field name="sequence">10</field>
<field name="customer_visible">True</field>
</record>
<record id="enp_sb_sl_enp_s10_hot_rinse" model="fusion.plating.process.node">
<field name="name">Hot Rinse (S-13)</field>
<field name="node_type">step</field>
<field name="parent_id" ref="enp_sb_sl_enp_s10"/>
<field name="icon">fa-thermometer-half</field>
<field name="sequence">20</field>
<field name="customer_visible">True</field>
</record>
<!-- 5f. Hot Water Porosity (A-15) -->
<record id="enp_sb_sl_hot_water_porosity" model="fusion.plating.process.node">
<field name="name">Hot Water Porosity (A-15)</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="enp_sb_steel_line"/>
<field name="icon">fa-tint</field>
<field name="sequence">60</field>
<field name="requires_signoff">True</field>
<field name="customer_visible">True</field>
</record>
<!-- 5g. Dry -->
<record id="enp_sb_sl_dry" model="fusion.plating.process.node">
<field name="name">Dry</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="enp_sb_steel_line"/>
<field name="icon">fa-sun-o</field>
<field name="sequence">70</field>
<field name="customer_visible">True</field>
</record>
<!-- ========================= 6. Post-plate Bake (H2 Embrittlement Relief) =========================
Drives out hydrogen absorbed during plating. Must START within
~4 hours of plate exit (ASTM B850 / AMS 2759). Parts still on
rack — de-rack happens after this bake. -->
<record id="enp_sb_oven_baking" model="fusion.plating.process.node">
<field name="name">Post-plate Bake (H2 Embrittlement Relief)</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="recipe_enp_steel_basic"/>
<field name="icon">fa-fire</field>
<field name="sequence">60</field>
<field name="auto_complete">True</field>
<field name="requires_signoff">True</field>
<field name="is_manual">False</field>
</record>
<record id="enp_sb_oven_baking_ready" model="fusion.plating.process.node">
<field name="name">Ready for bake</field>
<field name="node_type">step</field>
<field name="parent_id" ref="enp_sb_oven_baking"/>
<field name="icon">fa-clock-o</field>
<field name="sequence">10</field>
<field name="customer_visible">False</field>
</record>
<record id="enp_sb_oven_baking_do" model="fusion.plating.process.node">
<field name="name">Bake</field>
<field name="node_type">step</field>
<field name="parent_id" ref="enp_sb_oven_baking"/>
<field name="icon">fa-fire</field>
<field name="sequence">20</field>
<field name="customer_visible">True</field>
</record>
<!-- ========================= 7. De-racking ========================= -->
<record id="enp_sb_deracking" model="fusion.plating.process.node">
<field name="name">De-racking</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="recipe_enp_steel_basic"/>
<field name="icon">fa-th</field>
<field name="sequence">70</field>
<field name="auto_complete">True</field>
<field name="is_manual">True</field>
</record>
<record id="enp_sb_deracking_ready" model="fusion.plating.process.node">
<field name="name">Ready For DeRacking</field>
<field name="node_type">step</field>
<field name="parent_id" ref="enp_sb_deracking"/>
<field name="icon">fa-clock-o</field>
<field name="sequence">10</field>
<field name="customer_visible">False</field>
</record>
<record id="enp_sb_deracking_do" model="fusion.plating.process.node">
<field name="name">DeRacking</field>
<field name="node_type">step</field>
<field name="parent_id" ref="enp_sb_deracking"/>
<field name="icon">fa-hand-paper-o</field>
<field name="sequence">20</field>
<field name="customer_visible">True</field>
</record>
<!-- ========================= 8. De-Masking ========================= -->
<record id="enp_sb_demasking" model="fusion.plating.process.node">
<field name="name">De-Masking</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="recipe_enp_steel_basic"/>
<field name="icon">fa-eraser</field>
<field name="sequence">80</field>
<field name="auto_complete">True</field>
<field name="requires_signoff">True</field>
<field name="is_manual">True</field>
</record>
<record id="enp_sb_demasking_ready" model="fusion.plating.process.node">
<field name="name">Ready for De-Masking</field>
<field name="node_type">step</field>
<field name="parent_id" ref="enp_sb_demasking"/>
<field name="icon">fa-clock-o</field>
<field name="sequence">10</field>
<field name="customer_visible">False</field>
</record>
<record id="enp_sb_demasking_do" model="fusion.plating.process.node">
<field name="name">De-Masking</field>
<field name="node_type">step</field>
<field name="parent_id" ref="enp_sb_demasking"/>
<field name="icon">fa-eraser</field>
<field name="sequence">20</field>
<field name="customer_visible">False</field>
</record>
<!-- ========================= 9. Oven bake (Post de-rack) ========================= -->
<record id="enp_sb_oven_post" model="fusion.plating.process.node">
<field name="name">Oven bake (Post de-rack)</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="recipe_enp_steel_basic"/>
<field name="icon">fa-fire</field>
<field name="sequence">90</field>
<field name="auto_complete">True</field>
<field name="opt_in_out">opt_out</field>
<field name="is_manual">False</field>
</record>
<record id="enp_sb_oven_post_ready" model="fusion.plating.process.node">
<field name="name">Ready for bake</field>
<field name="node_type">step</field>
<field name="parent_id" ref="enp_sb_oven_post"/>
<field name="icon">fa-clock-o</field>
<field name="sequence">10</field>
<field name="customer_visible">False</field>
</record>
<record id="enp_sb_oven_post_do" model="fusion.plating.process.node">
<field name="name">Bake</field>
<field name="node_type">step</field>
<field name="parent_id" ref="enp_sb_oven_post"/>
<field name="icon">fa-fire</field>
<field name="sequence">20</field>
<field name="customer_visible">True</field>
</record>
<!-- ========================= 10. Post Plate Inspection ========================= -->
<record id="enp_sb_post_inspection" model="fusion.plating.process.node">
<field name="name">Post Plate Inspection</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="recipe_enp_steel_basic"/>
<field name="icon">fa-search</field>
<field name="sequence">100</field>
<field name="auto_complete">True</field>
</record>
<record id="enp_sb_post_inspection_ready" model="fusion.plating.process.node">
<field name="name">Ready For Post Plate Inspection</field>
<field name="node_type">step</field>
<field name="parent_id" ref="enp_sb_post_inspection"/>
<field name="icon">fa-clock-o</field>
<field name="sequence">10</field>
<field name="customer_visible">False</field>
</record>
<record id="enp_sb_post_inspection_do" model="fusion.plating.process.node">
<field name="name">Post Plate Inspection</field>
<field name="node_type">step</field>
<field name="parent_id" ref="enp_sb_post_inspection"/>
<field name="icon">fa-search</field>
<field name="sequence">20</field>
<field name="customer_visible">True</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,169 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2026 Nexa Systems Inc.
License OPL-1 (Odoo Proprietary License v1.0)
Recipe: GENERAL_PROCESSING (General Processing — common workflow umbrella)
Source: Client's Steelhead export (April 2026 transcription).
This is the "envelope" workflow that sits AROUND every job: contract
review (sales/quoting checkpoint), incoming inspection, scheduling,
final inspection, and shipping. Specific plating recipes (ENP-STEEL,
ENP-SP, ENP-ALUM, etc.) handle the technical processing in between.
Steelhead-only features that did NOT migrate (we don't have these
wired up yet — let me know if you want them):
- "Default Lead Time" on the recipe
- "Product" linkage from the recipe to a product/service record
- "Contract Review Users" — list of users who must approve
contract review before the job can advance
- Treatment Groups / "Use Price Builders" hook into pricing
Tree:
GENERAL_PROCESSING (recipe)
├── Contract Review (op, opt-out, customer-visible)
├── Incoming Inspection (op, auto)
│ ├── Ready for Incoming Inspection
│ └── Incoming Inspection (customer-visible)
├── Scheduling (op)
├── Final Inspection / Packaging (op, auto)
│ ├── Ready For Final Inspection / Packaging
│ └── Final Inspection / Packaging (customer-visible)
└── Shipping (op, auto)
├── Ready For Shipping
├── Packing Slip Created
└── Shipped (customer-visible)
-->
<odoo>
<data noupdate="0">
<!-- ========================= ROOT ========================= -->
<record id="recipe_general_processing" model="fusion.plating.process.node">
<field name="name">General Processing</field>
<field name="code">GENERAL_PROCESSING</field>
<field name="node_type">recipe</field>
<field name="icon">fa-sitemap</field>
<field name="sequence">40</field>
<field name="auto_complete">True</field>
<field name="customer_visible">False</field>
</record>
<!-- ========================= 1. Contract Review ========================= -->
<!-- Sales / quoting checkpoint. Marked opt-out so it can be
skipped on repeat orders where contract terms haven't
changed (per Steelhead's setup). -->
<record id="gp_contract_review" model="fusion.plating.process.node">
<field name="name">Contract Review</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="recipe_general_processing"/>
<field name="icon">fa-check-circle</field>
<field name="sequence">10</field>
<field name="opt_in_out">opt_out</field>
<field name="customer_visible">True</field>
<field name="is_manual">True</field>
</record>
<!-- ========================= 2. Incoming Inspection ========================= -->
<record id="gp_incoming_inspection" model="fusion.plating.process.node">
<field name="name">Incoming Inspection</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="recipe_general_processing"/>
<field name="icon">fa-search</field>
<field name="sequence">20</field>
<field name="auto_complete">True</field>
<field name="is_manual">True</field>
</record>
<record id="gp_incoming_ready" model="fusion.plating.process.node">
<field name="name">Ready for Incoming Inspection</field>
<field name="node_type">step</field>
<field name="parent_id" ref="gp_incoming_inspection"/>
<field name="icon">fa-clock-o</field>
<field name="sequence">10</field>
<field name="customer_visible">False</field>
</record>
<record id="gp_incoming_do" model="fusion.plating.process.node">
<field name="name">Incoming Inspection</field>
<field name="node_type">step</field>
<field name="parent_id" ref="gp_incoming_inspection"/>
<field name="icon">fa-eye</field>
<field name="sequence">20</field>
<field name="customer_visible">True</field>
</record>
<!-- ========================= 3. Scheduling ========================= -->
<record id="gp_scheduling" model="fusion.plating.process.node">
<field name="name">Scheduling</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="recipe_general_processing"/>
<field name="icon">fa-clock-o</field>
<field name="sequence">30</field>
<field name="customer_visible">False</field>
<field name="is_manual">True</field>
</record>
<!-- ========================= 4. Final Inspection / Packaging ========================= -->
<record id="gp_final_inspection" model="fusion.plating.process.node">
<field name="name">Final Inspection / Packaging</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="recipe_general_processing"/>
<field name="icon">fa-cube</field>
<field name="sequence">40</field>
<field name="auto_complete">True</field>
<field name="is_manual">True</field>
</record>
<record id="gp_final_ready" model="fusion.plating.process.node">
<field name="name">Ready For Final Inspection / Packaging</field>
<field name="node_type">step</field>
<field name="parent_id" ref="gp_final_inspection"/>
<field name="icon">fa-clock-o</field>
<field name="sequence">10</field>
<field name="customer_visible">False</field>
</record>
<record id="gp_final_do" model="fusion.plating.process.node">
<field name="name">Final Inspection / Packaging</field>
<field name="node_type">step</field>
<field name="parent_id" ref="gp_final_inspection"/>
<field name="icon">fa-cube</field>
<field name="sequence">20</field>
<field name="customer_visible">True</field>
</record>
<!-- ========================= 5. Shipping ========================= -->
<!-- Steelhead shows a truck icon here; our icon list doesn't
include fa-truck so we fall back to fa-cube (the parts
leaving the door). -->
<record id="gp_shipping" model="fusion.plating.process.node">
<field name="name">Shipping</field>
<field name="node_type">operation</field>
<field name="parent_id" ref="recipe_general_processing"/>
<field name="icon">fa-cube</field>
<field name="sequence">50</field>
<field name="auto_complete">True</field>
<field name="is_manual">True</field>
</record>
<record id="gp_shipping_ready" model="fusion.plating.process.node">
<field name="name">Ready For Shipping</field>
<field name="node_type">step</field>
<field name="parent_id" ref="gp_shipping"/>
<field name="icon">fa-clock-o</field>
<field name="sequence">10</field>
<field name="customer_visible">False</field>
</record>
<record id="gp_shipping_packing_slip" model="fusion.plating.process.node">
<field name="name">Packing Slip Created</field>
<field name="node_type">step</field>
<field name="parent_id" ref="gp_shipping"/>
<field name="icon">fa-cube</field>
<field name="sequence">20</field>
<field name="customer_visible">False</field>
</record>
<record id="gp_shipping_shipped" model="fusion.plating.process.node">
<field name="name">Shipped</field>
<field name="node_type">step</field>
<field name="parent_id" ref="gp_shipping"/>
<field name="icon">fa-cube</field>
<field name="sequence">30</field>
<field name="customer_visible">True</field>
</record>
</data>
</odoo>

View File

@@ -199,6 +199,41 @@ class FpProcessNode(models.Model):
tracking=True,
)
# ---- Recipe-only fields (apply when node_type='recipe') -----------------
# These migrate Steelhead's recipe-level metadata: lead time, the
# product/service tied to this recipe, the contract review approver
# roster, and the pricing builders to apply when this recipe is on
# a quote. They're loose-coupled to keep non-recipe nodes clean.
default_lead_time = fields.Float(
string='Default Lead Time (days)',
digits=(8, 2),
help='When an MO is created using this recipe, '
'date_planned_finished is set to NOW + lead_time.',
tracking=True,
)
product_id = fields.Many2one(
'product.product',
string='Service / Product',
ondelete='set null',
help='The plating service product this recipe sells. When the '
'product appears on a sale order, the resulting MO can '
'auto-pick this recipe.',
tracking=True,
)
contract_review_user_ids = fields.Many2many(
'res.users',
relation='fp_process_node_contract_review_user_rel',
column1='node_id',
column2='user_id',
string='Contract Review Approvers',
help='Users authorised to sign off the Contract Review work order '
'on jobs running this recipe. Anyone outside this list will '
'be blocked from finishing the WO.',
)
# NB. `pricing_rule_ids` lives in fusion_plating_configurator
# (added there so this core module doesn't depend on the configurator).
# ---- Computed fields -----------------------------------------------------
display_name = fields.Char(
@@ -270,6 +305,73 @@ class FpProcessNode(models.Model):
raise ValidationError(
_('A process node cannot be its own ancestor.'))
# ---- Version auto-bump ---------------------------------------------------
# Any meaningful edit / add / delete inside a recipe bumps the recipe
# root's `version` field by one. Lets shop managers see at a glance
# how stable a recipe is and (later) lets a job pin to a specific
# recipe revision so already-running MOs don't see mid-flight changes.
# Fields that don't represent a "meaningful" change — adjusting these
# alone does not bump the version. `version` itself is in the list to
# avoid an infinite write loop.
_FP_NON_VERSIONED_FIELDS = {
'version', 'write_date', 'write_uid',
'create_date', 'create_uid',
'parent_path', 'display_name', 'recipe_root_id', 'depth',
}
def _fp_bump_recipe_versions(self):
"""Increment `version` by 1 on the distinct recipe roots covering
the current recordset."""
roots = self.mapped('recipe_root_id')
# _compute_recipe_root_id falls back to self for nodes whose
# parent_path isn't yet stored — pick those up too.
for rec in self:
if not rec.recipe_root_id and rec.node_type == 'recipe':
roots |= rec
if not roots:
return
# Use a direct SQL update so we (a) skip our own write override
# and (b) avoid touching write_date / write_uid on the root,
# which would itself be a no-op-but-noisy chatter event.
self.env.cr.execute(
'UPDATE fusion_plating_process_node '
'SET version = COALESCE(version, 0) + 1 '
'WHERE id IN %s',
(tuple(roots.ids),),
)
roots.invalidate_recordset(['version'])
@api.model_create_multi
def create(self, vals_list):
records = super().create(vals_list)
# Skip non-recipe roots — only count when the new node lives
# inside an existing recipe.
descendants = records.filtered(lambda r: r.node_type != 'recipe')
if descendants:
descendants._fp_bump_recipe_versions()
return records
def write(self, vals):
meaningful = bool(set(vals.keys()) - self._FP_NON_VERSIONED_FIELDS)
res = super().write(vals)
if meaningful and self:
self._fp_bump_recipe_versions()
return res
def unlink(self):
# Snapshot the affected recipe roots BEFORE delete, otherwise
# recipe_root_id becomes unreachable on the deleted records.
roots = self.mapped('recipe_root_id')
descendants = self.filtered(lambda r: r.node_type != 'recipe')
# Delete first so we don't bump the version of a recipe that's
# being removed entirely.
res = super().unlink()
survivors = roots.exists()
if descendants and survivors:
survivors._fp_bump_recipe_versions()
return res
# ---- Tree data for OWL component -----------------------------------------
def get_tree_data(self):

View File

@@ -136,9 +136,16 @@ export class RecipeTreeEditor extends Component {
if (result && result.ok) {
this.state.recipe = result.recipe;
this.state.tree = result.tree;
// Auto-expand root node
if (result.tree) {
this.state.expandedNodes[result.tree.id] = true;
// Auto-expand every node on first load so the full
// hierarchy is visible. The horizontal bracket layout
// works best when everything is open by default;
// operators can still collapse individual branches.
if (result.tree && Object.keys(this.state.expandedNodes).length === 0) {
const expandAll = (n) => {
this.state.expandedNodes[n.id] = true;
for (const c of (n.children || [])) expandAll(c);
};
expandAll(result.tree);
}
// Refresh selected node data if panel is open
if (this.state.selectedNodeId) {

View File

@@ -3,41 +3,177 @@
Copyright 2026 Nexa Systems Inc.
License OPL-1 (Odoo Proprietary License v1.0)
Part of the Fusion Plating product family.
Recipe Tree Editor — horizontal hierarchical layout (Steelhead-style).
Recursive template renders recipe → sub-process → operation → step
cards left→right with bracket connectors. Each card carries hover-
revealed Add / Delete buttons; a side panel slides in for editing
when a node is clicked.
-->
<templates xml:space="preserve">
<!-- ====================================================================
RECURSIVE NODE TEMPLATE
Expects: node, parentNode (or null), isFirst (bool)
==================================================================== -->
<t t-name="fusion_plating.RecipeTreeNode">
<div class="o_fp_re_node">
<!-- Card -->
<div t-att-class="'o_fp_re_card o_fp_re_type_' + node.node_type
+ (state.selectedNodeId === node.id ? ' o_fp_re_selected' : '')"
t-att-draggable="node.node_type !== 'recipe' ? 'true' : 'false'"
t-on-dragstart="(ev) => this.onNodeDragStart(node, parentNode, ev)"
t-on-dragend="(ev) => this.onNodeDragEnd(ev)"
t-on-dragover="(ev) => this.onNodeDragOver(node, ev)"
t-on-dragleave="(ev) => this.onNodeDragLeave(ev)"
t-on-drop="(ev) => this.onNodeDrop(node, parentNode, ev)"
t-on-click.stop="() => this.selectNode(node)">
<!-- Drag handle (left grip) -->
<i class="o_fp_re_handle fa fa-bars"
t-if="node.node_type !== 'recipe'"/>
<!-- Icon + name -->
<i t-attf-class="o_fp_re_icon fa #{ node.icon || 'fa-cog' }"/>
<div class="o_fp_re_card_body">
<div class="o_fp_re_title" t-esc="node.name"/>
<div class="o_fp_re_meta"
t-if="node.work_center or node.estimated_duration or node.input_count">
<span t-if="node.work_center">
<i class="fa fa-building me-1"/><t t-esc="node.work_center"/>
</span>
<span t-if="node.estimated_duration">
· <i class="fa fa-clock-o me-1"/><t t-esc="formatDuration(node.estimated_duration)"/>
</span>
<span t-if="node.input_count">
· <i class="fa fa-keyboard-o me-1"/><t t-esc="node.input_count"/>
</span>
</div>
</div>
<!-- Right side: type pill + capability icons + actions -->
<div class="o_fp_re_right">
<!-- Capability flags as small icons -->
<span class="o_fp_re_flags">
<i class="fa fa-check-square" t-if="node.requires_signoff" title="Requires sign-off"/>
<i class="fa fa-eye" t-if="node.customer_visible" title="Customer visible"/>
<i class="fa fa-magic" t-if="node.auto_complete" title="Auto-complete"/>
<i class="fa fa-bolt" t-if="!node.is_manual" title="Automated"/>
</span>
<!-- Type pill -->
<span t-attf-class="o_fp_re_type_pill o_fp_re_type_pill_#{ node.node_type }"
t-esc="getNodeTypeMeta(node.node_type).label"/>
<!-- Hover-revealed actions -->
<span class="o_fp_re_actions">
<button class="o_fp_re_btn o_fp_re_btn_add"
t-on-click.stop="() => this.startAddChild(node.id)"
title="Add child step">
<i class="fa fa-plus"/>
</button>
<button class="o_fp_re_btn o_fp_re_btn_del"
t-if="node.node_type !== 'recipe'"
t-on-click.stop="() => this.deleteNode(node.id)"
title="Delete">
<i class="fa fa-trash"/>
</button>
</span>
</div>
</div>
<!-- Children + inline-add form -->
<div class="o_fp_re_children"
t-if="(node.children and node.children.length and isExpanded(node.id)) or state.addingTo === node.id">
<t t-foreach="node.children || []" t-as="child" t-key="child.id">
<t t-call="fusion_plating.RecipeTreeNode">
<t t-set="node" t-value="child"/>
<t t-set="parentNode" t-value="node"/>
<t t-set="isFirst" t-value="false"/>
</t>
</t>
<!-- Inline add form (sits as the last child of `node`) -->
<div class="o_fp_re_node o_fp_re_add_form"
t-if="state.addingTo === node.id">
<div class="o_fp_re_card o_fp_re_card_add">
<i class="o_fp_re_icon fa fa-plus-circle"/>
<div class="o_fp_re_card_body">
<input type="text" class="o_fp_re_add_input"
placeholder="New step name..."
autofocus="autofocus"
t-att-value="state.newNodeName"
t-on-input="(ev) => { state.newNodeName = ev.target.value; }"
t-on-keydown="onAddNameKey"
t-on-click.stop=""/>
<div class="o_fp_re_add_row">
<select class="o_fp_re_add_select"
t-on-change="(ev) => { state.newNodeType = ev.target.value; }"
t-on-click.stop="">
<t t-foreach="getNodeTypeOptions()" t-as="opt" t-key="opt.value">
<option t-att-value="opt.value"
t-att-selected="state.newNodeType === opt.value"
t-esc="opt.label"/>
</t>
</select>
<button class="o_fp_re_btn o_fp_re_btn_confirm"
t-on-click.stop="confirmAdd">
<i class="fa fa-check"/>
</button>
<button class="o_fp_re_btn o_fp_re_btn_cancel"
t-on-click.stop="cancelAdd">
<i class="fa fa-times"/>
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Collapsed indicator: small chip showing # of hidden children -->
<div class="o_fp_re_collapsed_chip"
t-if="node.children and node.children.length and !isExpanded(node.id)"
t-on-click.stop="() => this.toggleExpand(node.id)">
<i class="fa fa-plus-square-o me-1"/>
<t t-esc="node.children.length"/> hidden
</div>
</div>
</t>
<!-- ====================================================================
ROOT TEMPLATE
==================================================================== -->
<t t-name="fusion_plating.RecipeTreeEditor">
<div class="o_fp_recipe_editor">
<div class="o_fp_recipe_editor o_fp_re_v2">
<!-- ========== HEADER ========== -->
<div class="o_fp_recipe_header">
<div class="o_fp_recipe_header_left">
<button class="btn btn-link o_fp_recipe_back_btn"
t-on-click="onBackToList" title="Back to list">
<i class="fa fa-arrow-left me-1"/> Recipes
</button>
<h2 class="o_fp_recipe_title" t-if="state.recipe">
<i class="fa fa-flask me-2"/>
<t t-esc="state.recipe.name"/>
<span class="badge rounded-pill o_fp_recipe_version_badge ms-2"
t-if="state.recipe.version">
<div class="o_fp_re_header">
<button class="o_fp_re_back"
t-on-click="onBackToList"
title="Back to recipes">
<i class="fa fa-arrow-left me-2"/>Recipes
</button>
<div class="o_fp_re_header_title" t-if="state.recipe">
<h2 class="o_fp_re_h2 mb-0">
<i class="fa fa-flask me-2"/><t t-esc="state.recipe.name"/>
<span t-if="state.recipe.version" class="o_fp_re_ver">
v<t t-esc="state.recipe.version"/>
</span>
</h2>
<div class="o_fp_re_subtitle" t-if="state.recipe.process_type">
<i class="fa fa-tag me-1"/><t t-esc="state.recipe.process_type"/>
</div>
</div>
<div class="o_fp_recipe_header_right" t-if="state.recipe">
<span class="text-muted small me-3" t-if="state.recipe.process_type">
<i class="fa fa-tag me-1"/>
<t t-esc="state.recipe.process_type"/>
</span>
<button class="btn btn-sm btn-outline-secondary me-1"
t-on-click="onDuplicate" title="Duplicate recipe">
<i class="fa fa-copy me-1"/> Duplicate
<div class="o_fp_re_header_actions" t-if="state.recipe">
<button class="o_fp_re_btn_outline"
t-on-click="onDuplicate"
title="Duplicate recipe">
<i class="fa fa-copy me-1"/>Duplicate
</button>
<button class="btn btn-sm btn-outline-primary"
<button class="o_fp_re_btn_outline"
t-on-click="() => this.onOpenForm(state.recipe.id)"
title="Edit in form view">
<i class="fa fa-pencil me-1"/> Form View
<i class="fa fa-pencil me-1"/>Form
</button>
</div>
</div>
@@ -48,17 +184,17 @@
<p class="mt-2 text-muted">Loading recipe tree...</p>
</div>
<!-- ========== NO RECIPE ========== -->
<div class="text-center py-5" t-if="!state.loading and !_recipeId">
<i class="fa fa-exclamation-triangle fa-3x text-muted"/>
<p class="mt-3 text-muted">No recipe selected.</p>
<!-- ========== EMPTY ========== -->
<div class="o_fp_re_empty" t-if="!state.loading and !_recipeId">
<i class="fa fa-exclamation-triangle"/>
<div>No recipe selected.</div>
</div>
<!-- ========== TREE + PANEL LAYOUT ========== -->
<div class="o_fp_recipe_body" t-if="state.tree">
<!-- ========== BODY (canvas + side panel) ========== -->
<div class="o_fp_re_body" t-if="state.tree">
<!-- Tree area -->
<div class="o_fp_recipe_tree_area">
<!-- Tree canvas -->
<div class="o_fp_re_canvas">
<t t-call="fusion_plating.RecipeTreeNode">
<t t-set="node" t-value="state.tree"/>
<t t-set="parentNode" t-value="null"/>
@@ -67,26 +203,29 @@
</div>
<!-- Side panel -->
<div t-att-class="'o_fp_recipe_panel' + (state.showPanel ? ' o_fp_recipe_panel_open' : '')">
<div t-att-class="'o_fp_re_panel' + (state.showPanel ? ' o_fp_re_panel_open' : '')">
<t t-if="state.showPanel and state.selectedNode">
<div class="o_fp_recipe_panel_header">
<div class="o_fp_re_panel_head">
<h5>
<i t-att-class="'fa ' + (state.selectedNode.icon || 'fa-cog') + ' me-2'"/>
<i t-attf-class="fa #{ state.selectedNode.icon || 'fa-cog' } me-2"/>
Edit Node
</h5>
<button class="btn btn-sm btn-link" t-on-click="closePanel">
<button class="o_fp_re_btn o_fp_re_btn_cancel"
t-on-click="closePanel">
<i class="fa fa-times"/>
</button>
</div>
<div class="o_fp_recipe_panel_body">
<div class="mb-3">
<label class="form-label fw-bold">Name</label>
<div class="o_fp_re_panel_body">
<div class="o_fp_re_field">
<label>Name</label>
<input type="text" class="form-control"
t-att-value="state.selectedNode.name"
t-on-change="(ev) => { state.selectedNode.name = ev.target.value; }"/>
</div>
<div class="mb-3">
<label class="form-label fw-bold">Type</label>
<div class="o_fp_re_field">
<label>Type</label>
<select class="form-select"
t-on-change="(ev) => { state.selectedNode.node_type = ev.target.value; }">
<option value="recipe"
@@ -99,53 +238,57 @@
t-att-selected="state.selectedNode.node_type === 'step'">Step</option>
</select>
</div>
<div class="mb-3">
<label class="form-label fw-bold">Icon</label>
<div class="o_fp_recipe_icon_picker">
<div class="o_fp_re_field">
<label>Icon</label>
<div class="o_fp_re_icon_picker">
<t t-foreach="getIconOptions()" t-as="ic" t-key="ic.value">
<button t-att-class="'o_fp_recipe_icon_btn' + (state.selectedNode.icon === ic.value ? ' active' : '')"
<button t-att-class="'o_fp_re_icon_btn' + (state.selectedNode.icon === ic.value ? ' active' : '')"
t-on-click.stop="() => { state.selectedNode.icon = ic.value; }"
t-att-title="ic.label">
<i t-att-class="'fa ' + ic.value"/>
<i t-attf-class="fa #{ ic.value }"/>
</button>
</t>
</div>
</div>
<div class="mb-3">
<label class="form-label fw-bold">Duration (min)</label>
<div class="o_fp_re_field">
<label>Estimated Duration (min)</label>
<input type="number" class="form-control" min="0" step="1"
t-att-value="state.selectedNode.estimated_duration || 0"
t-on-change="(ev) => { state.selectedNode.estimated_duration = parseFloat(ev.target.value) || 0; }"/>
</div>
<div class="mb-3">
<label class="form-label fw-bold d-block">Flags</label>
<div class="o_fp_re_field">
<label>Flags</label>
<div class="form-check">
<input type="checkbox" class="form-check-input" id="fp_chk_manual"
<input type="checkbox" class="form-check-input" id="fp_re_chk_manual"
t-att-checked="state.selectedNode.is_manual"
t-on-change="(ev) => { state.selectedNode.is_manual = ev.target.checked; }"/>
<label class="form-check-label" for="fp_chk_manual">Manual operation</label>
<label class="form-check-label" for="fp_re_chk_manual">Manual operation</label>
</div>
<div class="form-check">
<input type="checkbox" class="form-check-input" id="fp_chk_auto"
<input type="checkbox" class="form-check-input" id="fp_re_chk_auto"
t-att-checked="state.selectedNode.auto_complete"
t-on-change="(ev) => { state.selectedNode.auto_complete = ev.target.checked; }"/>
<label class="form-check-label" for="fp_chk_auto">Auto-complete</label>
<label class="form-check-label" for="fp_re_chk_auto">Auto-complete</label>
</div>
<div class="form-check">
<input type="checkbox" class="form-check-input" id="fp_chk_signoff"
<input type="checkbox" class="form-check-input" id="fp_re_chk_signoff"
t-att-checked="state.selectedNode.requires_signoff"
t-on-change="(ev) => { state.selectedNode.requires_signoff = ev.target.checked; }"/>
<label class="form-check-label" for="fp_chk_signoff">Requires sign-off</label>
<label class="form-check-label" for="fp_re_chk_signoff">Requires sign-off</label>
</div>
<div class="form-check">
<input type="checkbox" class="form-check-input" id="fp_chk_visible"
<input type="checkbox" class="form-check-input" id="fp_re_chk_visible"
t-att-checked="state.selectedNode.customer_visible"
t-on-change="(ev) => { state.selectedNode.customer_visible = ev.target.checked; }"/>
<label class="form-check-label" for="fp_chk_visible">Customer visible</label>
<label class="form-check-label" for="fp_re_chk_visible">Customer visible</label>
</div>
</div>
<div class="mb-3">
<label class="form-label fw-bold">Opt In/Out</label>
<div class="o_fp_re_field">
<label>Opt In/Out</label>
<select class="form-select"
t-on-change="(ev) => { state.selectedNode.opt_in_out = ev.target.value; }">
<option value="disabled"
@@ -156,30 +299,16 @@
t-att-selected="state.selectedNode.opt_in_out === 'opt_out'">Opt-Out</option>
</select>
</div>
<!-- Info -->
<div class="text-muted small mb-2" t-if="state.selectedNode.work_center">
<i class="fa fa-building me-1"/>
<t t-esc="state.selectedNode.work_center"/>
</div>
<div class="text-muted small mb-2" t-if="state.selectedNode.process_type">
<i class="fa fa-tag me-1"/>
<t t-esc="state.selectedNode.process_type"/>
</div>
<div class="text-muted small mb-2"
t-if="state.selectedNode.input_count">
<i class="fa fa-keyboard-o me-1"/>
<t t-esc="state.selectedNode.input_count"/> operator input(s)
</div>
<!-- Tracking -->
<div class="o_fp_recipe_tracking mt-3 pt-3" t-if="state.selectedNode.create_date">
<div class="text-muted small mb-1">
<div class="o_fp_re_tracking" t-if="state.selectedNode.create_date">
<div>
<i class="fa fa-calendar-plus-o me-1"/>
Created <t t-esc="formatTimeAgo(state.selectedNode.create_date)"/>
<t t-if="state.selectedNode.create_uid_name">
by <strong t-esc="state.selectedNode.create_uid_name"/>
</t>
</div>
<div class="text-muted small" t-if="state.selectedNode.write_date">
<div t-if="state.selectedNode.write_date">
<i class="fa fa-pencil me-1"/>
Updated <t t-esc="formatTimeAgo(state.selectedNode.write_date)"/>
<t t-if="state.selectedNode.write_uid_name">
@@ -187,15 +316,15 @@
</t>
</div>
</div>
<!-- Actions -->
<div class="d-flex gap-2 mt-4">
<button class="btn btn-primary flex-fill"
<div class="o_fp_re_panel_actions">
<button class="o_fp_re_btn_save"
t-on-click="saveNode"
t-att-disabled="state.saving">
<i t-att-class="state.saving ? 'fa fa-spinner fa-spin me-1' : 'fa fa-check me-1'"/>
Save
</button>
<button class="btn btn-outline-secondary"
<button class="o_fp_re_btn_outline"
t-on-click="() => this.onOpenForm(state.selectedNode.id)"
title="Open full form">
<i class="fa fa-external-link"/>
@@ -208,127 +337,4 @@
</div>
</t>
<!-- ========== RECURSIVE NODE TEMPLATE ========== -->
<t t-name="fusion_plating.RecipeTreeNode">
<!-- Connector line (skip for root) -->
<div class="o_fp_recipe_connector" t-if="!isFirst"/>
<!-- Node card -->
<div t-att-class="'o_fp_recipe_node'
+ (state.selectedNodeId === node.id ? ' o_fp_recipe_node_selected' : '')
+ ' o_fp_recipe_node_' + node.node_type"
t-att-draggable="node.node_type !== 'recipe' ? 'true' : 'false'"
t-on-dragstart="(ev) => this.onNodeDragStart(node, parentNode, ev)"
t-on-dragend="(ev) => this.onNodeDragEnd(ev)"
t-on-dragover="(ev) => this.onNodeDragOver(node, ev)"
t-on-dragleave="(ev) => this.onNodeDragLeave(ev)"
t-on-drop="(ev) => this.onNodeDrop(node, parentNode, ev)"
t-on-click.stop="() => this.selectNode(node)">
<!-- Drag handle (non-root only) -->
<span class="o_fp_recipe_drag_handle" t-if="node.node_type !== 'recipe'">
<i class="fa fa-grip-vertical"/>
</span>
<!-- Node header row -->
<div class="o_fp_recipe_node_header">
<!-- Expand/collapse toggle -->
<button class="o_fp_recipe_toggle_btn"
t-if="node.children and node.children.length"
t-on-click.stop="() => this.toggleExpand(node.id)">
<i t-att-class="isExpanded(node.id) ? 'fa fa-chevron-down' : 'fa fa-chevron-right'"/>
</button>
<span class="o_fp_recipe_toggle_spacer" t-else=""/>
<!-- Icon -->
<i t-att-class="'o_fp_recipe_node_icon fa ' + (node.icon || 'fa-cog')"/>
<!-- Name -->
<span class="o_fp_recipe_node_name">
<t t-esc="node.name"/>
</span>
<!-- Type badge -->
<span t-att-class="'badge o_fp_recipe_node_badge ' + getNodeTypeMeta(node.node_type).badgeClass">
<t t-esc="getNodeTypeMeta(node.node_type).label"/>
</span>
</div>
<!-- Meta row: work centre, duration, capability icons -->
<div class="o_fp_recipe_node_meta">
<span class="o_fp_recipe_node_wc" t-if="node.work_center">
<i class="fa fa-building me-1"/>
<t t-esc="node.work_center"/>
</span>
<span class="o_fp_recipe_node_duration" t-if="node.estimated_duration">
<i class="fa fa-clock-o me-1"/>
<t t-esc="formatDuration(node.estimated_duration)"/>
</span>
<!-- Capability icons -->
<span class="o_fp_recipe_node_icons">
<i class="fa fa-hand-paper-o" t-if="node.is_manual" title="Manual"/>
<i class="fa fa-bolt" t-if="!node.is_manual" title="Automated"/>
<i class="fa fa-check-square" t-if="node.requires_signoff" title="Requires sign-off"/>
<i class="fa fa-eye" t-if="node.customer_visible" title="Customer visible"/>
<i class="fa fa-magic" t-if="node.auto_complete" title="Auto-complete"/>
</span>
</div>
<!-- Action buttons row -->
<div class="o_fp_recipe_node_actions">
<button class="btn btn-sm o_fp_recipe_add_btn"
t-on-click.stop="() => this.startAddChild(node.id)"
title="Add child step">
<i class="fa fa-plus me-1"/> Add Step
</button>
<button class="btn btn-sm o_fp_recipe_delete_btn"
t-if="node.node_type !== 'recipe'"
t-on-click.stop="() => this.deleteNode(node.id)"
title="Delete">
<i class="fa fa-trash"/>
</button>
</div>
</div>
<!-- Add child inline form -->
<div class="o_fp_recipe_add_form" t-if="state.addingTo === node.id">
<div class="o_fp_recipe_connector"/>
<div class="o_fp_recipe_add_card">
<input type="text" class="form-control form-control-sm mb-2"
placeholder="New step name..."
t-att-value="state.newNodeName"
t-on-input="(ev) => { state.newNodeName = ev.target.value; }"
t-on-keydown="onAddNameKey"/>
<div class="d-flex gap-2">
<select class="form-select form-select-sm flex-shrink-1"
style="max-width: 140px;"
t-on-change="(ev) => { state.newNodeType = ev.target.value; }">
<t t-foreach="getNodeTypeOptions()" t-as="opt" t-key="opt.value">
<option t-att-value="opt.value"
t-att-selected="state.newNodeType === opt.value"
t-esc="opt.label"/>
</t>
</select>
<button class="btn btn-sm btn-primary" t-on-click="confirmAdd">
<i class="fa fa-check"/>
</button>
<button class="btn btn-sm btn-outline-secondary" t-on-click="cancelAdd">
<i class="fa fa-times"/>
</button>
</div>
</div>
</div>
<!-- Children (recursive) -->
<div class="o_fp_recipe_children" t-if="node.children and node.children.length and isExpanded(node.id)">
<t t-foreach="node.children" t-as="child" t-key="child.id">
<t t-call="fusion_plating.RecipeTreeNode">
<t t-set="node" t-value="child"/>
<t t-set="parentNode" t-value="node"/>
<t t-set="isFirst" t-value="false"/>
</t>
</t>
</div>
</t>
</templates>

View File

@@ -79,6 +79,24 @@
<field name="active" invisible="True"/>
</group>
</group>
<!-- Recipe-only metadata (lead time, product link,
contract review approvers). Hidden on
operation/step nodes since those values are
only meaningful at the recipe root. -->
<group string="Recipe Settings"
invisible="node_type != 'recipe'">
<group>
<field name="default_lead_time"
widget="float_time"/>
<field name="product_id"
options="{'no_create': True}"/>
</group>
<group>
<field name="contract_review_user_ids"
widget="many2many_tags"
options="{'no_create': True}"/>
</group>
</group>
<group>
<group string="Tracking">
<field name="create_date" string="Created"/>