From e4b41828a34f84f40f1772f0322343f7e26412f5 Mon Sep 17 00:00:00 2001 From: gsinghpal Date: Sun, 12 Apr 2026 15:05:59 -0400 Subject: [PATCH] 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) --- .../controllers/recipe_controller.py | 2 +- .../fusion_plating/models/fp_process_node.py | 16 +++++++++ .../static/src/js/recipe_tree_editor.js | 23 ++++++++++++ .../static/src/scss/recipe_tree_editor.scss | 6 ++++ .../static/src/xml/recipe_tree_editor.xml | 35 +++++++++++++++++-- .../views/fp_process_node_views.xml | 9 +++++ 6 files changed, 87 insertions(+), 4 deletions(-) diff --git a/fusion-plating/fusion_plating/controllers/recipe_controller.py b/fusion-plating/fusion_plating/controllers/recipe_controller.py index 8c762732..131ed2a8 100644 --- a/fusion-plating/fusion_plating/controllers/recipe_controller.py +++ b/fusion-plating/fusion_plating/controllers/recipe_controller.py @@ -85,7 +85,7 @@ class FpRecipeController(http.Controller): 'description', 'notes', 'estimated_duration', 'auto_complete', 'customer_visible', 'is_manual', - 'requires_signoff', 'sequence', 'version', + 'requires_signoff', 'opt_in_out', 'sequence', 'version', } safe_vals = {k: v for k, v in vals.items() if k in allowed} if not safe_vals: diff --git a/fusion-plating/fusion_plating/models/fp_process_node.py b/fusion-plating/fusion_plating/models/fp_process_node.py index 6edf91d2..78f2e5bb 100644 --- a/fusion-plating/fusion_plating/models/fp_process_node.py +++ b/fusion-plating/fusion_plating/models/fp_process_node.py @@ -143,6 +143,17 @@ class FpProcessNode(models.Model): default=False, help='Quality hold point — requires operator sign-off.', ) + opt_in_out = fields.Selection( + [ + ('disabled', 'Disabled'), + ('opt_in', 'Opt-In'), + ('opt_out', 'Opt-Out'), + ], + string='Opt In/Out', + default='disabled', + help='Controls whether this step is optional for a given job.', + tracking=True, + ) # ---- Lifecycle ----------------------------------------------------------- @@ -274,7 +285,12 @@ class FpProcessNode(models.Model): 'requires_signoff': self.requires_signoff, 'version': self.version, 'child_count': len(children), + 'opt_in_out': self.opt_in_out or 'disabled', 'input_count': len(self.input_ids), + 'create_date': self.create_date.isoformat() if self.create_date else '', + 'create_uid_name': self.create_uid.name if self.create_uid else '', + 'write_date': self.write_date.isoformat() if self.write_date else '', + 'write_uid_name': self.write_uid.name if self.write_uid else '', 'children': children, } diff --git a/fusion-plating/fusion_plating/static/src/js/recipe_tree_editor.js b/fusion-plating/fusion_plating/static/src/js/recipe_tree_editor.js index d976e7b8..a1ff0024 100644 --- a/fusion-plating/fusion_plating/static/src/js/recipe_tree_editor.js +++ b/fusion-plating/fusion_plating/static/src/js/recipe_tree_editor.js @@ -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`; diff --git a/fusion-plating/fusion_plating/static/src/scss/recipe_tree_editor.scss b/fusion-plating/fusion_plating/static/src/scss/recipe_tree_editor.scss index cec9e216..1c386735 100644 --- a/fusion-plating/fusion_plating/static/src/scss/recipe_tree_editor.scss +++ b/fusion-plating/fusion_plating/static/src/scss/recipe_tree_editor.scss @@ -372,6 +372,12 @@ } } +// ---- Tracking section ------------------------------------------------------- + +.o_fp_recipe_tracking { + border-top: 1px solid $border-color; +} + // ---- Icon picker ------------------------------------------------------------ .o_fp_recipe_icon_picker { diff --git a/fusion-plating/fusion_plating/static/src/xml/recipe_tree_editor.xml b/fusion-plating/fusion_plating/static/src/xml/recipe_tree_editor.xml index effd3b78..065927ed 100644 --- a/fusion-plating/fusion_plating/static/src/xml/recipe_tree_editor.xml +++ b/fusion-plating/fusion_plating/static/src/xml/recipe_tree_editor.xml @@ -144,20 +144,49 @@ +
+ + +
-
+
-
+
-
operator input(s)
+ +
+
+ + Created + + by + +
+
+ + Updated + + by + +
+