Files
2025-12-02 16:27:21 +00:00

270 lines
9.8 KiB
Python

#!/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")