feat(fusion_plating_shopfloor): wire FpTabletLock + Hand-Off into Landing/Workspace/Manager (P6.2.5)

Three OWL client actions all wrap their root in <FpTabletLock>:

  ShopfloorLanding   wraps o_fp_landing
  JobWorkspace       wraps o_fp_ws
  ManagerDashboard   wraps o_fp_manager

Each adds FpTabletLock to static components, imports tech_store, and
gains a handOff() method that calls techStore.lock(). The Hand-Off
button (yellow, lock icon) lands next to the scan/QR controls in each
header — pressing it instantly returns the tablet to the tile grid
without waiting for the idle timer.

Component composition (per spec §6.5):
  FpTabletLock
    if isLocked → tile grid + FpPinPad
    else → existing client action (via <t t-slot="default"/>) + FpIdleWarning

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-05-23 00:32:52 -04:00
parent 29821bd541
commit 85609f99cd
6 changed files with 56 additions and 3 deletions

View File

@@ -25,16 +25,18 @@ import { WorkflowChip } from "./components/workflow_chip";
import { GateViz } from "./components/gate_viz";
import { FpSignaturePad } from "./components/signature_pad";
import { FpHoldComposer } from "./components/hold_composer";
import { FpTabletLock } from "./tablet_lock";
export class FpJobWorkspace extends Component {
static template = "fusion_plating_shopfloor.JobWorkspace";
static props = ["*"];
static components = { WorkflowChip, GateViz, FpSignaturePad, FpHoldComposer };
static components = { WorkflowChip, GateViz, FpSignaturePad, FpHoldComposer, FpTabletLock };
setup() {
this.notification = useService("notification");
this.action = useService("action");
this.dialog = useService("dialog");
this.techStore = useService("fp_shopfloor_tech_store");
this.state = useState({
data: null,
@@ -76,6 +78,11 @@ export class FpJobWorkspace extends Component {
this.action.doAction({ type: "ir.actions.act_window_close" });
}
// ---- Hand-Off (Phase 6.2) ---------------------------------------------
handOff() {
this.techStore.lock();
}
onJumpToBlocker({ model, id }) {
// If the predecessor is in this same workspace, just scroll to it
const inThisJob = (this.state.data.steps || []).find((s) => s.id === id);

View File

@@ -17,15 +17,17 @@ import { registry } from "@web/core/registry";
import { rpc } from "@web/core/network/rpc";
import { useService } from "@web/core/utils/hooks";
import { QrScanner } from "./qr_scanner";
import { FpTabletLock } from "./tablet_lock";
export class ManagerDashboard extends Component {
static template = "fusion_plating_shopfloor.ManagerDashboard";
static props = ["*"];
static components = { QrScanner };
static components = { QrScanner, FpTabletLock };
setup() {
this.notification = useService("notification");
this.action = useService("action");
this.techStore = useService("fp_shopfloor_tech_store");
this.state = useState({
overview: null,
@@ -148,6 +150,11 @@ export class ManagerDashboard extends Component {
this.state.mode = this.state.mode === "quick" ? "detailed" : "quick";
}
// ---- Hand-Off (Phase 6.2) ---------------------------------------------
handOff() {
this.techStore.lock();
}
toggleCard(jobId) {
this.state.expandedJobId = this.state.expandedJobId === jobId ? null : jobId;
}

View File

@@ -23,6 +23,7 @@ import { rpc } from "@web/core/network/rpc";
import { useService } from "@web/core/utils/hooks";
import { QrScanner } from "./qr_scanner";
import { FpKanbanCard } from "./components/kanban_card";
import { FpTabletLock } from "./tablet_lock";
const LS_STATION_ID = "fp_landing_station_id";
const LS_MODE = "fp_landing_mode";
@@ -31,11 +32,12 @@ const REFRESH_MS = 15000;
export class FpShopfloorLanding extends Component {
static template = "fusion_plating_shopfloor.ShopfloorLanding";
static props = ["*"];
static components = { QrScanner, FpKanbanCard };
static components = { QrScanner, FpKanbanCard, FpTabletLock };
setup() {
this.notification = useService("notification");
this.action = useService("action");
this.techStore = useService("fp_shopfloor_tech_store");
this.state = useState({
mode: localStorage.getItem(LS_MODE) || "all_plant",
@@ -120,6 +122,12 @@ export class FpShopfloorLanding extends Component {
this.refresh();
}
// ---- Hand-Off (Phase 6.2) ---------------------------------------------
handOff() {
// Tech walking away: lock the tablet so the next operator must PIN in
this.techStore.lock();
}
// ---- Search ------------------------------------------------------------
onSearchInput(ev) {
this.state.search = ev.target.value;

View File

@@ -2,6 +2,8 @@
<templates xml:space="preserve">
<t t-name="fusion_plating_shopfloor.JobWorkspace">
<FpTabletLock>
<t t-set-slot="default">
<div class="o_fp_ws">
<!-- Loading state -->
@@ -20,6 +22,12 @@
<button class="btn btn-link o_fp_ws_back" t-on-click="onBack">
<i class="fa fa-arrow-left"/> Back
</button>
<!-- Phase 6.2 — Hand-Off: lock the tablet -->
<button class="btn btn-sm btn-warning ms-2"
t-on-click="handOff"
title="Lock the tablet for the next operator">
<i class="fa fa-lock"/> Hand Off
</button>
<span class="o_fp_ws_wo"><t t-esc="state.data.job.display_wo_name"/></span>
<span class="o_fp_ws_dot"> · </span>
<span class="o_fp_ws_cust"><t t-esc="state.data.job.partner_name"/></span>
@@ -225,6 +233,8 @@
</t>
</div>
</t>
</FpTabletLock>
</t>
</templates>

View File

@@ -7,6 +7,8 @@
<templates xml:space="preserve">
<t t-name="fusion_plating_shopfloor.ManagerDashboard">
<FpTabletLock>
<t t-set-slot="default">
<div class="o_fp_manager">
<!-- ============ Hero ============ -->
@@ -45,6 +47,12 @@
<i t-att-class="'fa fa-refresh' + (state.isFetching ? ' fa-spin' : '')"/>
</button>
<QrScanner cssClass="'btn'"/>
<!-- Phase 6.2 — Hand-Off: lock the tablet -->
<button class="btn btn-warning"
t-on-click="handOff"
title="Lock the tablet for the next operator">
<i class="fa fa-lock"/> Hand Off
</button>
<button t-att-class="'btn ' + (state.mode === 'quick' ? 'btn-primary' : '')"
t-on-click="toggleMode">
<t t-if="state.mode === 'quick'">Quick View</t>
@@ -583,6 +591,8 @@
<div>Loading manager data…</div>
</div>
</div>
</t>
</FpTabletLock>
</t>
</templates>

View File

@@ -2,6 +2,8 @@
<templates xml:space="preserve">
<t t-name="fusion_plating_shopfloor.ShopfloorLanding">
<FpTabletLock>
<t t-set-slot="default">
<div class="o_fp_landing">
<!-- Loading state -->
@@ -62,6 +64,13 @@
</button>
<QrScanner cssClass="'btn btn-sm btn-outline-secondary'" label="'Camera'"/>
<!-- Phase 6.2 — Hand-Off: lock the tablet for the next operator -->
<button class="btn btn-sm btn-warning"
t-on-click="handOff"
title="Lock the tablet for the next operator">
<i class="fa fa-lock"/> Hand Off
</button>
<!-- Refresh indicator -->
<span class="o_fp_landing_refresh text-muted">
<i class="fa fa-clock-o"/> <t t-esc="state.lastRefresh"/>
@@ -158,6 +167,8 @@
</t>
</div>
</t>
</FpTabletLock>
</t>
</templates>