This commit is contained in:
gsinghpal
2026-04-20 01:16:12 -04:00
parent 8217bb0ff6
commit 54e56ed0e6
39 changed files with 5600 additions and 1131 deletions

View File

@@ -199,6 +199,41 @@ class FpProcessNode(models.Model):
tracking=True,
)
# ---- Recipe-only fields (apply when node_type='recipe') -----------------
# These migrate Steelhead's recipe-level metadata: lead time, the
# product/service tied to this recipe, the contract review approver
# roster, and the pricing builders to apply when this recipe is on
# a quote. They're loose-coupled to keep non-recipe nodes clean.
default_lead_time = fields.Float(
string='Default Lead Time (days)',
digits=(8, 2),
help='When an MO is created using this recipe, '
'date_planned_finished is set to NOW + lead_time.',
tracking=True,
)
product_id = fields.Many2one(
'product.product',
string='Service / Product',
ondelete='set null',
help='The plating service product this recipe sells. When the '
'product appears on a sale order, the resulting MO can '
'auto-pick this recipe.',
tracking=True,
)
contract_review_user_ids = fields.Many2many(
'res.users',
relation='fp_process_node_contract_review_user_rel',
column1='node_id',
column2='user_id',
string='Contract Review Approvers',
help='Users authorised to sign off the Contract Review work order '
'on jobs running this recipe. Anyone outside this list will '
'be blocked from finishing the WO.',
)
# NB. `pricing_rule_ids` lives in fusion_plating_configurator
# (added there so this core module doesn't depend on the configurator).
# ---- Computed fields -----------------------------------------------------
display_name = fields.Char(
@@ -270,6 +305,73 @@ class FpProcessNode(models.Model):
raise ValidationError(
_('A process node cannot be its own ancestor.'))
# ---- Version auto-bump ---------------------------------------------------
# Any meaningful edit / add / delete inside a recipe bumps the recipe
# root's `version` field by one. Lets shop managers see at a glance
# how stable a recipe is and (later) lets a job pin to a specific
# recipe revision so already-running MOs don't see mid-flight changes.
# Fields that don't represent a "meaningful" change — adjusting these
# alone does not bump the version. `version` itself is in the list to
# avoid an infinite write loop.
_FP_NON_VERSIONED_FIELDS = {
'version', 'write_date', 'write_uid',
'create_date', 'create_uid',
'parent_path', 'display_name', 'recipe_root_id', 'depth',
}
def _fp_bump_recipe_versions(self):
"""Increment `version` by 1 on the distinct recipe roots covering
the current recordset."""
roots = self.mapped('recipe_root_id')
# _compute_recipe_root_id falls back to self for nodes whose
# parent_path isn't yet stored — pick those up too.
for rec in self:
if not rec.recipe_root_id and rec.node_type == 'recipe':
roots |= rec
if not roots:
return
# Use a direct SQL update so we (a) skip our own write override
# and (b) avoid touching write_date / write_uid on the root,
# which would itself be a no-op-but-noisy chatter event.
self.env.cr.execute(
'UPDATE fusion_plating_process_node '
'SET version = COALESCE(version, 0) + 1 '
'WHERE id IN %s',
(tuple(roots.ids),),
)
roots.invalidate_recordset(['version'])
@api.model_create_multi
def create(self, vals_list):
records = super().create(vals_list)
# Skip non-recipe roots — only count when the new node lives
# inside an existing recipe.
descendants = records.filtered(lambda r: r.node_type != 'recipe')
if descendants:
descendants._fp_bump_recipe_versions()
return records
def write(self, vals):
meaningful = bool(set(vals.keys()) - self._FP_NON_VERSIONED_FIELDS)
res = super().write(vals)
if meaningful and self:
self._fp_bump_recipe_versions()
return res
def unlink(self):
# Snapshot the affected recipe roots BEFORE delete, otherwise
# recipe_root_id becomes unreachable on the deleted records.
roots = self.mapped('recipe_root_id')
descendants = self.filtered(lambda r: r.node_type != 'recipe')
# Delete first so we don't bump the version of a recipe that's
# being removed entirely.
res = super().unlink()
survivors = roots.exists()
if descendants and survivors:
survivors._fp_bump_recipe_versions()
return res
# ---- Tree data for OWL component -----------------------------------------
def get_tree_data(self):