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