This commit is contained in:
gsinghpal
2026-04-29 03:35:33 -04:00
parent 6ac6d24da6
commit a2fe1fcbcc
61 changed files with 4655 additions and 667 deletions

View File

@@ -202,9 +202,9 @@ class FpJob(models.Model):
job.racking_inspection_state = ri.state if ri else False
def action_view_racking_inspection(self):
"""Open the racking inspection. Auto-create if missing (e.g. job
was created before Sub 8 shipped, or auto-create silently failed
at action_confirm time)."""
"""Open the racking inspection. Auto-create if missing, or seed
lines from the SO if it exists but was created before line auto-
seeding shipped (the helper handles both cases idempotently)."""
self.ensure_one()
if 'fp.racking.inspection' not in self.env:
from odoo.exceptions import UserError
@@ -212,9 +212,12 @@ class FpJob(models.Model):
'Sub 8 racking inspection module not installed. '
'Install fusion_plating_receiving to enable.'
))
if not self.racking_inspection_id:
self._fp_create_racking_inspection()
self.invalidate_recordset(['racking_inspection_ids'])
# Always call the helper — it short-circuits for already-populated
# draft inspections and creates fresh ones when missing. This is
# also the entry point that backfills lines on inspections that
# pre-date the line-seeding feature.
self._fp_create_racking_inspection()
self.invalidate_recordset(['racking_inspection_ids'])
ri = self.racking_inspection_id or self.racking_inspection_ids[:1]
if not ri:
from odoo.exceptions import UserError
@@ -239,11 +242,39 @@ class FpJob(models.Model):
'context': {'default_job_id': self.id},
}
def action_finish_current_step(self):
"""Steelhead-style header button: finish whatever's currently
in_progress and auto-start the next pending/ready step. If
nothing is running yet, start the lowest-sequence pending step
instead — operator's first click on a fresh job just begins
the line.
"""
self.ensure_one()
running = self.step_ids.filtered(lambda s: s.state == 'in_progress')[:1]
if running:
return running.action_finish_and_advance()
# No running step — kick off the first pending/ready one.
first = self.step_ids.filtered(
lambda s: s.state in ('pending', 'ready', 'paused')
).sorted('sequence')[:1]
if not first:
raise UserError(_(
'No runnable step found on this job — either every step '
'is done or the job is still in draft.'
))
first.with_context(fp_skip_predecessor_check=True).button_start()
self.message_post(body=_(
'Started first step "%s".'
) % first.name)
return True
def action_open_move_wizard(self):
"""Header button — opens the Move wizard pre-filled with the
currently in-progress (or most recently in-progress) step as the
from-step. Lets the manager move the job forward without first
clicking into a specific step row.
"""Original Move wizard — kept available for cross-station moves
and rework / scrap transfers. The simple "finish current → start
next" flow is now action_finish_current_step (header button).
Opens the wizard pre-filled with the currently in-progress (or
most recently in-progress) step as the from-step.
"""
self.ensure_one()
active_step = self.step_ids.filtered(
@@ -871,6 +902,9 @@ class FpJob(models.Model):
production_id too so legacy reports keep working.
Idempotent — if an inspection already exists for this job, skip.
Either way the inspection's lines are seeded from the SO's
plating order lines so the racker walks into a pre-populated
checklist instead of an empty form.
"""
self.ensure_one()
if 'fp.racking.inspection' not in self.env:
@@ -883,17 +917,62 @@ class FpJob(models.Model):
('x_fc_job_id', '=', self.id),
], limit=1)
if existing:
# Self-heal: pre-existing inspections from before line seeding
# was added show up empty. Top them up now if still empty +
# the inspection isn't already finalised (don't rewrite history).
if not existing.line_ids and existing.state == 'draft':
self._fp_seed_racking_lines(existing)
return
# Phase 6 (Sub 11) — production_id retired; bind by x_fc_job_id only.
vals = {'x_fc_job_id': self.id}
try:
Inspection.create(vals)
insp = Inspection.create(vals)
self._fp_seed_racking_lines(insp)
except Exception as e:
_logger.warning(
"Job %s: failed to auto-create racking inspection: %s",
self.name, e,
)
def _fp_seed_racking_lines(self, inspection):
"""Populate the inspection with one line per SO plating order line.
Walks sale_order_line_ids (the M2M of SO lines tied to this job),
falling back to the linked SO's order_line. Each line carries the
part_catalog and the quoted qty as the expected count — the
racker confirms or amends on the floor.
"""
self.ensure_one()
if not inspection or inspection.line_ids:
return
Line = self.env['fp.racking.inspection.line'].sudo()
# Source preference: explicit M2M of plating lines bound to this
# job (fast-order multi-part jobs), falling back to the SO header.
so_lines = self.sale_order_line_ids
if not so_lines and self.sale_order_id:
so_lines = self.sale_order_id.order_line
plating_lines = so_lines.filtered(
lambda l: l.x_fc_part_catalog_id and not l.display_type
)
if not plating_lines:
return
seq = 10
for sol in plating_lines:
try:
Line.create({
'inspection_id': inspection.id,
'sequence': seq,
'part_catalog_id': sol.x_fc_part_catalog_id.id,
'qty_expected': int(sol.product_uom_qty or 0),
'condition': 'ok',
})
except Exception as e:
_logger.warning(
"Job %s: failed to seed racking line for SO line %s: %s",
self.name, sol.id, e,
)
seq += 10
def _fp_create_portal_job(self):
"""Create the fusion.plating.portal.job mirror record."""
self.ensure_one()

View File

@@ -326,6 +326,98 @@ class FpJobStep(models.Model):
)) % (step.name, old, new, new - old, self.env.user.name))
return True
def action_finish_and_advance(self):
"""Steelhead-style "Finish & Next" — finish this step then auto-
start the next pending/ready step in sequence. Single click
replaces the prior Finish-then-Move-wizard dance.
If the step has authored step_input prompts AND none have been
captured yet, we route through the simplified Record Inputs
wizard first; saving the wizard re-enters here with the
`fp_after_inputs=True` context flag so we don't loop.
"""
self.ensure_one()
if self.state != 'in_progress':
raise UserError(_(
"Step '%s' is in state '%s' — start it before clicking Finish."
) % (self.name, self.state))
# Prompt-first behaviour: show the Record Inputs dialog when the
# recipe step has authored prompts and nothing has been captured
# in this run. Bypass when context flag is set (i.e. we're being
# called BACK from the wizard's commit), or when the operator
# already saved values via the Record Inputs button earlier.
if (not self.env.context.get('fp_after_inputs')
and self._fp_has_uncaptured_step_inputs()):
return self._fp_open_input_wizard(advance_after=True)
self.button_finish()
next_step = self._fp_next_runnable_step()
if next_step:
next_step.with_context(
fp_skip_predecessor_check=True,
).button_start()
self.job_id.message_post(body=_(
'Step "%(prev)s" finished — auto-started next step "%(next)s".'
) % {'prev': self.name, 'next': next_step.name})
return True
def _fp_next_runnable_step(self):
"""The lowest-sequence step on this job that isn't terminal yet
and isn't this one. Used by action_finish_and_advance."""
self.ensure_one()
candidates = self.job_id.step_ids.filtered(
lambda s: s.id != self.id
and s.state in ('pending', 'ready', 'paused')
).sorted('sequence')
return candidates[:1] or self.env['fp.job.step']
def _fp_has_uncaptured_step_inputs(self):
"""True when the recipe step defines step_input prompts AND
the user hasn't already saved values for this step's current
run via the Record Inputs wizard.
"""
self.ensure_one()
node = self.recipe_node_id
if not node:
return False
prompts = node.input_ids
if 'kind' in prompts._fields:
prompts = prompts.filtered(lambda i: i.kind == 'step_input')
if not prompts:
return False
# Has the operator already recorded values during this run?
# Heuristic: any in-place fp.job.step.move (transfer_type='step')
# for this step since date_started.
Move = self.env['fp.job.step.move']
already = Move.search_count([
('from_step_id', '=', self.id),
('transfer_type', '=', 'step'),
('move_datetime', '>=', self.date_started or fields.Datetime.now()),
])
return already == 0
def _fp_open_input_wizard(self, advance_after=False):
"""Open the simplified Record Inputs dialog. When advance_after
is True, the wizard's Save button finishes the step and starts
the next one as a single atomic flow."""
self.ensure_one()
action = self.env['ir.actions.act_window']._for_xml_id(
'fusion_plating_jobs.action_fp_job_step_input_wizard'
)
action['context'] = {
**dict(self.env.context),
'default_step_id': self.id,
'active_id': self.id,
'fp_advance_after_save': advance_after,
}
return action
# NB: action_open_input_wizard is defined further down (line ~829)
# — that one stays as the per-row "Record" button entry-point.
# _fp_open_input_wizard above adds the advance_after pathway used
# only by action_finish_and_advance.
def button_finish(self):
"""Override to:
1) Auto-spawn a bake.window when a wet plating step finishes