fix(jobs): override_map always wins in _is_node_included + 2 wiring fixes
Three cascading bugs caused DOD-00153/WO-30061 to confirm with zero steps (and DOD-00150 to keep masking/bake even with overrides): 1. _is_node_included() in fp_job._generate_steps_from_recipe consulted the per-job override_map ONLY when node.opt_in_out was 'opt_in' or 'opt_out'. Default is 'disabled' (mandatory), so overrides on mandatory recipe nodes (Masking, De-Masking, Oven baking) were silently ignored. Fix: consult override_map FIRST — explicit per-job override always wins, regardless of node's opt_in_out value. 2. fp.direct.order.line.recipe_choice_ids didn't include the wizard's material_process recipe (Express Orders order-level recipe), so the line's process_variant_id domain rejected propagation. Added a 4th tier to the compute that pulls the order's header recipe in. 3. sale_order._fp_resolve_recipe_for_line fell back from line picker to part.default_process_id with nothing between. Added Express header recipe (self.x_fc_material_process) as a 2nd-priority fallback — catches cases where G3 propagation failed to reach the line but the SO header has the recipe set. Also fixed an unrelated G4 bug: _FP_PART_SYNC_FIELDS mapped process_variant_id → 'default_process_variant_id' which doesn't exist. Real field is 'default_process_id' (singular). Cleaned up DOD-00153/WO-30061 manually: backfilled line + job.recipe_id, regenerated steps with overrides respected. 8 steps now visible, masking/bake correctly omitted.
This commit is contained in:
@@ -81,7 +81,8 @@ class FpDirectOrderLine(models.Model):
|
||||
string='Allowed Recipes (computed)',
|
||||
)
|
||||
|
||||
@api.depends('part_catalog_id', 'wizard_id.partner_id')
|
||||
@api.depends('part_catalog_id', 'wizard_id.partner_id',
|
||||
'wizard_id.material_process')
|
||||
def _compute_recipe_choice_ids(self):
|
||||
Node = self.env['fusion.plating.process.node']
|
||||
SOL = self.env['sale.order.line']
|
||||
@@ -107,6 +108,11 @@ class FpDirectOrderLine(models.Model):
|
||||
], order='create_date desc', limit=500
|
||||
).mapped('x_fc_process_variant_id')
|
||||
ids.update(used.ids)
|
||||
# 4) The wizard's order-level Material/Process recipe — must be
|
||||
# selectable on the line so the G3 propagation can write it
|
||||
# without the domain rejecting (2026-05-27 fix).
|
||||
if rec.wizard_id and rec.wizard_id.material_process:
|
||||
ids.add(rec.wizard_id.material_process.id)
|
||||
rec.recipe_choice_ids = [(6, 0, list(ids))]
|
||||
save_as_default_process = fields.Boolean(
|
||||
string='Set as Part Default',
|
||||
@@ -652,7 +658,9 @@ class FpDirectOrderLine(models.Model):
|
||||
'bake_instructions': 'default_bake_instructions',
|
||||
'thickness_range': 'x_fc_default_thickness_range',
|
||||
'masking_enabled': 'default_masking_enabled',
|
||||
'process_variant_id': 'default_process_variant_id',
|
||||
# Real field is default_process_id on fp.part.catalog (singular,
|
||||
# not default_process_variant_id which doesn't exist).
|
||||
'process_variant_id': 'default_process_id',
|
||||
}
|
||||
|
||||
def _fp_sync_to_part(self):
|
||||
|
||||
@@ -1383,15 +1383,23 @@ class FpJob(models.Model):
|
||||
"""Determine if a node should be included based on
|
||||
opt-in/out logic, per-job overrides, and start-at-node
|
||||
filter.
|
||||
|
||||
Override map (per-job override rows) ALWAYS wins
|
||||
regardless of the node's opt_in_out setting. This is the
|
||||
escape hatch the Express Orders flow relies on to opt
|
||||
out of 'disabled' (mandatory) nodes like masking + bake.
|
||||
Without this, overrides on disabled nodes were silently
|
||||
ignored.
|
||||
"""
|
||||
nid = node.id
|
||||
if allowed_ids is not None and nid not in allowed_ids:
|
||||
return False
|
||||
# Explicit per-job override wins over any default behaviour.
|
||||
if nid in override_map:
|
||||
return override_map[nid]
|
||||
opt = node.opt_in_out or 'disabled'
|
||||
if opt == 'disabled':
|
||||
return True
|
||||
if nid in override_map:
|
||||
return override_map[nid]
|
||||
if opt == 'opt_in':
|
||||
return False # Default excluded
|
||||
return True # opt_out → default included
|
||||
|
||||
@@ -361,15 +361,17 @@ class SaleOrder(models.Model):
|
||||
return super().unlink()
|
||||
|
||||
def _fp_resolve_recipe_for_line(self, line):
|
||||
"""4-tier recipe resolution. Used BOTH for grouping (Task 6
|
||||
recipe-driven WO splits) AND for the per-job vals construction.
|
||||
"""Recipe resolution with Express-Orders SO header fallback.
|
||||
|
||||
Priority (most-specific first):
|
||||
1. line.x_fc_process_variant_id — Sarah explicitly picked a
|
||||
part-scoped variant on this order line. Always wins.
|
||||
2. part.default_process_id — part's flagged default
|
||||
1. line.x_fc_process_variant_id — explicit per-line variant
|
||||
(always wins; this is where G3 propagation lands a value).
|
||||
2. self.x_fc_material_process — Express Orders order-level
|
||||
recipe. Catches the case where G3 propagation failed to
|
||||
reach the line but the header has the recipe.
|
||||
3. part.default_process_id — part's flagged default
|
||||
variant. Customer-and-part-tuned recipe.
|
||||
3. part.recipe_id — legacy fallback.
|
||||
4. part.recipe_id — legacy fallback.
|
||||
Returns the recipe record or an empty recordset.
|
||||
"""
|
||||
Node = self.env['fusion.plating.process.node']
|
||||
@@ -384,6 +386,9 @@ class SaleOrder(models.Model):
|
||||
) or False
|
||||
if picked:
|
||||
return picked
|
||||
# Express Orders header recipe (2026-05-27 fallback)
|
||||
if 'x_fc_material_process' in self._fields and self.x_fc_material_process:
|
||||
return self.x_fc_material_process
|
||||
if part and 'default_process_id' in part._fields and part.default_process_id:
|
||||
return part.default_process_id
|
||||
if part and 'recipe_id' in part._fields and part.recipe_id:
|
||||
|
||||
Reference in New Issue
Block a user