const mode = msg._mode || ''; const started = msg.startOrder || null; const completed = msg.completeOrder || null; delete msg._mode; delete msg.startOrder; delete msg.completeOrder; delete msg.action; delete msg.filename; // ======================================================== // MODE: CHECK PROGRESS (PHASE 3) // ======================================================== if (mode === "check-progress") { const rows = Array.isArray(msg.payload) ? msg.payload : []; if (rows.length > 0) { const row = rows[0]; const cycleCount = Number(row.cycle_count) || 0; const goodParts = Number(row.good_parts) || 0; // Retrieve pending work order from flow context const pendingOrder = flow.get("pendingWorkOrder"); if (!pendingOrder) { node.error("No pending work order found in check-progress"); return [null, null, null, null]; } // If there's existing progress, send resume prompt to UI if (cycleCount > 0 || goodParts > 0) { msg.topic = "resumePrompt"; msg.payload = { id: pendingOrder.id, sku: pendingOrder.sku || "", target: Number(pendingOrder.target) || 0, cycleCount: cycleCount, goodParts: goodParts, progressPercent: Number(row.progress_percent) || 0 }; node.warn(`[RESUME PROMPT] WO ${pendingOrder.id} has ${goodParts}/${pendingOrder.target} parts (${cycleCount} cycles)`); return [null, msg, null, null]; } } // No progress found - proceed with normal start (automatically call resume with 0 values) const pendingOrder = flow.get("pendingWorkOrder"); if (pendingOrder) { // Auto-start with zero values msg.topic = "UPDATE work_orders SET status = CASE WHEN work_order_id = ? THEN 'RUNNING' ELSE 'PENDING' END, updated_at = CASE WHEN work_order_id = ? THEN NOW() ELSE updated_at END WHERE status <> 'DONE'"; msg.payload = [pendingOrder.id, pendingOrder.id]; global.set("activeWorkOrder", pendingOrder); global.set("cycleCount", 0); flow.set("lastMachineState", 0); global.set("scrapPromptIssuedFor", null); node.warn(`[AUTO-START] WO ${pendingOrder.id} has no existing progress, starting fresh`); // Clear pending order flow.set("pendingWorkOrder", null); } return [null, null, null, null]; } // ======================================================== // MODE: RESUME WORK ORDER (PHASE 3) // ======================================================== if (mode === "resume") { const order = global.get("activeWorkOrder") || {}; const cycleCount = Number(global.get("cycleCount")) || 0; const kpis = global.get("currentKPIs") || { oee: 0, availability: 0, performance: 0, quality: 0 }; const homeMsg = { topic: "activeWorkOrder", payload: { id: order.id || "", sku: order.sku || "", target: Number(order.target) || 0, good: Number(order.good) || 0, scrap: Number(order.scrap) || 0, cycleTime: Number(order.cycleTime || order.theoreticalCycleTime || 0), progressPercent: Number(order.progressPercent) || 0, lastUpdateIso: order.lastUpdateIso || null, kpis: kpis } }; // Clear pending order flow.set("pendingWorkOrder", null); node.warn(`[RESUME] Loaded WO ${order.id} with ${cycleCount} cycles in UI`); return [null, homeMsg, null, null]; } // ======================================================== // MODE: RESTART WORK ORDER (PHASE 3) // ======================================================== if (mode === "restart") { const order = global.get("activeWorkOrder") || {}; const kpis = global.get("currentKPIs") || { oee: 0, availability: 0, performance: 0, quality: 0 }; const homeMsg = { topic: "activeWorkOrder", payload: { id: order.id || "", sku: order.sku || "", target: Number(order.target) || 0, good: 0, scrap: 0, cycleTime: Number(order.cycleTime || order.theoreticalCycleTime || 0), progressPercent: 0, lastUpdateIso: null, kpis: kpis } }; // Clear pending order flow.set("pendingWorkOrder", null); node.warn(`[RESTART] Reset WO ${order.id} to zero in UI`); return [null, homeMsg, null, null]; } // ======================================================== // MODE: UPLOAD // ======================================================== if (mode === "upload") { msg.topic = "uploadStatus"; msg.payload = { message: "✅ Work orders uploaded successfully." }; return [msg, null, null, null]; } // ======================================================== // MODE: SELECT (Load Work Orders) // ======================================================== if (mode === "select") { const rawRows = Array.isArray(msg.payload) ? msg.payload : []; msg.topic = "workOrdersList"; msg.payload = rawRows.map(row => ({ id: row.work_order_id ?? row.id ?? "", sku: row.sku ?? "", target: Number(row.target_qty ?? row.target ?? 0), good: Number(row.good_parts ?? row.good ?? 0), scrap: Number(row.scrap_count ?? row.scrap ?? 0), progressPercent: Number(row.progress_percent ?? row.progress ?? 0), status: (row.status ?? "PENDING").toUpperCase(), lastUpdateIso: row.updated_at ?? row.last_update ?? null, cycleTime: Number(row.cycle_time ?? row.theoretical_cycle_time ?? 0) })); return [msg, null, null, null]; } // ======================================================== // MODE: START WORK ORDER // ======================================================== if (mode === "start") { const order = started || {}; const kpis = msg.kpis || global.get("currentKPIs") || { oee: 0, availability: 0, performance: 0, quality: 0 }; const homeMsg = { topic: "activeWorkOrder", payload: { id: order.id || "", sku: order.sku || "", target: Number(order.target) || 0, good: Number(order.good) || 0, scrap: Number(order.scrap) || 0, cycleTime: Number(order.cycleTime || order.theoreticalCycleTime || 0), progressPercent: Number(order.progressPercent) || 0, lastUpdateIso: order.lastUpdateIso || null, kpis: kpis } }; return [null, homeMsg, null, null]; } // ======================================================== // MODE: COMPLETE WORK ORDER // ======================================================== if (mode === "complete") { const homeMsg = { topic: "activeWorkOrder", payload: null }; return [null, homeMsg, null, null]; } // ======================================================== // MODE: CYCLE UPDATE DURING PRODUCTION // ======================================================== if (mode === "cycle") { const cycle = msg.cycle || {}; const workOrderMsg = { topic: "workOrderCycle", payload: { id: cycle.id || "", sku: cycle.sku || "", target: Number(cycle.target) || 0, good: Number(cycle.good) || 0, scrap: Number(cycle.scrap) || 0, progressPercent: Number(cycle.progressPercent) || 0, lastUpdateIso: cycle.lastUpdateIso || new Date().toISOString(), status: cycle.progressPercent >= 100 ? "DONE" : "RUNNING" } }; const kpis = msg.kpis || global.get("currentKPIs") || { oee: 0, availability: 0, performance: 0, quality: 0 }; const homeMsg = { topic: "activeWorkOrder", payload: { id: cycle.id || "", sku: cycle.sku || "", target: Number(cycle.target) || 0, good: Number(cycle.good) || 0, scrap: Number(cycle.scrap) || 0, cycleTime: Number(cycle.cycleTime) || 0, progressPercent: Number(cycle.progressPercent) || 0, lastUpdateIso: cycle.lastUpdateIso || new Date().toISOString(), kpis: kpis } }; return [workOrderMsg, homeMsg, null, null]; } // ======================================================== // MODE: MACHINE PRODUCTION STATE // ======================================================== if (mode === "production-state") { const homeMsg = { topic: "machineStatus", payload: { machineOnline: msg.machineOnline ?? true, productionStarted: !!msg.productionStarted, trackingEnabled: msg.payload?.trackingEnabled ?? msg.trackingEnabled ?? false } }; return [null, homeMsg, null, null]; } // ======================================================== // MODE: CURRENT STATE (for tab switch sync) // ======================================================== if (mode === "current-state") { const state = msg.payload || {}; const homeMsg = { topic: "currentState", payload: { activeWorkOrder: state.activeWorkOrder, trackingEnabled: state.trackingEnabled, productionStarted: state.productionStarted, kpis: state.kpis } }; return [null, homeMsg, null, null]; } // ======================================================== // MODE: RESTORE QUERY (startup state recovery) // ======================================================== if (mode === "restore-query") { const rows = Array.isArray(msg.payload) ? msg.payload : []; if (rows.length > 0) { const row = rows[0]; const restoredOrder = { id: row.work_order_id || row.id || "", sku: row.sku || "", target: Number(row.target_qty || row.target || 0), good: Number(row.good_parts || row.good || 0), scrap: Number(row.scrap_parts || row.scrap || 0), progressPercent: Number(row.progress_percent || 0), cycleTime: Number(row.cycle_time || 0), lastUpdateIso: row.updated_at || null }; // Restore global state global.set("activeWorkOrder", restoredOrder); global.set("cycleCount", Number(row.cycle_count) || 0); // PHASE 5: Don't auto-start tracking - user must click START // But work order stays RUNNING so user doesn't have to Load again global.set("trackingEnabled", false); global.set("productionStarted", false); node.warn('[RESTORE] Restored work order: ' + restoredOrder.id + ' with ' + global.get("cycleCount") + ' cycles. Status remains RUNNING, awaiting START click.'); const homeMsg = { topic: "activeWorkOrder", payload: restoredOrder }; return [null, homeMsg, null, null]; } else { node.warn('[RESTORE] No running work order found'); } return [null, null, null, null]; } // ======================================================== // MODE: SCRAP PROMPT // ======================================================== if (mode === "scrap-prompt") { const prompt = msg.scrapPrompt || {}; const homeMsg = { topic: "scrapPrompt", payload: prompt }; const tabMsg = { ui_control: { tab: "Home" } }; // output1: nothing // output2: home template // output3: tab navigation // output4: graphs template (unused here) return [null, homeMsg, tabMsg, null]; } // ======================================================== // MODE: SCRAP UPDATE // ======================================================== if (mode === "scrap-update") { const activeOrder = global.get("activeWorkOrder") || {}; const kpis = msg.kpis || global.get("currentKPIs") || { oee: 0, availability: 0, performance: 0, quality: 0 }; const homeMsg = { topic: "activeWorkOrder", payload: { id: activeOrder.id || "", sku: activeOrder.sku || "", target: Number(activeOrder.target) || 0, good: Number(activeOrder.good) || 0, scrap: Number(activeOrder.scrap) || 0, cycleTime: Number(activeOrder.cycleTime) || 0, progressPercent: Number(activeOrder.progressPercent) || 0, lastUpdateIso: activeOrder.lastUpdateIso || new Date().toISOString(), kpis: kpis } }; return [null, homeMsg, null, null]; } // ======================================================== // MODE: SCRAP COMPLETE // ======================================================== if (mode === "scrap-complete") { const homeMsg = { topic: "activeWorkOrder", payload: null }; return [null, homeMsg, null, null]; } // ======================================================== // MODE: CHARTS → SEND REAL DATA TO GRAPH TEMPLATE // ======================================================== //if (mode === "charts") { // const realOEE = msg.realOEE || global.get("realOEE") || []; // const realAvailability = msg.realAvailability || global.get("realAvailability") || []; // const realPerformance = msg.realPerformance || global.get("realPerformance") || []; // const realQuality = msg.realQuality || global.get("realQuality") || []; // const chartsMsg = { // topic: "chartsData", // payload: { // oee: realOEE, // availability: realAvailability, // performance: realPerformance, // quality: realQuality // } // }; // Send ONLY to output #4 // return [null, null, null, chartsMsg]; //} // ======================================================== // DEFAULT // ======================================================== return [null, null, null, null];