feat(fusion_plating_shopfloor): tech_store + activity_tracker OWL services (P6.2.1)
Two registry-level services:
tech_store Shared reactive state holding currentTechId after a
successful PIN unlock. Other components subscribe via
useService("fp_shopfloor_tech_store") and read
currentTechId to inject into action RPCs. setTech(id, name)
on unlock; lock() on auto-lock / Hand-Off.
activity_tracker Document-level event tracker for pointerdown / touchstart
/ keydown / visibilitychange. Mouse-move alone deliberately
EXCLUDED — a tool resting on a tablet would otherwise keep
the session alive indefinitely. Public API:
bump(), getSecondsUntilLock(), getWarnThresholdSec()
Reads thresholds from ir.config_parameter at start +
every 5 min (so manager edits propagate within a shift).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
/** @odoo-module **/
|
||||
// =============================================================================
|
||||
// Fusion Plating — Activity Tracker (shared OWL service)
|
||||
//
|
||||
// Watches the document for pointer/touch/keydown/visibility events and
|
||||
// tracks lastActiveAt. FpTabletLock reads getSecondsUntilLock() once per
|
||||
// second to drive the idle warning + auto-lock transitions.
|
||||
//
|
||||
// Threshold reads from ir.config_parameter at service start; refreshes
|
||||
// every 5 min in case the manager changed it.
|
||||
// =============================================================================
|
||||
|
||||
import { rpc } from "@web/core/network/rpc";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
const DEFAULT_IDLE_MIN = 5;
|
||||
const DEFAULT_WARN_SEC = 30;
|
||||
|
||||
export const fpShopfloorActivityTracker = {
|
||||
async start() {
|
||||
let lastActiveAt = Date.now();
|
||||
let idleThresholdMs = DEFAULT_IDLE_MIN * 60 * 1000;
|
||||
let warnThresholdSec = DEFAULT_WARN_SEC;
|
||||
|
||||
async function refreshThreshold() {
|
||||
try {
|
||||
const minutes = await rpc("/web/dataset/call_kw", {
|
||||
model: "ir.config_parameter",
|
||||
method: "get_param",
|
||||
args: ["fp.shopfloor.tablet_idle_lock_minutes", String(DEFAULT_IDLE_MIN)],
|
||||
kwargs: {},
|
||||
});
|
||||
idleThresholdMs = (parseInt(minutes, 10) || DEFAULT_IDLE_MIN) * 60 * 1000;
|
||||
const warn = await rpc("/web/dataset/call_kw", {
|
||||
model: "ir.config_parameter",
|
||||
method: "get_param",
|
||||
args: ["fp.shopfloor.tablet_warn_seconds_before_lock", String(DEFAULT_WARN_SEC)],
|
||||
kwargs: {},
|
||||
});
|
||||
warnThresholdSec = parseInt(warn, 10) || DEFAULT_WARN_SEC;
|
||||
} catch (e) {
|
||||
// keep defaults if RPC fails (e.g. no session yet)
|
||||
}
|
||||
}
|
||||
await refreshThreshold();
|
||||
setInterval(refreshThreshold, 5 * 60 * 1000);
|
||||
|
||||
// Activity = explicit user input. Mouse-move alone DOES NOT count
|
||||
// because something brushing the screen (a stray glove, a tool
|
||||
// resting on the tablet) could otherwise keep the session alive.
|
||||
const bump = () => { lastActiveAt = Date.now(); };
|
||||
document.addEventListener("pointerdown", bump, { capture: true });
|
||||
document.addEventListener("touchstart", bump, { capture: true, passive: true });
|
||||
document.addEventListener("keydown", bump, { capture: true });
|
||||
document.addEventListener("visibilitychange", () => {
|
||||
if (document.visibilityState === "visible") bump();
|
||||
});
|
||||
|
||||
return {
|
||||
bump,
|
||||
getSecondsUntilLock() {
|
||||
return Math.max(0, Math.floor((lastActiveAt + idleThresholdMs - Date.now()) / 1000));
|
||||
},
|
||||
getWarnThresholdSec() { return warnThresholdSec; },
|
||||
getIdleThresholdMs() { return idleThresholdMs; },
|
||||
getLastActiveAt() { return lastActiveAt; },
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
registry
|
||||
.category("services")
|
||||
.add("fp_shopfloor_activity", fpShopfloorActivityTracker);
|
||||
@@ -0,0 +1,42 @@
|
||||
/** @odoo-module **/
|
||||
// =============================================================================
|
||||
// Fusion Plating — Tech Store (shared OWL service)
|
||||
//
|
||||
// Holds the "current tech of record" for the locked tablet. Set by
|
||||
// FpTabletLock on successful PIN unlock; cleared on auto-lock / Hand-Off.
|
||||
// Other components read currentTechId via useService("fp_shopfloor_tech_store")
|
||||
// and pass it through fpRpc() so server actions credit the right user.
|
||||
// =============================================================================
|
||||
|
||||
import { reactive } from "@odoo/owl";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
export const fpShopfloorTechStore = {
|
||||
start() {
|
||||
const state = reactive({
|
||||
currentTechId: null,
|
||||
currentTechName: "",
|
||||
lockedAt: null,
|
||||
});
|
||||
return {
|
||||
get currentTechId() { return state.currentTechId; },
|
||||
get currentTechName() { return state.currentTechName; },
|
||||
get isLocked() { return !state.currentTechId; },
|
||||
setTech(id, name) {
|
||||
state.currentTechId = id;
|
||||
state.currentTechName = name;
|
||||
state.lockedAt = null;
|
||||
},
|
||||
lock() {
|
||||
state.currentTechId = null;
|
||||
state.currentTechName = "";
|
||||
state.lockedAt = Date.now();
|
||||
},
|
||||
state, // exposed for OWL reactive subscriptions
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
registry
|
||||
.category("services")
|
||||
.add("fp_shopfloor_tech_store", fpShopfloorTechStore);
|
||||
Reference in New Issue
Block a user