feat(numbering): wire CoC/RCV/DLV/PU into parent-numbered mixin + rename counters
Per-model counter fields on sale.order renamed to x_fc_pn_*_count to avoid collision with pre-existing compute fields of the same short name in bridge_mrp / receiving / configurator (silent compute-override was suppressing the storage). 4 child models (fp.certificate, fp.receiving, fusion.plating.delivery, fusion.plating.pickup.request) now derive names as PFX-<parent> with -NN suffix from the 2nd onward. fusion.plating.pickup.request gains a sale_order_id field (optional) so pickups created against an SO get parent-derived names, while standalone pickups (pre-SO) fall back to PU/YYYY/NNNN. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -69,6 +69,7 @@ export class FpRecordInputsDialog extends Component {
|
||||
saving: false,
|
||||
stepName: "",
|
||||
jobName: "",
|
||||
recipeRootId: false,
|
||||
rows: [],
|
||||
// Operator's persisted initials — pre-filled into signature
|
||||
// / "Reviewer Initials" prompts on load. When the operator
|
||||
@@ -103,6 +104,7 @@ export class FpRecordInputsDialog extends Component {
|
||||
}
|
||||
this.state.stepName = data.step.name;
|
||||
this.state.jobName = data.job.name;
|
||||
this.state.recipeRootId = data.recipe_root_id || false;
|
||||
this.state.userInitials = data.user_initials || "";
|
||||
this.state.instructionsHtml = data.instructions_html || "";
|
||||
this.state.instructionImages = data.instruction_images || [];
|
||||
@@ -193,13 +195,14 @@ export class FpRecordInputsDialog extends Component {
|
||||
isSelection(row) { return row.input_type === "selection"; }
|
||||
isPassFail(row) { return row.input_type === "pass_fail"; }
|
||||
isSignature(row) { return row.input_type === "signature"; }
|
||||
// Fallback to text for anything else (text, time_hms, ...)
|
||||
isTimeHms(row) { return row.input_type === "time_hms"; }
|
||||
// Fallback to text for anything else
|
||||
isText(row) {
|
||||
return !this.isNumeric(row) && !this.isBoolean(row)
|
||||
&& !this.isDate(row) && !this.isPhoto(row)
|
||||
&& !this.isMulti(row) && !this.isPanel(row)
|
||||
&& !this.isSelection(row) && !this.isPassFail(row)
|
||||
&& !this.isSignature(row);
|
||||
&& !this.isSignature(row) && !this.isTimeHms(row);
|
||||
}
|
||||
|
||||
// Friendly label for the type pill — defaults to the raw key when no
|
||||
@@ -208,6 +211,60 @@ export class FpRecordInputsDialog extends Component {
|
||||
return TYPE_LABELS[row.input_type] || row.input_type || "Text";
|
||||
}
|
||||
|
||||
// Step granularity for <input type="number"> — drives the up/down
|
||||
// arrow increment AND the typed-decimal validity. Defaults of step=1
|
||||
// make tablet entry painful when the spec is 0.03 – 0.05 mil because
|
||||
// every arrow press jumps a full unit. Derive from the recipe-author's
|
||||
// target_min / target_max precision so operator arrow-taps move in the
|
||||
// same decimal magnitude the spec was written in. Falls back to
|
||||
// input-type defaults when no targets are set.
|
||||
stepFor(row) {
|
||||
const decimals = Math.max(
|
||||
this._fpCountDecimals(row.target_min),
|
||||
this._fpCountDecimals(row.target_max),
|
||||
);
|
||||
if (decimals > 0) {
|
||||
return Math.pow(10, -decimals).toFixed(decimals);
|
||||
}
|
||||
const t = row.input_type || "";
|
||||
if (t === "thickness" || t === "multi_point_thickness") return "0.0001";
|
||||
if (t === "ph") return "0.01";
|
||||
if (t === "temperature" || t === "time_seconds") return "1";
|
||||
return "any";
|
||||
}
|
||||
|
||||
_fpCountDecimals(n) {
|
||||
if (n === null || n === undefined || n === "" || n === 0) return 0;
|
||||
const s = String(n);
|
||||
const idx = s.indexOf(".");
|
||||
if (idx < 0) return 0;
|
||||
// Trim trailing zeros so "0.0500" doesn't look like 4-decimals
|
||||
// when the author actually wrote 2-decimal precision.
|
||||
return s.slice(idx + 1).replace(/0+$/, "").length;
|
||||
}
|
||||
|
||||
// Jump from the runtime dialog into the Simple Recipe Editor on the
|
||||
// EXACT recipe variant this job step is bound to. Closes the dialog
|
||||
// (operator returns by re-opening Record Inputs after editing). The
|
||||
// intent is to remove the "I edited the recipe but nothing changed"
|
||||
// confusion — they were editing a sibling variant.
|
||||
async openSimpleEditor() {
|
||||
if (!this.state.recipeRootId) {
|
||||
this.notification.add(
|
||||
_t("No recipe linked to this step yet."),
|
||||
{ type: "warning" },
|
||||
);
|
||||
return;
|
||||
}
|
||||
this.props.close();
|
||||
await this.action.doAction({
|
||||
type: "ir.actions.client",
|
||||
tag: "fp_simple_recipe_editor",
|
||||
name: _t("Edit Recipe"),
|
||||
context: { recipe_id: this.state.recipeRootId },
|
||||
});
|
||||
}
|
||||
|
||||
// True when the recipe author defined BOTH target_min and target_max
|
||||
// on the prompt — the signal that the operator is expected to capture
|
||||
// a range (multiple readings → record their min and max observation).
|
||||
|
||||
@@ -11,6 +11,12 @@
|
||||
Job <t t-esc="state.jobName"/>
|
||||
</span>
|
||||
</div>
|
||||
<button t-if="state.recipeRootId"
|
||||
class="btn btn-link o_fp_ri_edit_recipe"
|
||||
title="Edit this step's prompts (target ranges, type, options) in the Simple Recipe Editor."
|
||||
t-on-click="openSimpleEditor">
|
||||
<i class="fa fa-pencil me-1"/> Edit Recipe
|
||||
</button>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
@@ -116,7 +122,7 @@
|
||||
class="o_fp_ri_numeric">
|
||||
<input type="number"
|
||||
class="o_fp_ri_input o_fp_ri_input_numeric"
|
||||
step="any"
|
||||
t-att-step="stepFor(row)"
|
||||
t-model.number="row.value_number"
|
||||
t-att-placeholder="row.target_min or '0.00'"/>
|
||||
<t t-set="hint" t-value="rangeHint(row)"/>
|
||||
@@ -136,7 +142,7 @@
|
||||
<span class="o_fp_ri_dual_label">Min Reading</span>
|
||||
<input type="number"
|
||||
class="o_fp_ri_input o_fp_ri_input_numeric"
|
||||
step="any"
|
||||
t-att-step="stepFor(row)"
|
||||
t-model.number="row.value_min"
|
||||
t-att-placeholder="row.target_min or '0.00'"/>
|
||||
</label>
|
||||
@@ -144,7 +150,7 @@
|
||||
<span class="o_fp_ri_dual_label">Max Reading</span>
|
||||
<input type="number"
|
||||
class="o_fp_ri_input o_fp_ri_input_numeric"
|
||||
step="any"
|
||||
t-att-step="stepFor(row)"
|
||||
t-model.number="row.value_max"
|
||||
t-att-placeholder="row.target_max or '0.00'"/>
|
||||
</label>
|
||||
@@ -167,7 +173,7 @@
|
||||
<span class="o_fp_ri_dual_label">Min Reading</span>
|
||||
<input type="number"
|
||||
class="o_fp_ri_input o_fp_ri_input_numeric"
|
||||
step="any"
|
||||
t-att-step="stepFor(row)"
|
||||
t-model.number="row.value_min"
|
||||
t-att-placeholder="row.target_min or '0.00'"/>
|
||||
</label>
|
||||
@@ -175,7 +181,7 @@
|
||||
<span class="o_fp_ri_dual_label">Max Reading</span>
|
||||
<input type="number"
|
||||
class="o_fp_ri_input o_fp_ri_input_numeric"
|
||||
step="any"
|
||||
t-att-step="stepFor(row)"
|
||||
t-model.number="row.value_max"
|
||||
t-att-placeholder="row.target_max or '0.00'"/>
|
||||
</label>
|
||||
@@ -301,19 +307,19 @@
|
||||
<div t-if="isMulti(row)" class="o_fp_ri_multi">
|
||||
<div class="o_fp_ri_multi_grid">
|
||||
<label>R1
|
||||
<input type="number" step="any" t-model.number="row.point_1"/>
|
||||
<input type="number" t-att-step="stepFor(row)" t-model.number="row.point_1"/>
|
||||
</label>
|
||||
<label>R2
|
||||
<input type="number" step="any" t-model.number="row.point_2"/>
|
||||
<input type="number" t-att-step="stepFor(row)" t-model.number="row.point_2"/>
|
||||
</label>
|
||||
<label>R3
|
||||
<input type="number" step="any" t-model.number="row.point_3"/>
|
||||
<input type="number" t-att-step="stepFor(row)" t-model.number="row.point_3"/>
|
||||
</label>
|
||||
<label>R4
|
||||
<input type="number" step="any" t-model.number="row.point_4"/>
|
||||
<input type="number" t-att-step="stepFor(row)" t-model.number="row.point_4"/>
|
||||
</label>
|
||||
<label>R5
|
||||
<input type="number" step="any" t-model.number="row.point_5"/>
|
||||
<input type="number" t-att-step="stepFor(row)" t-model.number="row.point_5"/>
|
||||
</label>
|
||||
<div class="o_fp_ri_multi_avg">
|
||||
<span class="text-muted">Avg</span>
|
||||
@@ -325,20 +331,28 @@
|
||||
<!-- Bath chemistry panel — pH / conc / temp / bath -->
|
||||
<div t-if="isPanel(row)" class="o_fp_ri_panel">
|
||||
<label>pH
|
||||
<input type="number" step="any" t-model.number="row.panel_ph"/>
|
||||
<input type="number" step="0.01" t-model.number="row.panel_ph"/>
|
||||
</label>
|
||||
<label>Concentration
|
||||
<input type="number" step="any" t-model.number="row.panel_concentration"/>
|
||||
<input type="number" step="0.1" t-model.number="row.panel_concentration"/>
|
||||
</label>
|
||||
<label>Temperature
|
||||
<input type="number" step="any" t-model.number="row.panel_temperature"/>
|
||||
<input type="number" step="1" t-model.number="row.panel_temperature"/>
|
||||
</label>
|
||||
<label>Bath ID
|
||||
<input type="text" t-model="row.panel_bath_id"/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Text fallback (text, signature, time_hms, anything else) -->
|
||||
<!-- Time (HH:MM:SS) — native time picker with seconds.
|
||||
Mobile/tablet browsers surface the OS time wheel. -->
|
||||
<input t-if="isTimeHms(row)"
|
||||
type="time"
|
||||
step="1"
|
||||
class="o_fp_ri_input o_fp_ri_input_text"
|
||||
t-model="row.value_text"/>
|
||||
|
||||
<!-- Text fallback (text, signature, anything else) -->
|
||||
<input t-if="isText(row)"
|
||||
type="text"
|
||||
class="o_fp_ri_input o_fp_ri_input_text"
|
||||
|
||||
Reference in New Issue
Block a user