feat(promote-customer-spec): Phase E — final removal of coating + treatment
DELETED entirely (model + view + ACL + data file + menu): - fp.coating.config (configurator) - fp.treatment (configurator + seeded data) - fp.coating.thickness (configurator) — replaced by fp.recipe.thickness in Phase A - fp.customer.price.list (configurator) — coating-keyed, no replacement Field deletions: - sale.order.x_fc_coating_config_id - sale.order.line.x_fc_coating_config_id + x_fc_treatment_ids - account.move.line.x_fc_coating_config_id - fp.part.catalog.x_fc_default_coating_config_id + x_fc_default_treatment_ids - fp.job.coating_config_id - fp.pricing.rule.coating_config_id - fp.quality.point.coating_config_ids - fp.direct.order.line.coating_config_id + treatment_ids - fp.sale.description.template.coating_config_id Refactored: - fp.quote.configurator.coating_config_id → recipe_id (now points at fusion.plating.process.node, the actual recipe). All compute, onchange, and matcher logic updated to use recipe directly. Quality inherit extends matcher with spec-tier scoring. - fp.job._fp_create_certificates now reads spec from job.customer_spec_id and formats spec_reference as "code Rev rev". Same for thickness source — bake fields read from recipe_root (Phase A). - fp.job.step.button_finish bake-window auto-spawn reads bake settings from recipe_root instead of coating. - fp.certificate auto-fill spec_min_mils/max_mils from recipe (Phase A thickness fields) instead of coating. - jobs/sale_order.py: job creation reads x_fc_customer_spec_id from line, drops coating refs and the legacy header-coating fallback. - Wizards drop coating + treatment fields and refs. - Configurator views drop x_fc_coating_config_id + x_fc_treatment_ids fields entirely. Quality inherits re-anchor on stable fields (x_fc_part_catalog_id, x_fc_internal_description, default_process_id, process_variant_id, substrate_material) so they keep working. - Reports drop coating fallback elifs; print recipe / spec. - Tablet payload drops coating_config_id from job.read fields. Skipped (deferred to backlog): - fusion_plating_bridge_mrp — module is uninstalled per Sub 11; source files retain coating refs but no runtime impact. - fusion_plating_portal — circular dep (portal → quality → certs → portal). Customer-facing portal coating picker stays for now; promote-spec polish is a separate sub-project. Verification: grep for "coating_config_id|fp.coating.config| fp.treatment|fp.coating.thickness" in live (non-bridge_mrp, non-portal, non-script, non-test) Python/XML/CSV returns 3 hits, all in module / class docstrings explaining Phase E history. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
|
||||
{
|
||||
'name': 'Fusion Plating — Quality (QMS)',
|
||||
'version': '19.0.5.3.0',
|
||||
'version': '19.0.6.0.0',
|
||||
'category': 'Manufacturing/Plating',
|
||||
'summary': 'Native QMS for plating shops: NCR, CAPA, calibration, AVL, FAIR, '
|
||||
'internal audits, customer specs, document control. CE + EE compatible.',
|
||||
|
||||
@@ -63,10 +63,6 @@ class FpQualityPoint(models.Model):
|
||||
'fp.part.catalog', 'fp_quality_point_part_rel',
|
||||
'point_id', 'part_id', string='Parts',
|
||||
)
|
||||
coating_config_ids = fields.Many2many(
|
||||
'fp.coating.config', 'fp_quality_point_coating_rel',
|
||||
'point_id', 'coating_id', string='Coatings',
|
||||
)
|
||||
customer_spec_ids = fields.Many2many(
|
||||
'fusion.plating.customer.spec',
|
||||
'fp_quality_point_spec_rel',
|
||||
@@ -119,7 +115,7 @@ class FpQualityPoint(models.Model):
|
||||
# ------------------------------------------------------------------
|
||||
# Matching + spawning
|
||||
# ------------------------------------------------------------------
|
||||
def _matches(self, partner=None, part=None, coating=None, step=None,
|
||||
def _matches(self, partner=None, part=None, step=None,
|
||||
customer_spec=None, recipe=None):
|
||||
"""Return True if this point's filters all pass against the supplied
|
||||
context. Empty filter == match anything.
|
||||
@@ -130,9 +126,6 @@ class FpQualityPoint(models.Model):
|
||||
if self.part_catalog_ids and (
|
||||
not part or part not in self.part_catalog_ids):
|
||||
return False
|
||||
if self.coating_config_ids and (
|
||||
not coating or coating not in self.coating_config_ids):
|
||||
return False
|
||||
if self.customer_spec_ids and (
|
||||
not customer_spec
|
||||
or customer_spec not in self.customer_spec_ids):
|
||||
@@ -146,7 +139,7 @@ class FpQualityPoint(models.Model):
|
||||
return True
|
||||
|
||||
@api.model
|
||||
def _find_matching(self, trigger, partner=None, part=None, coating=None,
|
||||
def _find_matching(self, trigger, partner=None, part=None,
|
||||
step=None, customer_spec=None, recipe=None):
|
||||
"""Return active points whose trigger + filters match the context."""
|
||||
candidates = self.search([
|
||||
@@ -154,7 +147,7 @@ class FpQualityPoint(models.Model):
|
||||
('trigger_type', '=', trigger),
|
||||
])
|
||||
return candidates.filtered(lambda p: p._matches(
|
||||
partner=partner, part=part, coating=coating, step=step,
|
||||
partner=partner, part=part, step=step,
|
||||
customer_spec=customer_spec, recipe=recipe,
|
||||
))
|
||||
|
||||
|
||||
@@ -52,22 +52,16 @@ class SaleOrderPointHook(models.Model):
|
||||
# Walk lines for part / coating / spec context.
|
||||
parts = so.order_line.mapped('x_fc_part_catalog_id') \
|
||||
if 'x_fc_part_catalog_id' in so.order_line._fields else False
|
||||
coatings = so.order_line.mapped('x_fc_coating_config_id') \
|
||||
if 'x_fc_coating_config_id' in so.order_line._fields else False
|
||||
specs = so.order_line.mapped('x_fc_customer_spec_id') \
|
||||
if 'x_fc_customer_spec_id' in so.order_line._fields else False
|
||||
points = Point._find_matching(
|
||||
trigger='so_confirmed', partner=partner,
|
||||
)
|
||||
for point in points:
|
||||
# Filter by part / coating / spec intersection if the
|
||||
# point cares.
|
||||
# Filter by part / spec intersection if the point cares.
|
||||
if point.part_catalog_ids and parts and \
|
||||
not (point.part_catalog_ids & parts):
|
||||
continue
|
||||
if point.coating_config_ids and coatings and \
|
||||
not (point.coating_config_ids & coatings):
|
||||
continue
|
||||
if point.customer_spec_ids and specs and \
|
||||
not (point.customer_spec_ids & specs):
|
||||
continue
|
||||
@@ -85,12 +79,11 @@ class FpJobPointHook(models.Model):
|
||||
for job in self:
|
||||
partner = job.partner_id
|
||||
part = getattr(job, 'part_catalog_id', False) or False
|
||||
coating = getattr(job, 'coating_config_id', False) or False
|
||||
customer_spec = getattr(job, 'customer_spec_id', False) or False
|
||||
recipe = getattr(job, 'recipe_id', False) or False
|
||||
points = Point._find_matching(
|
||||
trigger='job_confirmed', partner=partner,
|
||||
part=part or None, coating=coating or None,
|
||||
part=part or None,
|
||||
customer_spec=customer_spec or None,
|
||||
recipe=recipe or None,
|
||||
)
|
||||
@@ -108,12 +101,11 @@ class FpJobPointHook(models.Model):
|
||||
continue
|
||||
partner = job.partner_id
|
||||
part = getattr(job, 'part_catalog_id', False) or False
|
||||
coating = getattr(job, 'coating_config_id', False) or False
|
||||
customer_spec = getattr(job, 'customer_spec_id', False) or False
|
||||
recipe = getattr(job, 'recipe_id', False) or False
|
||||
points = Point._find_matching(
|
||||
trigger='job_done', partner=partner,
|
||||
part=part or None, coating=coating or None,
|
||||
part=part or None,
|
||||
customer_spec=customer_spec or None,
|
||||
recipe=recipe or None,
|
||||
)
|
||||
@@ -137,12 +129,11 @@ class FpJobStepPointHook(models.Model):
|
||||
job = step.job_id
|
||||
partner = job.partner_id if job else False
|
||||
part = getattr(job, 'part_catalog_id', False) or False
|
||||
coating = getattr(job, 'coating_config_id', False) or False
|
||||
customer_spec = getattr(job, 'customer_spec_id', False) or False
|
||||
recipe = getattr(job, 'recipe_id', False) or False
|
||||
points = Point._find_matching(
|
||||
trigger='job_step_done', partner=partner,
|
||||
part=part or None, coating=coating or None, step=step,
|
||||
part=part or None, step=step,
|
||||
customer_spec=customer_spec or None,
|
||||
recipe=recipe or None,
|
||||
)
|
||||
|
||||
@@ -24,19 +24,9 @@ class FpQuoteConfigurator(models.Model):
|
||||
"""Extend the configurator's matcher to consider Spec + Recipe.
|
||||
|
||||
Spec match adds +8 (highest priority — explicit customer spec
|
||||
wins over chemistry / cert-level filters). Recipe adds +6.
|
||||
Falls through to the existing coating / material / cert scoring.
|
||||
wins over chemistry filters). Recipe adds +6. Material is +2.
|
||||
"""
|
||||
# Cache the recipe before super (super may overwrite via thickness
|
||||
# logic in some inherit chains).
|
||||
recipe = (
|
||||
self.coating_config_id.recipe_id
|
||||
if self.coating_config_id and self.coating_config_id.recipe_id
|
||||
else False
|
||||
)
|
||||
# Build the candidate rule set the same way super does — but
|
||||
# since super uses a private mechanism we re-implement to keep
|
||||
# the spec/recipe scoring inline with the rest.
|
||||
recipe = self.recipe_id or False
|
||||
builder_rules = (
|
||||
recipe.pricing_rule_ids
|
||||
if recipe else self.env['fp.pricing.rule']
|
||||
@@ -49,38 +39,25 @@ class FpQuoteConfigurator(models.Model):
|
||||
rules = self.env['fp.pricing.rule'].search(
|
||||
[('active', '=', True)], order='sequence, id'
|
||||
)
|
||||
cert_level = (
|
||||
self.coating_config_id.certification_level
|
||||
if self.coating_config_id else False
|
||||
)
|
||||
|
||||
best = None
|
||||
best_score = -1
|
||||
for rule in rules:
|
||||
score = 0
|
||||
# NEW — spec wins biggest
|
||||
# Spec wins biggest
|
||||
if rule.customer_spec_id:
|
||||
if rule.customer_spec_id != self.customer_spec_id:
|
||||
continue
|
||||
score += 8
|
||||
# NEW — recipe is next
|
||||
# Recipe is next
|
||||
if rule.recipe_id:
|
||||
if rule.recipe_id != recipe:
|
||||
continue
|
||||
score += 6
|
||||
# Legacy — coating / material / cert
|
||||
if rule.coating_config_id:
|
||||
if rule.coating_config_id != self.coating_config_id:
|
||||
continue
|
||||
score += 4
|
||||
if rule.substrate_material:
|
||||
if rule.substrate_material != self.substrate_material:
|
||||
continue
|
||||
score += 2
|
||||
if rule.certification_level:
|
||||
if rule.certification_level != cert_level:
|
||||
continue
|
||||
score += 1
|
||||
if score > best_score:
|
||||
best_score = score
|
||||
best = rule
|
||||
|
||||
@@ -15,17 +15,18 @@
|
||||
<field name="inherit_id"
|
||||
ref="fusion_plating_configurator.view_fp_direct_order_wizard_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<!-- Wizard line list (main editable rows) -->
|
||||
<xpath expr="//field[@name='line_ids']/list/field[@name='coating_config_id']"
|
||||
<!-- Wizard line list (main editable rows). Anchor on
|
||||
internal_description (stable, configurator-defined). -->
|
||||
<xpath expr="//field[@name='line_ids']/list/field[@name='internal_description']"
|
||||
position="after">
|
||||
<field name="customer_spec_id"
|
||||
string="Specification"
|
||||
options="{'no_quick_create': True}"
|
||||
optional="show"/>
|
||||
</xpath>
|
||||
<!-- Wizard line drawer / form view (the "expand line" panel) -->
|
||||
<xpath expr="//field[@name='line_ids']/form//field[@name='coating_config_id']"
|
||||
position="after">
|
||||
<!-- Wizard line drawer / form view -->
|
||||
<xpath expr="//field[@name='line_ids']/form//field[@name='process_variant_id']"
|
||||
position="before">
|
||||
<field name="customer_spec_id"
|
||||
string="Specification"
|
||||
options="{'no_quick_create': True}"/>
|
||||
|
||||
@@ -16,7 +16,9 @@
|
||||
<field name="inherit_id"
|
||||
ref="fusion_plating_configurator.view_fp_part_catalog_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='x_fc_default_coating_config_id']"
|
||||
<!-- Anchor on default_process_id (stable, in core).
|
||||
Default Treatment block was removed in Phase E. -->
|
||||
<xpath expr="//field[@name='default_process_id']"
|
||||
position="after">
|
||||
<field name="x_fc_default_customer_spec_id"
|
||||
string="Default Specification"
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
<field name="inherit_id"
|
||||
ref="fusion_plating_configurator.view_fp_pricing_rule_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='coating_config_id']"
|
||||
position="after">
|
||||
<xpath expr="//field[@name='substrate_material']"
|
||||
position="before">
|
||||
<field name="customer_spec_id"
|
||||
options="{'no_quick_create': True}"/>
|
||||
<field name="recipe_id"
|
||||
|
||||
@@ -69,8 +69,6 @@
|
||||
placeholder="All specs if empty"/>
|
||||
<field name="recipe_ids" widget="many2many_tags"
|
||||
placeholder="All recipes if empty"/>
|
||||
<field name="coating_config_ids" widget="many2many_tags"
|
||||
placeholder="All coatings if empty"/>
|
||||
<field name="step_kind"
|
||||
invisible="trigger_type != 'job_step_done'"
|
||||
placeholder="Any step kind if empty"/>
|
||||
|
||||
@@ -17,15 +17,18 @@
|
||||
|
||||
<!-- Configurator's view_sale_order_form_fp inherits sale.view_order_form
|
||||
and adds Plating fields to the order_line tree. We inherit THAT
|
||||
view to add Specification right after Primary Treatment. -->
|
||||
view to add Specification next to Part Catalog. -->
|
||||
<record id="view_sale_order_form_quality_inherit" model="ir.ui.view">
|
||||
<field name="name">sale.order.form.quality.spec.inherit</field>
|
||||
<field name="model">sale.order</field>
|
||||
<field name="inherit_id"
|
||||
ref="fusion_plating_configurator.view_sale_order_form_fp"/>
|
||||
<field name="arch" type="xml">
|
||||
<!-- Editable order_line tree (estimator's main grid) -->
|
||||
<xpath expr="//field[@name='order_line']/list/field[@name='x_fc_coating_config_id']"
|
||||
<!-- Editable order_line tree (estimator's main grid).
|
||||
Anchor on x_fc_internal_description because it's
|
||||
unique to the editable list (not in the read-only
|
||||
summary list at the form bottom). -->
|
||||
<xpath expr="//field[@name='x_fc_internal_description']"
|
||||
position="after">
|
||||
<field name="x_fc_customer_spec_id"
|
||||
string="Specification"
|
||||
|
||||
Reference in New Issue
Block a user