changes
This commit is contained in:
@@ -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([
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user