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:
gsinghpal
2026-05-23 00:27:13 -04:00
parent 1d6184dd2f
commit 52097ca59b
2 changed files with 115 additions and 0 deletions

View File

@@ -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);

View File

@@ -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);