#!/usr/bin/env python3 import json # Read flows.json with open('/home/mdares/.node-red/flows.json', 'r') as f: flows = json.load(f) # New function code with Phase 3 modifications new_func = """switch (msg.action) { case "upload-excel": msg._mode = "upload"; return [msg, null, null, null]; case "refresh-work-orders": msg._mode = "select"; msg.topic = "SELECT * FROM work_orders ORDER BY created_at DESC;"; return [null, msg, null, null]; case "start-work-order": { msg._mode = "start-check-progress"; const order = msg.payload || {}; if (!order.id) { node.error("No work order id supplied for start", msg); return [null, null, null, null]; } // Store order data temporarily for after DB query flow.set("pendingWorkOrder", order); // Query database to check for existing progress msg.topic = "SELECT cycle_count, good_parts, progress_percent, target_qty FROM work_orders WHERE work_order_id = ? LIMIT 1"; msg.payload = [order.id]; node.warn(`[START-WO] Checking progress for WO ${order.id}`); return [null, msg, null, null]; } case "resume-work-order": { msg._mode = "resume"; const order = msg.payload || {}; if (!order.id) { node.error("No work order id supplied for resume", msg); return [null, null, null, null]; } node.warn(`[RESUME-WO] Resuming WO ${order.id} with existing progress`); // Set status to RUNNING without resetting progress 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 = [order.id, order.id]; msg.startOrder = order; // Load existing values into global state (will be set from DB query result) global.set("activeWorkOrder", order); global.set("cycleCount", Number(order.cycle_count) || 0); flow.set("lastMachineState", 0); global.set("scrapPromptIssuedFor", null); node.warn(`[RESUME-WO] Set cycleCount to ${order.cycle_count}`); return [null, null, msg, null]; } case "restart-work-order": { msg._mode = "restart"; const order = msg.payload || {}; if (!order.id) { node.error("No work order id supplied for restart", msg); return [null, null, null, null]; } node.warn(`[RESTART-WO] Restarting WO ${order.id} - resetting progress to 0`); // Reset progress in database AND set status to RUNNING msg.topic = "UPDATE work_orders SET status = CASE WHEN work_order_id = ? THEN 'RUNNING' ELSE 'PENDING' END, cycle_count = 0, good_parts = 0, progress_percent = 0, updated_at = NOW() WHERE work_order_id = ? OR status = 'RUNNING'"; msg.payload = [order.id, order.id]; msg.startOrder = order; // Initialize global state to 0 global.set("activeWorkOrder", order); global.set("cycleCount", 0); flow.set("lastMachineState", 0); global.set("scrapPromptIssuedFor", null); node.warn(`[RESTART-WO] Reset cycleCount to 0`); return [null, null, msg, null]; } case "complete-work-order": { msg._mode = "complete"; const order = msg.payload || {}; if (!order.id) { node.error("No work order id supplied for complete", msg); return [null, null, null, null]; } msg.completeOrder = order; // SQL with bound parameter for safety msg.topic = "UPDATE work_orders SET status = 'DONE', updated_at = NOW() WHERE work_order_id = ?"; msg.payload = [order.id]; // Clear ALL state on completion global.set("activeWorkOrder", null); global.set("trackingEnabled", false); global.set("productionStarted", false); global.set("kpiStartupMode", false); global.set("operatingTime", 0); global.set("lastCycleTime", null); global.set("cycleCount", 0); flow.set("lastMachineState", 0); global.set("scrapPromptIssuedFor", null); node.warn('[COMPLETE] Cleared all state flags'); return [null, null, null, msg]; } case "get-current-state": { // Return current state for UI sync on tab switch const activeOrder = global.get("activeWorkOrder") || null; const trackingEnabled = global.get("trackingEnabled") || false; const productionStarted = global.get("productionStarted") || false; const kpis = global.get("currentKPIs") || { oee: 0, availability: 0, performance: 0, quality: 0 }; msg._mode = "current-state"; msg.payload = { activeWorkOrder: activeOrder, trackingEnabled: trackingEnabled, productionStarted: productionStarted, kpis: kpis }; return [null, msg, null, null]; } case "restore-session": { // Query DB for any RUNNING work order on startup msg._mode = "restore-query"; msg.topic = "SELECT * FROM work_orders WHERE status = 'RUNNING' LIMIT 1"; msg.payload = []; node.warn('[RESTORE] Checking for running work order on startup'); return [null, msg, null, null]; } case "scrap-entry": { const { id, scrap } = msg.payload || {}; const scrapNum = Number(scrap) || 0; if (!id) { node.error("No work order id supplied for scrap entry", msg); return [null, null, null, null]; } const activeOrder = global.get("activeWorkOrder"); if (activeOrder && activeOrder.id === id) { activeOrder.scrap = (Number(activeOrder.scrap) || 0) + scrapNum; global.set("activeWorkOrder", activeOrder); } global.set("scrapPromptIssuedFor", null); msg._mode = "scrap-update"; msg.scrapEntry = { id, scrap: scrapNum }; // SQL with bound parameters for safety msg.topic = "UPDATE work_orders SET scrap_parts = scrap_parts + ?, updated_at = NOW() WHERE work_order_id = ?"; msg.payload = [scrapNum, id]; return [null, null, msg, null]; } case "scrap-skip": { const { id, remindAgain } = msg.payload || {}; if (!id) { node.error("No work order id supplied for scrap skip", msg); return [null, null, null, null]; } if (remindAgain) { global.set("scrapPromptIssuedFor", null); } msg._mode = "scrap-skipped"; return [null, null, null, null]; } case "start": { // START with KPI timestamp init - FIXED const now = Date.now(); global.set("trackingEnabled", true); global.set("productionStarted", true); global.set("kpiStartupMode", true); global.set("kpiBuffer", []); global.set("lastKPIRecordTime", now - 60000); global.set("productionStartTime", now); global.set("lastMachineCycleTime", now); global.set("lastCycleTime", now); global.set("operatingTime", 0); node.warn('[START] Initialized: trackingEnabled=true, productionStarted=true, kpiStartupMode=true, operatingTime=0'); const activeOrder = global.get("activeWorkOrder") || {}; msg._mode = "production-state"; msg.payload = msg.payload || {}; msg.trackingEnabled = true; msg.productionStarted = true; msg.machineOnline = true; msg.payload.trackingEnabled = true; msg.payload.productionStarted = true; msg.payload.machineOnline = true; return [null, msg, null, null]; } case "stop": { global.set("trackingEnabled", false); global.set("productionStarted", false); node.warn('[STOP] Set trackingEnabled=false, productionStarted=false'); // Send UI update so button state reflects change msg._mode = "production-state"; msg.payload = msg.payload || {}; msg.trackingEnabled = false; msg.productionStarted = false; msg.machineOnline = true; msg.payload.trackingEnabled = false; msg.payload.productionStarted = false; msg.payload.machineOnline = true; return [null, msg, null, null]; } case "start-tracking": { const activeOrder = global.get('activeOrder') || {}; if (!activeOrder.id) { node.warn('[START] Cannot start tracking: No active order loaded.'); return [null, { topic: "alert", payload: "Error: No active work order loaded." }, null, null]; } const now = Date.now(); global.set("trackingEnabled", true); global.set("kpiBuffer", []); global.set("lastKPIRecordTime", now - 60000); global.set("lastMachineCycleTime", now); global.set("lastCycleTime", now); global.set("operatingTime", 0.001); node.warn('[START] Cleared kpiBuffer for fresh production run'); // FIX: Use work_order_id consistently const dbMsg = { topic: `UPDATE work_orders SET production_start_time = ${now}, is_tracking = 1 WHERE work_order_id = '${activeOrder.id}'`, payload: [] }; const stateMsg = { topic: "machineStatus", payload: msg.payload || {} }; stateMsg.payload.trackingEnabled = true; stateMsg.payload.productionStarted = true; stateMsg.payload.machineOnline = true; return [dbMsg, stateMsg, null, null]; } }""" # Find and update Work Order buttons function node for node in flows: if node.get('name') == 'Work Order buttons': node['func'] = new_func print(f"Updated Work Order buttons function node") break # Write back to flows.json with open('/home/mdares/.node-red/flows.json', 'w') as f: json.dump(flows, f, indent=4) print("flows.json updated successfully")