Files
gsinghpal f08f328688 changes
2026-04-27 00:11:18 -04:00

191 lines
7.6 KiB
Python

# Walk part creation + 4 process variants step by step.
# Personas:
# Bob (Estimator) — owns the part catalog, designs process variants
# Sarah (CSR) — picks a variant on order entry
#
# Goal: prove that
# 1) Bob can create a part
# 2) Bob can attach 4 distinct process variants via the Composer flow
# 3) One is flagged default; switching default works
# 4) Sarah opens a Direct Order, picks the part — variant dropdown lists ALL FOUR
# 5) Sarah picks a non-default variant; the SO + job actually use it
from odoo import fields
from odoo.addons.fusion_plating_configurator.controllers.fp_part_composer_controller \
import _list_variants, _clone_subtree
P = env['res.partner']
Part = env['fp.part.catalog']
Coating = env['fp.coating.config']
Treat = env['fp.treatment']
Node = env['fusion.plating.process.node']
Tpl = Node # template recipes are also fp.process.node records
# ====================================================================== STEP 2
print('='*72)
print('STEP 2 — Bob creates a brand-new part')
print('='*72)
target_partner = P.browse(2529) # 2CM INNOVATIVE
default_coating = Coating.search([], limit=1)
default_treats = Treat.search([], limit=2)
part = Part.create({
'partner_id': target_partner.id,
'part_number': 'E2E-VAR-' + fields.Datetime.now().strftime('%H%M%S'),
'revision': 'A',
'name': 'E2E variant test bracket',
'substrate_material': 'aluminium',
'surface_area': 12.5,
'surface_area_uom': 'sq_in',
'weight': 0.45,
'complexity': 'simple',
'masking_zones': 1,
'x_fc_default_coating_config_id': default_coating.id,
'x_fc_default_treatment_ids': [(6, 0, default_treats.ids)],
})
print(f'[Bob] Created part: {part.display_name} (id={part.id})')
print(f' default coating: {part.x_fc_default_coating_config_id.name}')
print(f' default treatments: {default_treats.mapped("name")}')
print(f' process_variant_count (BEFORE adding any): {part.process_variant_count}')
# Find a shared template recipe to clone from. Templates = fp.process.node
# records with node_type='recipe', parent_id=False, part_catalog_id=False.
template = Node.search([
('node_type', '=', 'recipe'),
('parent_id', '=', False),
('part_catalog_id', '=', False),
], limit=1)
if not template:
print(' ❌ No shared template recipes available — cannot continue!')
raise SystemExit
print(f'[Bob] Will clone from shared template: {template.name} ({len(template.child_ids)} root children)')
# ====================================================================== STEP 3
print()
print('='*72)
print('STEP 3 — Bob adds variant #1: Standard Production')
print('='*72)
v1 = _clone_subtree(env, template, part, parent=False)
v1.variant_label = 'Standard Production'
v1.is_default_variant = True
part.default_process_id = v1.id
print(f'[Bob] Created variant: {v1.variant_label} (root node id={v1.id}, name="{v1.name}")')
print(f' is_default: {v1.is_default_variant}')
print(f' child nodes cloned: {len(v1.child_ids)}')
# ====================================================================== STEP 4
print()
print('='*72)
print('STEP 4 — Bob adds variant #2: Aerospace Cert (AS9100)')
print('='*72)
v2 = _clone_subtree(env, template, part, parent=False)
v2.variant_label = 'Aerospace Cert (AS9100)'
print(f'[Bob] Created variant: {v2.variant_label} (root id={v2.id})')
print(f' is_default: {v2.is_default_variant} (correct — first one stays default)')
# ====================================================================== STEP 5
print()
print('='*72)
print('STEP 5 — Bob adds variant #3: Quick-turn (no bake)')
print('='*72)
v3 = _clone_subtree(env, template, part, parent=False)
v3.variant_label = 'Quick-turn (no bake)'
print(f'[Bob] Created variant: {v3.variant_label} (root id={v3.id})')
# ====================================================================== STEP 6
print()
print('='*72)
print('STEP 6 — Bob adds variant #4: Heavy build (wear)')
print('='*72)
v4 = _clone_subtree(env, template, part, parent=False)
v4.variant_label = 'Heavy build (wear)'
print(f'[Bob] Created variant: {v4.variant_label} (root id={v4.id})')
# Refresh the part and inspect what the form would show.
part.invalidate_recordset()
print()
print(f'[Bob] After 4 adds — part {part.display_name}:')
print(f' process_variant_count: {part.process_variant_count}')
print(f' default_process_id: {part.default_process_id.name if part.default_process_id else None}')
print(f' Variants list (per Composer endpoint /fp/part/composer/state):')
for entry in _list_variants(part):
flag = '★ default' if entry['is_default'] else ' '
print(f' {flag} id={entry["id"]:>5} "{entry["label"]}"{entry["node_count"]} nodes')
# ====================================================================== STEP 7
print()
print('='*72)
print('STEP 7 — Sarah enters a Direct Order, picks the part, picks a variant')
print('='*72)
W = env['fp.direct.order.wizard']
Line = env['fp.direct.order.line']
w = W.create({
'partner_id': target_partner.id, 'po_pending': True,
'po_number': 'PO-VARTEST-001',
'invoice_strategy': 'net_terms',
})
w._onchange_partner_id()
# Sarah adds a line, picks the part. Onchange should pre-fill default coating.
ln = Line.new({'wizard_id': w.id})
ln.part_catalog_id = part
ln._onchange_part_clears_variant()
print(f'[Sarah] Picked part {part.part_number}.')
print(f' Pre-filled coating: {ln.coating_config_id.name if ln.coating_config_id else "(none)"}')
print(f' Pre-filled treatments: {ln.treatment_ids.mapped("name") if ln.treatment_ids else "(none)"}')
# What variants would the dropdown show? Inspect process_variant_id field domain.
print()
print(f'[Sarah] Looking at the Variant dropdown on the line:')
# Domain on x_fc_process_variant_id (defined on sale.order.line) is part-scoped.
# For the wizard line it's process_variant_id with the same domain.
visible_variants = part.process_variant_ids
print(f' Domain: part_scoped (id, child_of, ...). Visible variants: {len(visible_variants)}')
for v in visible_variants:
flag = '' if v.is_default_variant else ' '
print(f' {flag} {v.variant_label or v.name} (id={v.id})')
# Sarah picks variant #3 (Quick-turn).
ln.process_variant_id = v3
print()
print(f'[Sarah] Picked variant: {ln.process_variant_id.variant_label}')
# Persist via Line.create with the chosen variant.
new_line = Line.create({
'wizard_id': w.id,
'part_catalog_id': part.id,
'coating_config_id': default_coating.id,
'process_variant_id': v3.id,
'quantity': 5,
'unit_price': 25.0,
})
print(f' Saved line: process_variant_id={new_line.process_variant_id.variant_label}')
# ====================================================================== STEP 8
print()
print('='*72)
print('STEP 8 — Confirm SO; verify the JOB uses variant #3, not the default')
print('='*72)
result = w.action_create_order()
so = env['sale.order'].browse(result['res_id'])
print(f'[Sarah] SO created: {so.name}')
# Inspect the SO line's variant.
sol = so.order_line[:1]
print(f' SO line process_variant_id: {sol.x_fc_process_variant_id.variant_label if sol.x_fc_process_variant_id else "(none)"}')
# Confirm the SO.
so.action_confirm()
job = env['fp.job'].search([('sale_order_id', '=', so.id)], limit=1)
print(f' Job created: {job.name}')
print(f' Job recipe_id: {job.recipe_id.name if job.recipe_id else "(none)"}')
print(f' EXPECTED: recipe_id should match variant #3 root (id={v3.id}, name="{v3.name}")')
print(f' ACTUAL: recipe_id={job.recipe_id.id} (name="{job.recipe_id.name}")')
if job.recipe_id.id == v3.id:
print(f' ✓ Job correctly inherited the picked variant')
else:
print(f' ❌ Job did NOT use the picked variant! Recipe is {job.recipe_id.name}, expected {v3.name}')
env.cr.commit()
print()
print('== Walk complete ==')