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).
|
||||
|
||||
Reference in New Issue
Block a user