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>
128 lines
4.5 KiB
Python
128 lines
4.5 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Copyright 2026 Nexa Systems Inc.
|
|
# License OPL-1 (Odoo Proprietary License v1.0)
|
|
# Part of the Fusion Plating product family.
|
|
|
|
from . import controllers
|
|
from . import models
|
|
from . import wizard
|
|
|
|
|
|
def _backfill_currency(env):
|
|
"""Fill missing currency_id on existing money-holding records.
|
|
|
|
Older demo data and manually-created rows were persisted before the
|
|
`required=True` was added, so some records sit with currency_id=NULL
|
|
and Monetary fields render without a $ symbol. This runs on module
|
|
install/upgrade and pins them to the company's currency.
|
|
"""
|
|
company_currency = env.company.currency_id.id
|
|
if not company_currency:
|
|
return
|
|
for model_name in (
|
|
'fp.pricing.rule',
|
|
'fp.quote.configurator',
|
|
):
|
|
Model = env.get(model_name)
|
|
if Model is None:
|
|
continue
|
|
Model.search([('currency_id', '=', False)]).write(
|
|
{'currency_id': company_currency}
|
|
)
|
|
|
|
|
|
def _backfill_cloned_process_names(env):
|
|
"""Append " — <part_number> Rev <revision>" to every existing part-
|
|
cloned process ROOT whose name doesn't already carry the suffix.
|
|
|
|
Feedback on 2026-04-23: the Process tab on the part form was
|
|
showing a bare template name ("General Processing"), so users
|
|
couldn't tell at a glance that the clone belonged to THIS part.
|
|
The clone logic now adds the suffix automatically; this backfill
|
|
brings older clones up to the same format without forcing
|
|
users to re-compose (which would wipe their edits).
|
|
|
|
Idempotent: checks for a literal " — " separator before rewriting.
|
|
"""
|
|
Node = env['fusion.plating.process.node']
|
|
roots = Node.search([
|
|
('node_type', '=', 'recipe'),
|
|
('part_catalog_id', '!=', False),
|
|
('parent_id', '=', False),
|
|
])
|
|
renamed = 0
|
|
for root in roots:
|
|
part = root.part_catalog_id
|
|
if not part:
|
|
continue
|
|
if ' — ' in (root.name or ''):
|
|
continue # Already has a suffix — leave alone.
|
|
suffix_bits = []
|
|
if part.part_number:
|
|
suffix_bits.append(part.part_number)
|
|
if part.revision:
|
|
# `revision` sometimes already carries a "Rev " prefix
|
|
# (e.g. "Rev 2") — don't double up.
|
|
rev = part.revision.strip()
|
|
if not rev.lower().startswith('rev'):
|
|
rev = 'Rev %s' % rev
|
|
suffix_bits.append(rev)
|
|
if not suffix_bits:
|
|
continue
|
|
root.name = '%s — %s' % (root.name or '', ' '.join(suffix_bits))
|
|
renamed += 1
|
|
|
|
|
|
def _backfill_part_material_id(env):
|
|
"""Pin existing parts AND quote configurators to a row in the
|
|
shared material library.
|
|
|
|
Pre-Sub-12d, both models only had a `substrate_material` Selection.
|
|
This sets `material_id` on every record that doesn't yet have one,
|
|
matching by substrate_material → seed material XML id. Idempotent.
|
|
"""
|
|
Part = env['fp.part.catalog']
|
|
Material = env['fp.part.material']
|
|
if Part is None or Material is None:
|
|
return
|
|
# Map legacy Selection key → seed XML id (the generic per-category entry).
|
|
xmlid_by_key = {
|
|
'aluminium': 'fusion_plating_configurator.fp_material_aluminium',
|
|
'steel': 'fusion_plating_configurator.fp_material_steel',
|
|
'stainless': 'fusion_plating_configurator.fp_material_stainless',
|
|
'copper': 'fusion_plating_configurator.fp_material_copper',
|
|
'titanium': 'fusion_plating_configurator.fp_material_titanium',
|
|
'other': 'fusion_plating_configurator.fp_material_other',
|
|
}
|
|
cache = {}
|
|
for key, xmlid in xmlid_by_key.items():
|
|
rec = env.ref(xmlid, raise_if_not_found=False)
|
|
if rec:
|
|
cache[key] = rec.id
|
|
if not cache:
|
|
return
|
|
# Parts
|
|
for part in Part.search([('material_id', '=', False)]):
|
|
mid = cache.get(part.substrate_material)
|
|
if mid:
|
|
part.material_id = mid
|
|
# Quote configurators (same Selection key → same library)
|
|
Quote = env['fp.quote.configurator']
|
|
if Quote is not None:
|
|
for q in Quote.search([('material_id', '=', False)]):
|
|
mid = cache.get(q.substrate_material)
|
|
if mid:
|
|
q.material_id = mid
|
|
|
|
|
|
def post_init_hook(env):
|
|
_backfill_currency(env)
|
|
_backfill_cloned_process_names(env)
|
|
_backfill_part_material_id(env)
|
|
|
|
|
|
def post_upgrade_hook(env):
|
|
_backfill_currency(env)
|
|
_backfill_cloned_process_names(env)
|
|
_backfill_part_material_id(env)
|