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

@@ -154,6 +154,14 @@ class FpJobStepInputWizard(models.TransientModel):
self.step_id.message_post(body=_(
'%(n)s step input(s) recorded by %(user)s'
) % {'n': captured, 'user': self.env.user.name})
# When the wizard was opened from "Finish & Next" we re-enter
# the step's finish-and-advance flow with a context flag so it
# skips the prompt-for-inputs branch and finishes directly.
if self.env.context.get('fp_advance_after_save'):
return self.step_id.with_context(
fp_after_inputs=True,
).action_finish_and_advance()
return {'type': 'ir.actions.act_window_close'}
@@ -207,6 +215,36 @@ class FpJobStepInputWizardLine(models.TransientModel):
for rec in self:
rec.is_authored = bool(rec.node_input_id)
# ---- Single-column value editor -----------------------------------------
# The previous wizard exposed FOUR value columns (text / number /
# yes-no / date) — operators saw 9 columns wide and got lost. We
# collapse them into one "Value" column whose widget routes to the
# right typed field based on input_type. Booleans and dates get
# their own dedicated field (still per-row) so the widget behaves
# naturally; everything else types into a single value box.
is_boolean_type = fields.Boolean(
compute='_compute_type_flags',
)
is_date_type = fields.Boolean(
compute='_compute_type_flags',
)
is_numeric_type = fields.Boolean(
compute='_compute_type_flags',
)
@api.depends('input_type')
def _compute_type_flags(self):
numeric_types = {
'number', 'temperature', 'thickness',
'time_seconds',
}
for rec in self:
it = rec.input_type or 'text'
rec.is_boolean_type = it in ('boolean', 'pass_fail')
rec.is_date_type = it == 'date'
rec.is_numeric_type = it in numeric_types
def _has_value(self):
self.ensure_one()
return any([

View File

@@ -11,38 +11,58 @@
<field name="step_id" readonly="1"/>
<field name="job_id" readonly="1"/>
</group>
<separator string="Step Inputs"/>
<separator string="Measurements"/>
<p class="text-muted" invisible="line_ids">
No authored prompts on this recipe step. Click
<strong>Add a line</strong> below to record one or
more ad-hoc measurements (operator name + value).
Authored prompts will appear here automatically once
the recipe gets `step_input` rows in the Process
Composer.
Click <strong>Add a line</strong> to record one or
more measurements for this step.
</p>
<field name="line_ids">
<list editable="bottom">
<field name="is_authored" column_invisible="1"/>
<field name="is_boolean_type" column_invisible="1"/>
<field name="is_date_type" column_invisible="1"/>
<field name="is_numeric_type" column_invisible="1"/>
<field name="name"
string="Measurement"
readonly="is_authored"
placeholder="e.g. Oven Temp, Operator Initials, Bath Reading"/>
placeholder="e.g. Oven Temp, Bath Reading, Operator Initials"/>
<field name="input_type"
string="Type"
readonly="is_authored"/>
<field name="target_unit"
string="Unit"
readonly="is_authored"
placeholder="number / text / boolean / date"
optional="show"/>
<field name="target_min" readonly="is_authored" optional="hide"/>
<field name="target_max" readonly="is_authored" optional="hide"/>
<field name="target_unit" readonly="is_authored" optional="show"/>
<field name="value_text"/>
<field name="value_number"/>
<field name="value_boolean" widget="boolean_toggle"/>
<field name="value_date"/>
<!-- Distinct column labels so the operator
reads which input matches the row's
type. List-view columns are static in
Odoo — labelling each by its purpose
removes the "four identical Value
columns" guesswork from the previous
layout. Only the cell matching the
row's type stays editable; others sit
blank. -->
<field name="value_number"
string="Number"
invisible="not is_numeric_type"/>
<field name="value_boolean"
string="Yes / No"
widget="boolean_toggle"
invisible="not is_boolean_type"/>
<field name="value_date"
string="Date / Time"
invisible="not is_date_type"/>
<field name="value_text"
string="Text"
invisible="is_numeric_type or is_boolean_type or is_date_type"/>
<field name="target_min" optional="hide"/>
<field name="target_max" optional="hide"/>
</list>
</field>
</sheet>
<footer>
<button name="action_commit" type="object"
string="Record" class="btn-primary"/>
string="Save" class="btn-primary"/>
<button string="Cancel" class="btn-secondary"
special="cancel"/>
</footer>

View File

@@ -115,7 +115,14 @@ class FpJobStepMoveWizard(models.TransientModel):
if from_step.exists():
defaults['from_step_id'] = from_step.id
defaults['job_id'] = from_step.job_id.id
defaults['qty_moved'] = int(from_step.job_id.qty or 1)
# Default to "qty currently here", not "job total". A job
# already mid-flight may have parts split across steps;
# pre-filling with the full job qty would silently let
# the operator move more than is actually parked here.
# Fall back to job qty when qty_at_step is 0 (e.g.
# opened on a fresh step before any movement).
qty_here = int(from_step.qty_at_step or 0)
defaults['qty_moved'] = qty_here or int(from_step.job_id.qty or 1)
# Next sequenced step that isn't done/cancelled
next_step = self.env['fp.job.step'].search([
('job_id', '=', from_step.job_id.id),
@@ -222,6 +229,29 @@ class FpJobStepMoveWizard(models.TransientModel):
if not self.from_step_id or not self.to_step_id:
raise UserError(_('Pick both From and To steps before moving.'))
# Partial-qty guards. The operator can't move more than is
# parked at the from-step, and zero/negative is meaningless.
# Self-loop moves (input recording) bypass the upper bound
# because they don't move qty.
if self.qty_moved <= 0:
raise UserError(_(
'Qty Moved must be at least 1. Use Skip on the step row '
'instead if no parts are being processed.'
))
is_self_loop = (self.from_step_id == self.to_step_id)
if not is_self_loop:
qty_here = int(self.from_step_id.qty_at_step or 0)
if qty_here > 0 and self.qty_moved > qty_here:
raise UserError(_(
'Cannot move %(req)s parts — only %(here)s currently '
'parked at "%(step)s". Adjust Qty Moved or split '
'across multiple moves.'
) % {
'req': self.qty_moved,
'here': qty_here,
'step': self.from_step_id.name,
})
Move = self.env['fp.job.step.move']
move = Move.create({
'job_id': self.job_id.id,