fusion_plating: add opt_in_out field + time tracking display (v19.0.2.0.4)

New opt_in_out selection field (disabled/opt-in/opt-out) matching
Steelhead's Configure OPT IN/OUT feature. Shown in both the form
view and the tree editor side panel.

Time tracking: form view now shows Created, Created By, Last Updated,
Updated By fields. Tree editor side panel shows relative timestamps
down to the second (e.g. "46w 3d 4h 17m 21s ago by Brett Kinzett").

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-04-12 15:05:59 -04:00
parent 3316b5d519
commit e4b41828a3
6 changed files with 87 additions and 4 deletions

View File

@@ -453,6 +453,29 @@ export class RecipeTreeEditor extends Component {
return ICON_OPTIONS;
}
formatTimeAgo(isoStr) {
if (!isoStr) return "";
const date = new Date(isoStr);
const now = new Date();
let diff = Math.floor((now - date) / 1000); // seconds
if (diff < 0) diff = 0;
const parts = [];
const weeks = Math.floor(diff / 604800);
diff %= 604800;
const days = Math.floor(diff / 86400);
diff %= 86400;
const hours = Math.floor(diff / 3600);
diff %= 3600;
const minutes = Math.floor(diff / 60);
const seconds = diff % 60;
if (weeks) parts.push(`${weeks}w`);
if (days) parts.push(`${days}d`);
if (hours) parts.push(`${hours}h`);
if (minutes) parts.push(`${minutes}m`);
parts.push(`${seconds}s`);
return parts.join(" ") + " ago";
}
formatDuration(minutes) {
if (!minutes) return "";
if (minutes < 60) return `${Math.round(minutes)}m`;

View File

@@ -372,6 +372,12 @@
}
}
// ---- Tracking section -------------------------------------------------------
.o_fp_recipe_tracking {
border-top: 1px solid $border-color;
}
// ---- Icon picker ------------------------------------------------------------
.o_fp_recipe_icon_picker {

View File

@@ -144,20 +144,49 @@
<label class="form-check-label" for="fp_chk_visible">Customer visible</label>
</div>
</div>
<div class="mb-3">
<label class="form-label fw-bold">Opt In/Out</label>
<select class="form-select"
t-on-change="(ev) => { state.selectedNode.opt_in_out = ev.target.value; }">
<option value="disabled"
t-att-selected="state.selectedNode.opt_in_out === 'disabled'">Disabled</option>
<option value="opt_in"
t-att-selected="state.selectedNode.opt_in_out === 'opt_in'">Opt-In</option>
<option value="opt_out"
t-att-selected="state.selectedNode.opt_in_out === 'opt_out'">Opt-Out</option>
</select>
</div>
<!-- Info -->
<div class="text-muted small mb-3" t-if="state.selectedNode.work_center">
<div class="text-muted small mb-2" t-if="state.selectedNode.work_center">
<i class="fa fa-building me-1"/>
<t t-esc="state.selectedNode.work_center"/>
</div>
<div class="text-muted small mb-3" t-if="state.selectedNode.process_type">
<div class="text-muted small mb-2" t-if="state.selectedNode.process_type">
<i class="fa fa-tag me-1"/>
<t t-esc="state.selectedNode.process_type"/>
</div>
<div class="text-muted small mb-3"
<div class="text-muted small mb-2"
t-if="state.selectedNode.input_count">
<i class="fa fa-keyboard-o me-1"/>
<t t-esc="state.selectedNode.input_count"/> operator input(s)
</div>
<!-- Tracking -->
<div class="o_fp_recipe_tracking mt-3 pt-3" t-if="state.selectedNode.create_date">
<div class="text-muted small mb-1">
<i class="fa fa-calendar-plus-o me-1"/>
Created <t t-esc="formatTimeAgo(state.selectedNode.create_date)"/>
<t t-if="state.selectedNode.create_uid_name">
by <strong t-esc="state.selectedNode.create_uid_name"/>
</t>
</div>
<div class="text-muted small" t-if="state.selectedNode.write_date">
<i class="fa fa-pencil me-1"/>
Updated <t t-esc="formatTimeAgo(state.selectedNode.write_date)"/>
<t t-if="state.selectedNode.write_uid_name">
by <strong t-esc="state.selectedNode.write_uid_name"/>
</t>
</div>
</div>
<!-- Actions -->
<div class="d-flex gap-2 mt-4">
<button class="btn btn-primary flex-fill"