Initial commit, 90% there
This commit is contained in:
@@ -0,0 +1,204 @@
|
||||
// ============================================================================
|
||||
// STARTUP RECOVERY Function - Session State Restoration (Issue 1)
|
||||
// Purpose: Restore session state from database on Node-RED startup/crash recovery
|
||||
// Trigger: Inject node on startup
|
||||
// Outputs: 2 (UI notification, database query)
|
||||
// ============================================================================
|
||||
|
||||
// This function should be called on Node-RED startup (via inject node with "inject once after X seconds")
|
||||
// It queries the session_state table and restores global variables
|
||||
|
||||
const mode = msg.mode || "check";
|
||||
|
||||
switch (mode) {
|
||||
// ========================================================================
|
||||
// STEP 1: Query database for session state
|
||||
// ========================================================================
|
||||
case "check": {
|
||||
msg._mode = "query-session-state";
|
||||
msg.topic = "SELECT * FROM session_state WHERE session_key = 'current_session';";
|
||||
|
||||
node.warn("[RECOVERY] Checking for existing session state...");
|
||||
|
||||
return [null, msg];
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// STEP 2: Process query results and prompt user
|
||||
// ========================================================================
|
||||
case "process-results": {
|
||||
const results = msg.payload;
|
||||
|
||||
if (!results || results.length === 0) {
|
||||
node.warn("[RECOVERY] No session state found in database");
|
||||
return [null, null];
|
||||
}
|
||||
|
||||
const session = results[0];
|
||||
|
||||
// Check if there's a valid session to restore
|
||||
if (!session.work_order_id || session.cycle_count === 0) {
|
||||
node.warn("[RECOVERY] Session state exists but is empty - nothing to restore");
|
||||
return [null, null];
|
||||
}
|
||||
|
||||
// Check if tracking was enabled
|
||||
if (!session.tracking_enabled) {
|
||||
node.warn("[RECOVERY] Previous session was not actively tracking - restoring state without prompt");
|
||||
|
||||
// Restore the state silently
|
||||
global.set("cycleCount", session.cycle_count || 0);
|
||||
global.set("productionStartTime", session.production_start_time);
|
||||
global.set("operatingTime", Number(session.operating_time) || 0);
|
||||
global.set("downtime", Number(session.downtime) || 0);
|
||||
global.set("lastUpdateTime", session.last_update_time || Date.now());
|
||||
global.set("trackingEnabled", false);
|
||||
global.set("scrapPromptIssuedFor", session.scrap_prompt_issued_for || null);
|
||||
global.set("currentSessionId", session.current_session_id || null);
|
||||
|
||||
return [null, null];
|
||||
}
|
||||
|
||||
// Session was actively tracking - prompt user
|
||||
node.warn(`[RECOVERY] Found active session - Work Order: ${session.work_order_id}, Cycles: ${session.cycle_count}`);
|
||||
|
||||
const promptMsg = {
|
||||
_mode: "recovery-prompt",
|
||||
recoveryPrompt: {
|
||||
workOrderId: session.work_order_id,
|
||||
cycleCount: session.cycle_count,
|
||||
operatingTime: Number(session.operating_time) || 0,
|
||||
downtime: Number(session.downtime) || 0,
|
||||
timestamp: session.last_update_time,
|
||||
sessionData: session
|
||||
}
|
||||
};
|
||||
|
||||
return [promptMsg, null];
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// STEP 3: User chose to restore session
|
||||
// ========================================================================
|
||||
case "restore": {
|
||||
const sessionData = msg.payload;
|
||||
|
||||
if (!sessionData) {
|
||||
node.error("[RECOVERY] No session data provided for restore");
|
||||
return [null, null];
|
||||
}
|
||||
|
||||
node.warn(`[RECOVERY] Restoring session - Work Order: ${sessionData.work_order_id}, Cycles: ${sessionData.cycle_count}`);
|
||||
|
||||
// Restore all global variables
|
||||
global.set("cycleCount", sessionData.cycle_count || 0);
|
||||
global.set("productionStartTime", sessionData.production_start_time);
|
||||
global.set("operatingTime", Number(sessionData.operating_time) || 0);
|
||||
global.set("downtime", Number(sessionData.downtime) || 0);
|
||||
global.set("lastUpdateTime", sessionData.last_update_time || Date.now());
|
||||
global.set("trackingEnabled", !!sessionData.tracking_enabled);
|
||||
global.set("scrapPromptIssuedFor", sessionData.scrap_prompt_issued_for || null);
|
||||
global.set("currentSessionId", sessionData.current_session_id || null);
|
||||
|
||||
// Also need to restore activeWorkOrder from work_orders table
|
||||
const queryMsg = {
|
||||
_mode: "query-work-order",
|
||||
topic: `SELECT * FROM work_orders WHERE work_order_id = '${sessionData.work_order_id}';`
|
||||
};
|
||||
|
||||
const notificationMsg = {
|
||||
_mode: "recovery-success",
|
||||
notification: {
|
||||
message: `Session restored: ${sessionData.work_order_id} - ${sessionData.cycle_count} cycles`,
|
||||
type: "success"
|
||||
}
|
||||
};
|
||||
|
||||
return [notificationMsg, queryMsg];
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// STEP 4: Set activeWorkOrder from query result
|
||||
// ========================================================================
|
||||
case "set-work-order": {
|
||||
const results = msg.payload;
|
||||
|
||||
if (!results || results.length === 0) {
|
||||
node.error("[RECOVERY] Work order not found in database");
|
||||
return [null, null];
|
||||
}
|
||||
|
||||
const workOrder = results[0];
|
||||
|
||||
// Reconstruct activeWorkOrder object
|
||||
const activeOrder = {
|
||||
id: workOrder.work_order_id,
|
||||
sku: workOrder.sku,
|
||||
target: workOrder.target,
|
||||
good: workOrder.good_parts,
|
||||
scrap: workOrder.scrap_count || 0,
|
||||
cycleTime: workOrder.cycle_time,
|
||||
theoreticalCycleTime: workOrder.cycle_time,
|
||||
progressPercent: workOrder.progress_percent,
|
||||
status: workOrder.status,
|
||||
lastUpdateIso: new Date().toISOString()
|
||||
};
|
||||
|
||||
global.set("activeWorkOrder", activeOrder);
|
||||
|
||||
node.warn(`[RECOVERY] Active work order restored: ${activeOrder.id}`);
|
||||
|
||||
return [null, null];
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// STEP 5: User chose to start fresh
|
||||
// ========================================================================
|
||||
case "start-fresh": {
|
||||
node.warn("[RECOVERY] User chose to start fresh - clearing session state");
|
||||
|
||||
// Clear all global variables
|
||||
global.set("cycleCount", 0);
|
||||
global.set("productionStartTime", null);
|
||||
global.set("operatingTime", 0);
|
||||
global.set("downtime", 0);
|
||||
global.set("lastUpdateTime", Date.now());
|
||||
global.set("trackingEnabled", false);
|
||||
global.set("activeWorkOrder", null);
|
||||
global.set("scrapPromptIssuedFor", null);
|
||||
global.set("currentSessionId", null);
|
||||
|
||||
// Clear session_state in database
|
||||
const clearMsg = {
|
||||
_mode: "clear-session-state",
|
||||
topic: `
|
||||
UPDATE session_state
|
||||
SET
|
||||
work_order_id = NULL,
|
||||
cycle_count = 0,
|
||||
production_start_time = NULL,
|
||||
operating_time = 0,
|
||||
downtime = 0,
|
||||
last_update_time = NULL,
|
||||
tracking_enabled = 0,
|
||||
machine_state = 0,
|
||||
scrap_prompt_issued_for = NULL,
|
||||
current_session_id = NULL
|
||||
WHERE session_key = 'current_session';
|
||||
`
|
||||
};
|
||||
|
||||
const notificationMsg = {
|
||||
_mode: "recovery-cleared",
|
||||
notification: {
|
||||
message: "Started with fresh session",
|
||||
type: "info"
|
||||
}
|
||||
};
|
||||
|
||||
return [notificationMsg, clearMsg];
|
||||
}
|
||||
}
|
||||
|
||||
// Default
|
||||
return [null, null];
|
||||
Reference in New Issue
Block a user