Merge: self-heal WOs missing recipe/steps (fusion_plating_jobs 19.0.12.6.0; deployed to entech)
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
# License OPL-1 (Odoo Proprietary License v1.0)
|
# License OPL-1 (Odoo Proprietary License v1.0)
|
||||||
{
|
{
|
||||||
'name': 'Fusion Plating - Native Jobs',
|
'name': 'Fusion Plating - Native Jobs',
|
||||||
'version': '19.0.12.5.0',
|
'version': '19.0.12.6.0',
|
||||||
'category': 'Manufacturing/Plating',
|
'category': 'Manufacturing/Plating',
|
||||||
'summary': 'Native plating job model - replaces mrp.production / mrp.workorder bridge.',
|
'summary': 'Native plating job model - replaces mrp.production / mrp.workorder bridge.',
|
||||||
'author': 'Nexa Systems Inc.',
|
'author': 'Nexa Systems Inc.',
|
||||||
|
|||||||
@@ -1385,6 +1385,70 @@ class FpJob(models.Model):
|
|||||||
# - Drops work_role_id (not on fp.job.step yet - Task 2.6+)
|
# - Drops work_role_id (not on fp.job.step yet - Task 2.6+)
|
||||||
# - Drops _fp_autofill_default_equipment (not yet on step)
|
# - Drops _fp_autofill_default_equipment (not yet on step)
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
|
def _fp_resync_recipe_from_so(self):
|
||||||
|
"""Re-resolve the recipe from this job's SO line(s) and build its
|
||||||
|
steps. Heals work orders created before the recipe was set on the
|
||||||
|
SO line (new parts, or the copy-from-quote path): once the
|
||||||
|
estimator sets the process variant on the line, the WO can pull it
|
||||||
|
in. Acts only on jobs with NO steps yet and not terminal, so
|
||||||
|
in-progress work is never disturbed. Idempotent. Returns True when
|
||||||
|
steps were generated.
|
||||||
|
"""
|
||||||
|
self.ensure_one()
|
||||||
|
if self.step_ids or self.state in ('done', 'cancelled'):
|
||||||
|
return False
|
||||||
|
so = self.sale_order_id
|
||||||
|
if not so:
|
||||||
|
return False
|
||||||
|
recipe = False
|
||||||
|
for line in self.sale_order_line_ids:
|
||||||
|
recipe = so._fp_resolve_recipe_for_line(line)
|
||||||
|
if recipe:
|
||||||
|
break
|
||||||
|
if not recipe:
|
||||||
|
return False
|
||||||
|
if self.recipe_id != recipe:
|
||||||
|
self.recipe_id = recipe.id
|
||||||
|
# Mirror action_confirm's post-recipe sequence so a healed WO
|
||||||
|
# matches a normally-confirmed one (steps, ready, express text).
|
||||||
|
self._generate_steps_from_recipe()
|
||||||
|
pending = self.step_ids.filtered(lambda s: s.state == 'pending')
|
||||||
|
if pending:
|
||||||
|
pending.write({'state': 'ready'})
|
||||||
|
if (self.recipe_id and self.step_ids
|
||||||
|
and 'x_fc_masking_enabled' in self.env['sale.order.line']._fields):
|
||||||
|
for sol in self.sale_order_line_ids:
|
||||||
|
if hasattr(sol, '_fp_apply_express_overrides_to_job'):
|
||||||
|
sol._fp_apply_express_overrides_to_job(self)
|
||||||
|
self.message_post(body=_(
|
||||||
|
'Recipe re-synced from the sale order (%(recipe)s); %(n)d '
|
||||||
|
'step(s) generated.'
|
||||||
|
) % {'recipe': recipe.display_name, 'n': len(self.step_ids)})
|
||||||
|
return True
|
||||||
|
|
||||||
|
def action_fp_resync_recipe_from_so(self):
|
||||||
|
"""Header button: re-pull the recipe from the SO and build steps
|
||||||
|
for work orders that came out empty because the recipe was set on
|
||||||
|
the SO line after the WO was created. Safe and idempotent.
|
||||||
|
"""
|
||||||
|
healed = self.env['fp.job']
|
||||||
|
for job in self:
|
||||||
|
if job._fp_resync_recipe_from_so():
|
||||||
|
healed |= job
|
||||||
|
if not healed:
|
||||||
|
return {
|
||||||
|
'type': 'ir.actions.client',
|
||||||
|
'tag': 'display_notification',
|
||||||
|
'params': {
|
||||||
|
'type': 'warning',
|
||||||
|
'message': _('Nothing to re-sync: no resolvable recipe '
|
||||||
|
'on the sale order, or the job already has '
|
||||||
|
'steps.'),
|
||||||
|
'sticky': False,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return True
|
||||||
|
|
||||||
def _generate_steps_from_recipe(self):
|
def _generate_steps_from_recipe(self):
|
||||||
"""Generate fp.job.step records from the assigned recipe.
|
"""Generate fp.job.step records from the assigned recipe.
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,18 @@ class SaleOrderLine(models.Model):
|
|||||||
for line in self:
|
for line in self:
|
||||||
old_qty_by_id[line.id] = line.product_uom_qty
|
old_qty_by_id[line.id] = line.product_uom_qty
|
||||||
result = super().write(vals)
|
result = super().write(vals)
|
||||||
|
# Recipe set/changed late on the line -> heal the linked WO that
|
||||||
|
# was created empty before the estimator picked the process. Only
|
||||||
|
# not-yet-started jobs (no steps) are touched.
|
||||||
|
if 'x_fc_process_variant_id' in vals:
|
||||||
|
Job = self.env['fp.job']
|
||||||
|
for line in self:
|
||||||
|
jobs = Job.search([
|
||||||
|
('sale_order_line_ids', 'in', line.id),
|
||||||
|
('state', 'not in', ('done', 'cancelled')),
|
||||||
|
])
|
||||||
|
for job in jobs.filtered(lambda j: not j.step_ids):
|
||||||
|
job.sudo()._fp_resync_recipe_from_so()
|
||||||
if 'product_uom_qty' not in vals:
|
if 'product_uom_qty' not in vals:
|
||||||
return result
|
return result
|
||||||
Job = self.env['fp.job']
|
Job = self.env['fp.job']
|
||||||
|
|||||||
@@ -20,6 +20,14 @@
|
|||||||
<field name="inherit_id" ref="fusion_plating.view_fp_job_form"/>
|
<field name="inherit_id" ref="fusion_plating.view_fp_job_form"/>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<xpath expr="//header" position="inside">
|
<xpath expr="//header" position="inside">
|
||||||
|
<!-- Heal a WO that was created before the recipe was set
|
||||||
|
on the SO line: re-pull the recipe and build steps.
|
||||||
|
Hidden once steps exist or the job is terminal. -->
|
||||||
|
<button name="action_fp_resync_recipe_from_so" type="object"
|
||||||
|
string="Re-sync Recipe from SO"
|
||||||
|
class="btn-secondary"
|
||||||
|
icon="fa-refresh"
|
||||||
|
invisible="state in ('done', 'cancelled') or step_ids"/>
|
||||||
<!-- Phase 1 - Tablet redesign. Opens the JobWorkspace OWL
|
<!-- Phase 1 - Tablet redesign. Opens the JobWorkspace OWL
|
||||||
client action focused on this WO. Primary entry point
|
client action focused on this WO. Primary entry point
|
||||||
for techs before the Landing kanban (Phase 3) ships;
|
for techs before the Landing kanban (Phase 3) ships;
|
||||||
|
|||||||
Reference in New Issue
Block a user