feat(fusion_plating_shopfloor): GateViz shared OWL service
Plan task P1.4. "Can't start yet — Waiting on Step N: X" block reused across JobWorkspace step rows and Manager Plant Board cards. Icon set maps to blocker_kind (predecessor/contract_review/parts_not_received/ racking_required/manager_input). Optional Jump button propagates to parent via onJump callback. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -68,6 +68,9 @@ Copyright (c) 2026 Nexa Systems Inc. All rights reserved.
|
||||
'fusion_plating_shopfloor/static/src/scss/components/_workflow_chip.scss',
|
||||
'fusion_plating_shopfloor/static/src/xml/components/workflow_chip.xml',
|
||||
'fusion_plating_shopfloor/static/src/js/components/workflow_chip.js',
|
||||
'fusion_plating_shopfloor/static/src/scss/components/_gate_viz.scss',
|
||||
'fusion_plating_shopfloor/static/src/xml/components/gate_viz.xml',
|
||||
'fusion_plating_shopfloor/static/src/js/components/gate_viz.js',
|
||||
'fusion_plating_shopfloor/static/src/scss/qr_scanner.scss',
|
||||
'fusion_plating_shopfloor/static/src/scss/fusion_plating_shopfloor.scss',
|
||||
'fusion_plating_shopfloor/static/src/scss/plant_overview.scss',
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
/** @odoo-module **/
|
||||
// =============================================================================
|
||||
// Fusion Plating — GateViz (shared OWL service)
|
||||
//
|
||||
// "Can't start because…" explainer for fp.job.step blockers. Drives off
|
||||
// step.blocker_kind/reason from the backend compute. Used in:
|
||||
// • JobWorkspace step rows (inline)
|
||||
// • Manager Plant Board "Needs Worker" cards (badge form)
|
||||
//
|
||||
// Props:
|
||||
// canStart : Boolean — when true, renders nothing
|
||||
// blockerKind : String — predecessor/contract_review/
|
||||
// parts_not_received/racking_required/
|
||||
// manager_input/other
|
||||
// blockerReason : String — human-readable explanation
|
||||
// jumpTargetModel : String — optional model name for tap-to-jump
|
||||
// jumpTargetId : Number — optional record id for tap-to-jump
|
||||
// onJump : Function — called with {model, id} on Jump click
|
||||
// =============================================================================
|
||||
|
||||
import { Component } from "@odoo/owl";
|
||||
|
||||
export class GateViz extends Component {
|
||||
static template = "fusion_plating_shopfloor.GateViz";
|
||||
static props = {
|
||||
canStart: { type: Boolean, optional: false },
|
||||
blockerKind: { type: String, optional: true },
|
||||
blockerReason: { type: String, optional: true },
|
||||
jumpTargetModel: { type: String, optional: true },
|
||||
jumpTargetId: { type: Number, optional: true },
|
||||
onJump: { type: Function, optional: true },
|
||||
};
|
||||
|
||||
get iconClass() {
|
||||
const map = {
|
||||
predecessor: "fa-lock",
|
||||
contract_review: "fa-file-text-o",
|
||||
parts_not_received: "fa-truck",
|
||||
racking_required: "fa-th-large",
|
||||
manager_input: "fa-user-md",
|
||||
};
|
||||
return map[this.props.blockerKind] || "fa-pause-circle";
|
||||
}
|
||||
|
||||
onJumpClick() {
|
||||
if (this.props.onJump && this.props.jumpTargetModel && this.props.jumpTargetId) {
|
||||
this.props.onJump({
|
||||
model: this.props.jumpTargetModel,
|
||||
id: this.props.jumpTargetId,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// =============================================================================
|
||||
// GateViz — "this step can't start because..." explainer
|
||||
// Dark-mode aware via $o-webclient-color-scheme branch.
|
||||
// =============================================================================
|
||||
|
||||
$o-webclient-color-scheme: bright !default;
|
||||
|
||||
$_gate-bg-hex: rgba(255, 159, 10, 0.10);
|
||||
$_gate-border-hex: #ff9f0a;
|
||||
$_gate-text-hex: #b06600;
|
||||
|
||||
@if $o-webclient-color-scheme == dark {
|
||||
$_gate-text-hex: #ffb84d !global;
|
||||
}
|
||||
|
||||
.o_fp_gate {
|
||||
background: $_gate-bg-hex;
|
||||
border-left: 3px solid $_gate-border-hex;
|
||||
padding: 0.5rem 0.75rem;
|
||||
border-radius: 0 6px 6px 0;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.o_fp_gate_icon { color: $_gate-border-hex; margin-top: 0.15rem; }
|
||||
.o_fp_gate_body { flex: 1; }
|
||||
.o_fp_gate_title { font-weight: 600; color: $_gate-text-hex; font-size: 0.85rem; }
|
||||
.o_fp_gate_reason { color: var(--text-secondary, #666); font-size: 0.78rem; margin-top: 0.1rem; }
|
||||
.o_fp_gate_jump { flex-shrink: 0; }
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="fusion_plating_shopfloor.GateViz">
|
||||
<div class="o_fp_gate" t-if="!props.canStart">
|
||||
<i t-att-class="'fa o_fp_gate_icon ' + iconClass"/>
|
||||
<div class="o_fp_gate_body">
|
||||
<div class="o_fp_gate_title">Can't start yet</div>
|
||||
<div class="o_fp_gate_reason">
|
||||
<t t-esc="props.blockerReason or 'Reason unknown — open the step in the back-office.'"/>
|
||||
</div>
|
||||
</div>
|
||||
<button t-if="props.jumpTargetModel and props.jumpTargetId and props.onJump"
|
||||
class="btn btn-sm btn-outline-warning o_fp_gate_jump"
|
||||
t-on-click="onJumpClick">
|
||||
Jump <i class="fa fa-arrow-right"/>
|
||||
</button>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
Reference in New Issue
Block a user