// ============================================================================ // 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];