From 2fa7f2aa2e4d8e99dd9168be49b8f026a1eb3f71 Mon Sep 17 00:00:00 2001 From: gsinghpal Date: Sun, 12 Apr 2026 18:28:43 -0400 Subject: [PATCH] =?UTF-8?q?feat(configurator):=20fp.pricing.rule=20?= =?UTF-8?q?=E2=80=94=20formula-based=20pricing=20engine=20with=20complexit?= =?UTF-8?q?y=20surcharges?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 (1M context) --- .../models/__init__.py | 3 + .../models/fp_pricing_complexity_surcharge.py | 25 ++++ .../models/fp_pricing_rule.py | 54 ++++++++ .../security/ir.model.access.csv | 6 + .../views/fp_pricing_rule_views.xml | 131 +++++++++++++++++- 5 files changed, 218 insertions(+), 1 deletion(-) create mode 100644 fusion-plating/fusion_plating_configurator/models/fp_pricing_complexity_surcharge.py create mode 100644 fusion-plating/fusion_plating_configurator/models/fp_pricing_rule.py diff --git a/fusion-plating/fusion_plating_configurator/models/__init__.py b/fusion-plating/fusion_plating_configurator/models/__init__.py index 1942b876..98bd4e24 100644 --- a/fusion-plating/fusion_plating_configurator/models/__init__.py +++ b/fusion-plating/fusion_plating_configurator/models/__init__.py @@ -6,3 +6,6 @@ from . import fp_treatment from . import fp_part_catalog from . import fp_coating_config +from . import fp_pricing_complexity_surcharge +from . import fp_pricing_rule +from . import sale_order diff --git a/fusion-plating/fusion_plating_configurator/models/fp_pricing_complexity_surcharge.py b/fusion-plating/fusion_plating_configurator/models/fp_pricing_complexity_surcharge.py new file mode 100644 index 00000000..d60bd0b8 --- /dev/null +++ b/fusion-plating/fusion_plating_configurator/models/fp_pricing_complexity_surcharge.py @@ -0,0 +1,25 @@ +# -*- 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 FpPricingComplexitySurcharge(models.Model): + """Complexity-based surcharge line on a pricing rule.""" + _name = 'fp.pricing.complexity.surcharge' + _description = 'Fusion Plating — Pricing Complexity Surcharge' + _order = 'complexity' + + rule_id = fields.Many2one('fp.pricing.rule', string='Pricing Rule', required=True, ondelete='cascade') + complexity = fields.Selection( + [('simple', 'Simple'), ('moderate', 'Moderate'), ('complex', 'Complex'), ('very_complex', 'Very Complex')], + string='Complexity', required=True, + ) + surcharge_percent = fields.Float(string='Surcharge %', help='Additional percentage on top of base price.') + + _sql_constraints = [ + ('fp_pricing_surcharge_rule_complexity_uniq', 'unique(rule_id, complexity)', + 'Only one surcharge per complexity level per rule.'), + ] diff --git a/fusion-plating/fusion_plating_configurator/models/fp_pricing_rule.py b/fusion-plating/fusion_plating_configurator/models/fp_pricing_rule.py new file mode 100644 index 00000000..c83b4902 --- /dev/null +++ b/fusion-plating/fusion_plating_configurator/models/fp_pricing_rule.py @@ -0,0 +1,54 @@ +# -*- 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 FpPricingRule(models.Model): + """Formula-based pricing rule. + + Rules are matched by coating config, substrate material, and + certification level. The first matching rule (by sequence) wins. + Global rules (no filters set) act as fallbacks. + """ + _name = 'fp.pricing.rule' + _description = 'Fusion Plating — Pricing Rule' + _order = 'sequence, id' + + name = fields.Char(string='Rule Name', required=True) + coating_config_id = fields.Many2one('fp.coating.config', string='Coating Config', + help='Leave blank for a global rule.') + substrate_material = fields.Selection( + [('aluminium', 'Aluminium'), ('steel', 'Steel'), ('stainless', 'Stainless Steel'), + ('copper', 'Copper'), ('titanium', 'Titanium'), ('other', 'Other')], + string='Substrate Material', help='Leave blank to match all materials.', + ) + certification_level = fields.Selection( + [('commercial', 'Commercial'), ('mil_spec', 'Mil-Spec'), + ('nadcap', 'Nadcap'), ('nuclear', 'Nuclear (CSA N299)')], + string='Certification Level', help='Leave blank to match all levels.', + ) + pricing_method = fields.Selection( + [('per_sqin', 'Per Square Inch'), ('per_sqft', 'Per Square Foot'), + ('per_piece', 'Per Piece'), ('flat_rate', 'Flat Rate')], + string='Pricing Method', required=True, default='per_sqin', + ) + currency_id = fields.Many2one('res.currency', string='Currency', + default=lambda self: self.env.company.currency_id) + base_rate = fields.Monetary(string='Base Rate', currency_field='currency_id', + help='Price per unit (sq in, sq ft, piece, or flat).') + thickness_factor = fields.Float(string='Thickness Factor', default=1.0, + help='Multiplier per mil of coating thickness. 1.0 = no adjustment.') + complexity_surcharge_ids = fields.One2many('fp.pricing.complexity.surcharge', 'rule_id', + string='Complexity Surcharges') + masking_rate_per_zone = fields.Monetary(string='Masking Rate / Zone', currency_field='currency_id') + setup_fee = fields.Monetary(string='Setup Fee', currency_field='currency_id', + help='One-time setup fee per batch.') + minimum_charge = fields.Monetary(string='Minimum Charge', currency_field='currency_id', + help='Floor price.') + rush_surcharge_percent = fields.Float(string='Rush Surcharge %') + sequence = fields.Integer(string='Sequence', default=10) + active = fields.Boolean(string='Active', default=True) + notes = fields.Text(string='Notes') diff --git a/fusion-plating/fusion_plating_configurator/security/ir.model.access.csv b/fusion-plating/fusion_plating_configurator/security/ir.model.access.csv index a7c63563..2c693652 100644 --- a/fusion-plating/fusion_plating_configurator/security/ir.model.access.csv +++ b/fusion-plating/fusion_plating_configurator/security/ir.model.access.csv @@ -8,3 +8,9 @@ access_fp_part_catalog_manager,fp.part.catalog.manager,model_fp_part_catalog,fus 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 +access_fp_pricing_rule_operator,fp.pricing.rule.operator,model_fp_pricing_rule,fusion_plating.group_fusion_plating_operator,1,0,0,0 +access_fp_pricing_rule_estimator,fp.pricing.rule.estimator,model_fp_pricing_rule,fusion_plating_configurator.group_fp_estimator,1,1,1,0 +access_fp_pricing_rule_manager,fp.pricing.rule.manager,model_fp_pricing_rule,fusion_plating.group_fusion_plating_manager,1,1,1,1 +access_fp_pricing_surcharge_operator,fp.pricing.complexity.surcharge.operator,model_fp_pricing_complexity_surcharge,fusion_plating.group_fusion_plating_operator,1,0,0,0 +access_fp_pricing_surcharge_estimator,fp.pricing.complexity.surcharge.estimator,model_fp_pricing_complexity_surcharge,fusion_plating_configurator.group_fp_estimator,1,1,1,0 +access_fp_pricing_surcharge_manager,fp.pricing.complexity.surcharge.manager,model_fp_pricing_complexity_surcharge,fusion_plating.group_fusion_plating_manager,1,1,1,1 diff --git a/fusion-plating/fusion_plating_configurator/views/fp_pricing_rule_views.xml b/fusion-plating/fusion_plating_configurator/views/fp_pricing_rule_views.xml index 85d8c23e..50e706c3 100644 --- a/fusion-plating/fusion_plating_configurator/views/fp_pricing_rule_views.xml +++ b/fusion-plating/fusion_plating_configurator/views/fp_pricing_rule_views.xml @@ -1,2 +1,131 @@ - + + + + + + fp.pricing.rule.list + fp.pricing.rule + + + + + + + + + + + + + + + + + + + fp.pricing.rule.form + fp.pricing.rule + +
+ + +
+
+ + + + + + + +
+ Leave filter fields blank to create a global rule + that matches any configuration. +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + + fp.pricing.rule.search + fp.pricing.rule + + + + + + + + + + + + + + + + + + + + + + Pricing Rules + fp.pricing.rule + list,form + + +

+ No pricing rules defined yet +

+

+ Define formula-based pricing rules matched by coating + configuration, substrate material, and certification level. + The first matching rule (by sequence) wins. +

+
+
+ +