feat(configurator): fp.part.catalog — customer part library with views

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-04-12 18:24:54 -04:00
parent 73b7325b46
commit 87325e2caf
4 changed files with 213 additions and 1 deletions

View File

@@ -4,3 +4,5 @@
# Part of the Fusion Plating product family.
from . import fp_treatment
from . import fp_part_catalog
from . import fp_coating_config

View File

@@ -0,0 +1,65 @@
# -*- coding: utf-8 -*-
# Copyright 2026 Nexa Systems Inc.
# License OPL-1 (Odoo Proprietary License v1.0)
# Part of the Fusion Plating product family.
from odoo import fields, models
class FpPartCatalog(models.Model):
"""Customer part library.
Stores geometry, material, and complexity data for parts that
customers send repeatedly. New orders reference existing catalog
entries for instant re-quoting; one-off parts create new entries.
"""
_name = 'fp.part.catalog'
_description = 'Fusion Plating — Part Catalog'
_inherit = ['mail.thread', 'mail.activity.mixin']
_order = 'partner_id, part_number, name'
name = fields.Char(string='Part Name', required=True, tracking=True)
partner_id = fields.Many2one(
'res.partner', string='Customer', required=True, ondelete='cascade',
tracking=True, domain="[('customer_rank', '>', 0)]",
)
part_number = fields.Char(string='Part Number', tracking=True, help="Customer's part number (e.g. VS-R392007E01).")
revision = fields.Char(string='Revision', help='Revision letter or number (e.g. Rev: 1B).')
substrate_material = fields.Selection(
[('aluminium', 'Aluminium'), ('steel', 'Steel'), ('stainless', 'Stainless Steel'),
('copper', 'Copper'), ('titanium', 'Titanium'), ('other', 'Other')],
string='Substrate Material', default='steel',
)
geometry_source = fields.Selection(
[('3d_model', '3D Model'), ('manual', 'Manual Measurements'), ('pdf_drawing', 'PDF Drawing')],
string='Geometry Source', default='manual',
)
model_attachment_id = fields.Many2one('ir.attachment', string='3D Model File', help='STEP, STL, or IGES file.')
drawing_attachment_ids = fields.Many2many(
'ir.attachment', 'fp_part_catalog_drawing_rel', 'part_catalog_id', 'attachment_id', string='PDF Drawings',
)
surface_area = fields.Float(string='Surface Area', digits=(12, 4))
surface_area_uom = fields.Selection(
[('sq_in', 'sq in'), ('sq_ft', 'sq ft'), ('sq_cm', 'sq cm'), ('sq_m', 'sq m')],
string='Surface Area UoM', default='sq_in',
)
weight = fields.Float(string='Weight (kg)', digits=(12, 4))
dimensions_length = fields.Float(string='Length', digits=(12, 4))
dimensions_width = fields.Float(string='Width', digits=(12, 4))
dimensions_height = fields.Float(string='Height', digits=(12, 4))
complexity = fields.Selection(
[('simple', 'Simple'), ('moderate', 'Moderate'), ('complex', 'Complex'), ('very_complex', 'Very Complex')],
string='Complexity', default='simple',
)
masking_zones = fields.Integer(string='Masking Zones', help='Number of areas requiring masking.')
masking_description = fields.Text(string='Masking Description', help='e.g. "Mask threaded holes, mask bore ID"')
has_blind_holes = fields.Boolean(string='Has Blind Holes')
has_recesses = fields.Boolean(string='Has Recesses')
has_threads = fields.Boolean(string='Has Threads')
notes = fields.Html(string='Notes')
active = fields.Boolean(string='Active', default=True)
_sql_constraints = [
('fp_part_catalog_partner_partnum_uniq', 'unique(partner_id, part_number)',
'Part number must be unique per customer.'),
]

View File

@@ -2,3 +2,9 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_fp_treatment_operator,fp.treatment.operator,model_fp_treatment,fusion_plating.group_fusion_plating_operator,1,0,0,0
access_fp_treatment_supervisor,fp.treatment.supervisor,model_fp_treatment,fusion_plating.group_fusion_plating_supervisor,1,1,0,0
access_fp_treatment_manager,fp.treatment.manager,model_fp_treatment,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_part_catalog_operator,fp.part.catalog.operator,model_fp_part_catalog,fusion_plating.group_fusion_plating_operator,1,0,0,0
access_fp_part_catalog_estimator,fp.part.catalog.estimator,model_fp_part_catalog,fusion_plating_configurator.group_fp_estimator,1,1,1,0
access_fp_part_catalog_manager,fp.part.catalog.manager,model_fp_part_catalog,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_coating_config_operator,fp.coating.config.operator,model_fp_coating_config,fusion_plating.group_fusion_plating_operator,1,0,0,0
access_fp_coating_config_estimator,fp.coating.config.estimator,model_fp_coating_config,fusion_plating_configurator.group_fp_estimator,1,1,1,0
access_fp_coating_config_manager,fp.coating.config.manager,model_fp_coating_config,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_treatment_operator fp.treatment.operator model_fp_treatment fusion_plating.group_fusion_plating_operator 1 0 0 0
3 access_fp_treatment_supervisor fp.treatment.supervisor model_fp_treatment fusion_plating.group_fusion_plating_supervisor 1 1 0 0
4 access_fp_treatment_manager fp.treatment.manager model_fp_treatment fusion_plating.group_fusion_plating_manager 1 1 1 1
5 access_fp_part_catalog_operator fp.part.catalog.operator model_fp_part_catalog fusion_plating.group_fusion_plating_operator 1 0 0 0
6 access_fp_part_catalog_estimator fp.part.catalog.estimator model_fp_part_catalog fusion_plating_configurator.group_fp_estimator 1 1 1 0
7 access_fp_part_catalog_manager fp.part.catalog.manager model_fp_part_catalog fusion_plating.group_fusion_plating_manager 1 1 1 1
8 access_fp_coating_config_operator fp.coating.config.operator model_fp_coating_config fusion_plating.group_fusion_plating_operator 1 0 0 0
9 access_fp_coating_config_estimator fp.coating.config.estimator model_fp_coating_config fusion_plating_configurator.group_fp_estimator 1 1 1 0
10 access_fp_coating_config_manager fp.coating.config.manager model_fp_coating_config fusion_plating.group_fusion_plating_manager 1 1 1 1

View File

@@ -1,2 +1,141 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo></odoo>
<!--
Copyright 2026 Nexa Systems Inc.
License OPL-1 (Odoo Proprietary License v1.0)
Part of the Fusion Plating product family.
-->
<odoo>
<!-- ===== Part Catalog List View ===== -->
<record id="view_fp_part_catalog_list" model="ir.ui.view">
<field name="name">fp.part.catalog.list</field>
<field name="model">fp.part.catalog</field>
<field name="arch" type="xml">
<list string="Part Catalog" decoration-muted="not active">
<field name="partner_id"/>
<field name="part_number"/>
<field name="revision"/>
<field name="substrate_material"/>
<field name="surface_area"/>
<field name="complexity"/>
<field name="active" widget="boolean_toggle"/>
</list>
</field>
</record>
<!-- ===== Part Catalog Form View ===== -->
<record id="view_fp_part_catalog_form" model="ir.ui.view">
<field name="name">fp.part.catalog.form</field>
<field name="model">fp.part.catalog</field>
<field name="arch" type="xml">
<form string="Part Catalog">
<sheet>
<widget name="web_ribbon" title="Archived" bg_color="text-bg-danger" invisible="active"/>
<div class="oe_title">
<label for="name"/>
<h1><field name="name" placeholder="e.g. Valve Body Housing"/></h1>
<field name="part_number" placeholder="Customer part number (e.g. VS-R392007E01)"/>
</div>
<group>
<group>
<field name="partner_id"/>
<field name="revision"/>
<field name="substrate_material"/>
<field name="geometry_source"/>
</group>
<group>
<field name="surface_area"/>
<field name="surface_area_uom"/>
<field name="weight"/>
</group>
</group>
<notebook>
<page string="Dimensions &amp; Complexity" name="dimensions">
<group>
<group>
<field name="dimensions_length"/>
<field name="dimensions_width"/>
<field name="dimensions_height"/>
</group>
<group>
<field name="complexity"/>
<field name="masking_zones"/>
<field name="has_blind_holes"/>
<field name="has_recesses"/>
<field name="has_threads"/>
</group>
</group>
<group>
<field name="masking_description" placeholder="e.g. Mask threaded holes, mask bore ID"/>
</group>
</page>
<page string="Attachments" name="attachments">
<group>
<field name="model_attachment_id"/>
<field name="drawing_attachment_ids" widget="many2many_binary"/>
</group>
</page>
<page string="Notes" name="notes">
<field name="notes" placeholder="Additional notes about this part..."/>
</page>
</notebook>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids"/>
<field name="activity_ids"/>
<field name="message_ids"/>
</div>
</form>
</field>
</record>
<!-- ===== Part Catalog Search View ===== -->
<record id="view_fp_part_catalog_search" model="ir.ui.view">
<field name="name">fp.part.catalog.search</field>
<field name="model">fp.part.catalog</field>
<field name="arch" type="xml">
<search>
<field name="name"/>
<field name="part_number"/>
<field name="partner_id"/>
<separator/>
<filter string="Aluminium" name="material_aluminium" domain="[('substrate_material','=','aluminium')]"/>
<filter string="Steel" name="material_steel" domain="[('substrate_material','=','steel')]"/>
<filter string="Stainless Steel" name="material_stainless" domain="[('substrate_material','=','stainless')]"/>
<filter string="Copper" name="material_copper" domain="[('substrate_material','=','copper')]"/>
<filter string="Titanium" name="material_titanium" domain="[('substrate_material','=','titanium')]"/>
<filter string="Other" name="material_other" domain="[('substrate_material','=','other')]"/>
<separator/>
<filter string="Simple" name="complexity_simple" domain="[('complexity','=','simple')]"/>
<filter string="Moderate" name="complexity_moderate" domain="[('complexity','=','moderate')]"/>
<filter string="Complex" name="complexity_complex" domain="[('complexity','=','complex')]"/>
<filter string="Very Complex" name="complexity_very_complex" domain="[('complexity','=','very_complex')]"/>
<separator/>
<filter string="Archived" name="inactive" domain="[('active','=',False)]"/>
<group>
<filter string="Customer" name="group_partner" context="{'group_by':'partner_id'}"/>
<filter string="Material" name="group_material" context="{'group_by':'substrate_material'}"/>
<filter string="Complexity" name="group_complexity" context="{'group_by':'complexity'}"/>
</group>
</search>
</field>
</record>
<!-- ===== Window Action ===== -->
<record id="action_fp_part_catalog" model="ir.actions.act_window">
<field name="name">Part Catalog</field>
<field name="res_model">fp.part.catalog</field>
<field name="view_mode">list,form</field>
<field name="search_view_id" ref="view_fp_part_catalog_search"/>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
No parts in the catalog yet
</p>
<p>
Add customer parts with geometry, material, and complexity data
for instant re-quoting on repeat orders.
</p>
</field>
</record>
</odoo>