This commit is contained in:
gsinghpal
2026-04-13 02:35:35 -04:00
parent 1176ba68ae
commit 0ff8c0b93f
116 changed files with 14227 additions and 2406 deletions

View File

@@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2026 Nexa Systems Inc.
# License OPL-1 (Odoo Proprietary License v1.0)
from . import models

View File

@@ -0,0 +1,56 @@
# -*- coding: utf-8 -*-
# Copyright 2026 Nexa Systems Inc.
# License OPL-1 (Odoo Proprietary License v1.0)
{
'name': 'Fusion Plating — Maintenance Bridge',
'version': '19.0.1.0.0',
'category': 'Manufacturing/Plating',
'summary': 'Bridge standard Odoo Maintenance with Fusion Plating equipment, '
'plans, checklists, and sensor integration.',
'description': """
Fusion Plating — Maintenance Bridge
====================================
Extends Odoo's standard Maintenance module for electroless nickel plating
and metal finishing operations. Replaces Steelhead Software CMMS.
* Maintenance plans (templates linked to equipment categories)
* Checklist nodes (individual items within a plan)
* Labour cost tracking on maintenance events
* "From last maintenance" recurrence mode
* Equipment linked to Fusion Plating tanks and facilities
* Optional sensor measurement bridge (soft dependency)
Part of the Fusion Plating product family by Nexa Systems Inc.
Copyright (c) 2026 Nexa Systems Inc. All rights reserved.
""",
'author': 'Nexa Systems Inc.',
'website': 'https://www.nexasystems.ca',
'maintainer': 'Nexa Systems Inc.',
'support': 'support@nexasystems.ca',
'license': 'OPL-1',
'price': 0.00,
'currency': 'CAD',
'depends': [
'fusion_plating',
'maintenance',
],
'data': [
'security/fp_maintenance_security.xml',
'security/ir.model.access.csv',
'data/fp_maintenance_stage_data.xml',
'data/fp_maintenance_sequence_data.xml',
'data/fp_equipment_category_data.xml',
'views/fp_maintenance_plan_views.xml',
'views/fp_maintenance_node_views.xml',
'views/maintenance_request_views.xml',
'views/maintenance_equipment_views.xml',
'views/fp_maintenance_dashboard_views.xml',
'views/fp_maintenance_menu.xml',
],
'installable': True,
'application': False,
'auto_install': False,
}

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<!-- Seed equipment categories from Steelhead -->
<record id="equip_cat_al_tanks" model="maintenance.equipment.category">
<field name="name">AL Tanks</field>
</record>
<record id="equip_cat_specialty_tanks" model="maintenance.equipment.category">
<field name="name">Specialty Tanks</field>
</record>
<record id="equip_cat_steel_tanks" model="maintenance.equipment.category">
<field name="name">Steel Tanks</field>
</record>
<record id="equip_cat_waste_water" model="maintenance.equipment.category">
<field name="name">Waste Water</field>
</record>
<record id="equip_cat_waste_water_treatment" model="maintenance.equipment.category">
<field name="name">Waste Water Treatment</field>
</record>
<record id="equip_cat_common" model="maintenance.equipment.category">
<field name="name">common</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="seq_fp_maintenance_plan" model="ir.sequence">
<field name="name">Maintenance Plan</field>
<field name="code">fp.maintenance.plan</field>
<field name="prefix">MPLAN/</field>
<field name="padding">4</field>
<field name="company_id" eval="False"/>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="0">
<!-- Override standard stages to match Steelhead lifecycle -->
<record id="maintenance.stage_0" model="maintenance.stage">
<field name="name">New</field>
<field name="sequence" eval="1"/>
<field name="fold" eval="False"/>
<field name="done" eval="False"/>
</record>
<record id="stage_active" model="maintenance.stage">
<field name="name">Active</field>
<field name="sequence" eval="2"/>
<field name="fold" eval="False"/>
<field name="done" eval="False"/>
</record>
<record id="stage_completed" model="maintenance.stage">
<field name="name">Completed</field>
<field name="sequence" eval="3"/>
<field name="fold" eval="True"/>
<field name="done" eval="True"/>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
# Copyright 2026 Nexa Systems Inc.
# License OPL-1 (Odoo Proprietary License v1.0)
from . import fp_maintenance_plan
from . import fp_maintenance_node
from . import fp_maintenance_label
from . import maintenance_request
from . import maintenance_equipment

View File

@@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
# Copyright 2026 Nexa Systems Inc.
# License OPL-1 (Odoo Proprietary License v1.0)
from odoo import fields, models
class FpMaintenanceLabel(models.Model):
"""Simple tag model for equipment labels."""
_name = 'fp.maintenance.label'
_description = 'Fusion Plating — Equipment Label'
_order = 'name'
name = fields.Char(string='Name', required=True)
color = fields.Integer(string='Colour')
_sql_constraints = [
('name_uniq', 'unique(name)', 'Label name must be unique.'),
]

View File

@@ -0,0 +1,45 @@
# -*- coding: utf-8 -*-
# Copyright 2026 Nexa Systems Inc.
# License OPL-1 (Odoo Proprietary License v1.0)
from odoo import api, fields, models
class FpMaintenanceNode(models.Model):
"""Maintenance checklist item.
Individual task or check within a maintenance plan.
Auto-numbered on creation.
"""
_name = 'fp.maintenance.node'
_description = 'Fusion Plating — Maintenance Node'
_order = 'number desc'
name = fields.Char(
string='Name',
required=True,
)
number = fields.Integer(
string='Number',
readonly=True,
copy=False,
)
plan_id = fields.Many2one(
'fp.maintenance.plan',
string='Plan',
ondelete='set null',
)
active = fields.Boolean(default=True)
company_id = fields.Many2one(
'res.company',
string='Company',
default=lambda self: self.env.company,
)
@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
if not vals.get('number'):
last = self.sudo().search([], order='number desc', limit=1)
vals['number'] = (last.number if last else 0) + 1
return super().create(vals_list)

View File

@@ -0,0 +1,65 @@
# -*- coding: utf-8 -*-
# Copyright 2026 Nexa Systems Inc.
# License OPL-1 (Odoo Proprietary License v1.0)
from odoo import api, fields, models
class FpMaintenancePlan(models.Model):
"""Maintenance plan template.
Groups checklist nodes and links to an equipment category.
Plans are selected when creating maintenance events.
"""
_name = 'fp.maintenance.plan'
_description = 'Fusion Plating — Maintenance Plan'
_inherit = ['mail.thread']
_order = 'name'
name = fields.Char(
string='Name',
required=True,
tracking=True,
help='e.g. "Tank A-10 Nickel Nichem HP 1170 - Daily Titration"',
)
equipment_category_id = fields.Many2one(
'maintenance.equipment.category',
string='Equipment Type',
ondelete='set null',
tracking=True,
)
description = fields.Html(string='Description')
default_assignee_id = fields.Many2one(
'res.users',
string='Default Assignee',
)
node_ids = fields.One2many(
'fp.maintenance.node',
'plan_id',
string='Checklist Items',
)
node_count = fields.Integer(
string='Items',
compute='_compute_node_count',
)
active = fields.Boolean(default=True)
company_id = fields.Many2one(
'res.company',
string='Company',
default=lambda self: self.env.company,
)
def _compute_node_count(self):
for plan in self:
plan.node_count = len(plan.node_ids)
def action_view_nodes(self):
self.ensure_one()
return {
'type': 'ir.actions.act_window',
'name': f'Items — {self.name}',
'res_model': 'fp.maintenance.node',
'view_mode': 'list,form',
'domain': [('plan_id', '=', self.id)],
'context': {'default_plan_id': self.id},
}

View File

@@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
# Copyright 2026 Nexa Systems Inc.
# License OPL-1 (Odoo Proprietary License v1.0)
from odoo import fields, models
class MaintenanceEquipment(models.Model):
"""Extend standard maintenance.equipment with plating links."""
_inherit = 'maintenance.equipment'
x_fc_tank_id = fields.Many2one(
'fusion.plating.tank',
string='Plating Tank',
help='Link this equipment to a Fusion Plating tank.',
)
x_fc_facility_id = fields.Many2one(
'fusion.plating.facility',
string='Facility',
)
x_fc_location_name = fields.Char(
string='Sub-Location',
help='e.g. "PLANT1.BoilerRoom", "PLANT1.TankLine"',
)
x_fc_label_ids = fields.Many2many(
'fp.maintenance.label',
'fp_maintenance_equipment_label_rel',
'equipment_id',
'label_id',
string='Labels',
)

View File

@@ -0,0 +1,77 @@
# -*- coding: utf-8 -*-
# Copyright 2026 Nexa Systems Inc.
# License OPL-1 (Odoo Proprietary License v1.0)
import logging
from datetime import timedelta
from odoo import api, fields, models
_logger = logging.getLogger(__name__)
class MaintenanceRequest(models.Model):
"""Extend standard maintenance.request with plating-specific fields."""
_inherit = 'maintenance.request'
x_fc_plan_id = fields.Many2one(
'fp.maintenance.plan',
string='Plan',
)
x_fc_node_id = fields.Many2one(
'fp.maintenance.node',
string='Checklist Item',
)
x_fc_labour_cost = fields.Monetary(
string='Labour Cost',
currency_field='x_fc_currency_id',
)
x_fc_currency_id = fields.Many2one(
'res.currency',
string='Currency',
default=lambda self: self.env.company.currency_id,
)
x_fc_completed_at = fields.Datetime(
string='Completed At',
readonly=True,
)
x_fc_from_last = fields.Boolean(
string='From Last Maintenance',
help='When checked, the next recurrence is scheduled relative to '
'completion date instead of a fixed calendar interval.',
)
x_fc_recurrence_days = fields.Integer(
string='Recurrence Days',
help='Number of days after completion to schedule the next event '
'(only used with "From Last Maintenance").',
)
def write(self, vals):
res = super().write(vals)
if 'stage_id' in vals:
for request in self:
if request.stage_id.done and not request.x_fc_completed_at:
request.x_fc_completed_at = fields.Datetime.now()
self._maybe_schedule_from_last(request)
elif not request.stage_id.done:
request.x_fc_completed_at = False
return res
def _maybe_schedule_from_last(self, request):
"""Schedule next maintenance from completion date."""
if not request.x_fc_from_last or not request.x_fc_recurrence_days:
return
next_date = fields.Datetime.now() + timedelta(
days=request.x_fc_recurrence_days,
)
request.copy({
'schedule_date': next_date,
'x_fc_completed_at': False,
'stage_id': self.env['maintenance.stage'].search(
[('done', '=', False)], order='sequence', limit=1,
).id,
})
_logger.info(
'Scheduled next from-last maintenance for %s on %s',
request.name, next_date,
)

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="fp_maintenance_plan_company_rule" model="ir.rule">
<field name="name">Maintenance Plan: multi-company</field>
<field name="model_id" ref="model_fp_maintenance_plan"/>
<field name="global" eval="True"/>
<field name="domain_force">[('company_id', 'in', company_ids + [False])]</field>
</record>
<record id="fp_maintenance_node_company_rule" model="ir.rule">
<field name="name">Maintenance Node: multi-company</field>
<field name="model_id" ref="model_fp_maintenance_node"/>
<field name="global" eval="True"/>
<field name="domain_force">[('company_id', 'in', company_ids + [False])]</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,10 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_fp_maintenance_plan_operator,fp.maintenance.plan.operator,model_fp_maintenance_plan,fusion_plating.group_fusion_plating_operator,1,0,0,0
access_fp_maintenance_plan_supervisor,fp.maintenance.plan.supervisor,model_fp_maintenance_plan,fusion_plating.group_fusion_plating_supervisor,1,1,1,0
access_fp_maintenance_plan_manager,fp.maintenance.plan.manager,model_fp_maintenance_plan,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_maintenance_node_operator,fp.maintenance.node.operator,model_fp_maintenance_node,fusion_plating.group_fusion_plating_operator,1,0,0,0
access_fp_maintenance_node_supervisor,fp.maintenance.node.supervisor,model_fp_maintenance_node,fusion_plating.group_fusion_plating_supervisor,1,1,1,0
access_fp_maintenance_node_manager,fp.maintenance.node.manager,model_fp_maintenance_node,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_maintenance_label_operator,fp.maintenance.label.operator,model_fp_maintenance_label,fusion_plating.group_fusion_plating_operator,1,0,0,0
access_fp_maintenance_label_supervisor,fp.maintenance.label.supervisor,model_fp_maintenance_label,fusion_plating.group_fusion_plating_supervisor,1,1,1,0
access_fp_maintenance_label_manager,fp.maintenance.label.manager,model_fp_maintenance_label,fusion_plating.group_fusion_plating_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_fp_maintenance_plan_operator fp.maintenance.plan.operator model_fp_maintenance_plan fusion_plating.group_fusion_plating_operator 1 0 0 0
3 access_fp_maintenance_plan_supervisor fp.maintenance.plan.supervisor model_fp_maintenance_plan fusion_plating.group_fusion_plating_supervisor 1 1 1 0
4 access_fp_maintenance_plan_manager fp.maintenance.plan.manager model_fp_maintenance_plan fusion_plating.group_fusion_plating_manager 1 1 1 1
5 access_fp_maintenance_node_operator fp.maintenance.node.operator model_fp_maintenance_node fusion_plating.group_fusion_plating_operator 1 0 0 0
6 access_fp_maintenance_node_supervisor fp.maintenance.node.supervisor model_fp_maintenance_node fusion_plating.group_fusion_plating_supervisor 1 1 1 0
7 access_fp_maintenance_node_manager fp.maintenance.node.manager model_fp_maintenance_node fusion_plating.group_fusion_plating_manager 1 1 1 1
8 access_fp_maintenance_label_operator fp.maintenance.label.operator model_fp_maintenance_label fusion_plating.group_fusion_plating_operator 1 0 0 0
9 access_fp_maintenance_label_supervisor fp.maintenance.label.supervisor model_fp_maintenance_label fusion_plating.group_fusion_plating_supervisor 1 1 1 0
10 access_fp_maintenance_label_manager fp.maintenance.label.manager model_fp_maintenance_label fusion_plating.group_fusion_plating_manager 1 1 1 1

View File

@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Dashboard: Active Events -->
<record id="action_fp_maintenance_active" model="ir.actions.act_window">
<field name="name">Active Events</field>
<field name="res_model">maintenance.request</field>
<field name="view_mode">list,kanban,form,calendar</field>
<field name="domain">[('archive', '=', False), ('stage_id.done', '=', False)]</field>
<field name="context">{'search_default_group_stage': 1}</field>
</record>
<!-- Dashboard: Completed Events -->
<record id="action_fp_maintenance_completed" model="ir.actions.act_window">
<field name="name">Completed Events</field>
<field name="res_model">maintenance.request</field>
<field name="view_mode">list,form</field>
<field name="domain">[('stage_id.done', '=', True)]</field>
<field name="context">{}</field>
</record>
<!-- Dashboard: All Events -->
<record id="action_fp_maintenance_all" model="ir.actions.act_window">
<field name="name">All Events</field>
<field name="res_model">maintenance.request</field>
<field name="view_mode">list,kanban,form,calendar</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Create a maintenance event
</p>
</field>
</record>
<!-- Dashboard: Equipment -->
<record id="action_fp_maintenance_equipment" model="ir.actions.act_window">
<field name="name">Equipment</field>
<field name="res_model">maintenance.equipment</field>
<field name="view_mode">list,kanban,form</field>
</record>
</odoo>

View File

@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- ===== Maintenance parent menu under Plating root ===== -->
<menuitem id="menu_fp_maintenance"
name="Maintenance"
parent="fusion_plating.menu_fp_root"
sequence="22"
groups="fusion_plating.group_fusion_plating_operator"/>
<menuitem id="menu_fp_maintenance_active"
name="Active Events"
parent="menu_fp_maintenance"
action="action_fp_maintenance_active"
sequence="5"/>
<menuitem id="menu_fp_maintenance_plans"
name="Plans"
parent="menu_fp_maintenance"
action="action_fp_maintenance_plan"
sequence="10"/>
<menuitem id="menu_fp_maintenance_nodes"
name="Checklist Items"
parent="menu_fp_maintenance"
action="action_fp_maintenance_node"
sequence="20"/>
<menuitem id="menu_fp_maintenance_all"
name="All Events"
parent="menu_fp_maintenance"
action="action_fp_maintenance_all"
sequence="30"/>
<menuitem id="menu_fp_maintenance_completed"
name="Completed Events"
parent="menu_fp_maintenance"
action="action_fp_maintenance_completed"
sequence="35"/>
<menuitem id="menu_fp_maintenance_equipment"
name="Equipment"
parent="menu_fp_maintenance"
action="action_fp_maintenance_equipment"
sequence="40"/>
</odoo>

View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- ===== Node List ===== -->
<record id="view_fp_maintenance_node_list" model="ir.ui.view">
<field name="name">fp.maintenance.node.list</field>
<field name="model">fp.maintenance.node</field>
<field name="arch" type="xml">
<list string="Checklist Items" default_order="number desc">
<field name="name"/>
<field name="number"/>
<field name="plan_id"/>
</list>
</field>
</record>
<!-- ===== Node Form ===== -->
<record id="view_fp_maintenance_node_form" model="ir.ui.view">
<field name="name">fp.maintenance.node.form</field>
<field name="model">fp.maintenance.node</field>
<field name="arch" type="xml">
<form string="Checklist Item">
<sheet>
<group>
<group>
<field name="name"/>
<field name="number" readonly="1"/>
</group>
<group>
<field name="plan_id"/>
</group>
</group>
</sheet>
</form>
</field>
</record>
<!-- ===== Window Action ===== -->
<record id="action_fp_maintenance_node" model="ir.actions.act_window">
<field name="name">Checklist Items</field>
<field name="res_model">fp.maintenance.node</field>
<field name="view_mode">list,form</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Create a checklist item
</p>
<p>Checklist items are individual tasks within a maintenance plan.</p>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,96 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- ===== Plan Form ===== -->
<record id="view_fp_maintenance_plan_form" model="ir.ui.view">
<field name="name">fp.maintenance.plan.form</field>
<field name="model">fp.maintenance.plan</field>
<field name="arch" type="xml">
<form string="Maintenance Plan">
<sheet>
<div class="oe_button_box" name="button_box">
<button class="oe_stat_button" icon="fa-list-ol"
type="object" name="action_view_nodes"
invisible="node_count == 0">
<field name="node_count" widget="statinfo" string="Items"/>
</button>
</div>
<div class="oe_title">
<h1>
<field name="name" placeholder="e.g. Tank A-10 Daily Titration"/>
</h1>
</div>
<group>
<group>
<field name="equipment_category_id" string="Equipment Type"/>
<field name="default_assignee_id"/>
</group>
<group>
<field name="active" invisible="1"/>
<field name="company_id" groups="base.group_multi_company"/>
</group>
</group>
<notebook>
<page string="Description" name="description">
<field name="description"/>
</page>
<page string="Checklist Items" name="nodes">
<field name="node_ids">
<list editable="bottom">
<field name="number" readonly="1"/>
<field name="name"/>
</list>
</field>
</page>
</notebook>
</sheet>
<chatter/>
</form>
</field>
</record>
<!-- ===== Plan List ===== -->
<record id="view_fp_maintenance_plan_list" model="ir.ui.view">
<field name="name">fp.maintenance.plan.list</field>
<field name="model">fp.maintenance.plan</field>
<field name="arch" type="xml">
<list string="Maintenance Plans" default_order="name">
<field name="name"/>
<field name="equipment_category_id" string="Equipment Type"/>
<field name="default_assignee_id"/>
<field name="node_count" string="Items"/>
</list>
</field>
</record>
<!-- ===== Plan Search ===== -->
<record id="view_fp_maintenance_plan_search" model="ir.ui.view">
<field name="name">fp.maintenance.plan.search</field>
<field name="model">fp.maintenance.plan</field>
<field name="arch" type="xml">
<search>
<field name="name"/>
<field name="equipment_category_id"/>
<group>
<filter string="Equipment Type" name="group_category"
context="{'group_by': 'equipment_category_id'}"/>
</group>
</search>
</field>
</record>
<!-- ===== Window Action ===== -->
<record id="action_fp_maintenance_plan" model="ir.actions.act_window">
<field name="name">Maintenance Plans</field>
<field name="res_model">fp.maintenance.plan</field>
<field name="view_mode">list,form</field>
<field name="search_view_id" ref="view_fp_maintenance_plan_search"/>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Create a maintenance plan
</p>
<p>Plans are templates for recurring maintenance tasks linked to equipment types.</p>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Extend maintenance.equipment form with plating links -->
<record id="view_maintenance_equipment_form_fp" model="ir.ui.view">
<field name="name">maintenance.equipment.form.fp.bridge</field>
<field name="model">maintenance.equipment</field>
<field name="inherit_id" ref="maintenance.hr_equipment_view_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='category_id']" position="after">
<field name="x_fc_tank_id"/>
<field name="x_fc_facility_id"/>
<field name="x_fc_location_name"
placeholder="e.g. PLANT1.TankLine"/>
<field name="x_fc_label_ids" widget="many2many_tags"
options="{'color_field': 'color'}"/>
</xpath>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Extend maintenance.request form with plating fields -->
<record id="view_maintenance_request_form_fp" model="ir.ui.view">
<field name="name">maintenance.request.form.fp.bridge</field>
<field name="model">maintenance.request</field>
<field name="inherit_id" ref="maintenance.hr_equipment_request_view_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='equipment_id'][not(ancestor::kanban)]" position="after">
<field name="x_fc_plan_id"/>
<field name="x_fc_node_id"/>
</xpath>
<xpath expr="//field[@name='schedule_end']" position="after">
<field name="x_fc_completed_at" readonly="1"/>
<field name="x_fc_labour_cost"/>
<field name="x_fc_currency_id" invisible="1"/>
<field name="x_fc_from_last"/>
<field name="x_fc_recurrence_days"
invisible="not x_fc_from_last"/>
</xpath>
</field>
</record>
<!-- Extend maintenance.request list with plating columns -->
<record id="view_maintenance_request_list_fp" model="ir.ui.view">
<field name="name">maintenance.request.list.fp.bridge</field>
<field name="model">maintenance.request</field>
<field name="inherit_id" ref="maintenance.hr_equipment_request_view_tree"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='stage_id']" position="before">
<field name="x_fc_plan_id" optional="show"/>
<field name="x_fc_node_id" optional="show"/>
</xpath>
<xpath expr="//field[@name='stage_id']" position="after">
<field name="x_fc_completed_at" optional="show"/>
<field name="x_fc_labour_cost" optional="hide"/>
<field name="x_fc_currency_id" column_invisible="1"/>
</xpath>
</field>
</record>
</odoo>