262 lines
9.9 KiB
Plaintext
262 lines
9.9 KiB
Plaintext
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": {
|
|
// PHASE 3: Check for existing progress before loading
|
|
msg._mode = "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 temporarily in flow context for resume/restart handlers
|
|
flow.set("pendingWorkOrder", order);
|
|
|
|
// Query database for existing progress
|
|
msg.topic = "SELECT cycle_count, good_parts, progress_percent FROM work_orders WHERE work_order_id = ?";
|
|
msg.payload = [order.id];
|
|
|
|
return [null, null, msg, null];
|
|
}
|
|
case "resume-work-order": {
|
|
// PHASE 3: Resume existing work order from database state
|
|
msg._mode = "resume";
|
|
const order = msg.payload || {};
|
|
const existingCycles = Number(order.cycle_count) || 0;
|
|
const existingGoodParts = Number(order.good_parts) || 0;
|
|
|
|
if (!order.id) {
|
|
node.error("No work order id supplied for resume", msg);
|
|
return [null, null, null, null];
|
|
}
|
|
|
|
// Set status to RUNNING
|
|
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];
|
|
|
|
// Initialize global state with existing values from database
|
|
global.set("activeWorkOrder", order);
|
|
global.set("cycleCount", existingCycles);
|
|
flow.set("lastMachineState", 0);
|
|
global.set("scrapPromptIssuedFor", null);
|
|
|
|
node.warn(`[RESUME] Loaded WO ${order.id} with ${existingCycles} cycles, ${existingGoodParts} good parts`);
|
|
|
|
return [null, null, msg, null];
|
|
}
|
|
case "restart-work-order": {
|
|
// PHASE 3: Restart work order from zero (reset database)
|
|
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];
|
|
}
|
|
|
|
// Reset database values to 0 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 = CASE WHEN work_order_id = ? THEN NOW() ELSE updated_at END WHERE status <> 'DONE'";
|
|
msg.payload = [order.id, order.id];
|
|
|
|
// Initialize global state to zero
|
|
global.set("activeWorkOrder", order);
|
|
global.set("cycleCount", 0);
|
|
flow.set("lastMachineState", 0);
|
|
global.set("scrapPromptIssuedFor", null);
|
|
|
|
node.warn(`[RESTART] Reset WO ${order.id} to zero`);
|
|
|
|
return [null, null, msg, null];
|
|
}
|
|
case "complete-work-order": {
|
|
// PHASE 4: Persist final counts before marking DONE
|
|
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];
|
|
}
|
|
|
|
// Get current state values to persist
|
|
const activeOrder = global.get("activeWorkOrder") || {};
|
|
const finalCycles = Number(global.get("cycleCount")) || 0;
|
|
const cavities = Number(global.get("moldActive")) || 1;
|
|
const scrapTotal = Number(activeOrder.scrap) || 0;
|
|
const totalProduced = finalCycles * cavities;
|
|
const finalGoodParts = Math.max(0, totalProduced - scrapTotal);
|
|
|
|
msg.completeOrder = order;
|
|
|
|
// PHASE 4: SQL updated to persist final counts
|
|
msg.topic = "UPDATE work_orders SET status = 'DONE', cycle_count = ?, good_parts = ?, progress_percent = 100, updated_at = NOW() WHERE work_order_id = ?";
|
|
msg.payload = [finalCycles, finalGoodParts, 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] Persisting final counts: cycles=${finalCycles}, good_parts=${finalGoodParts}. 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];
|
|
}
|
|
}
|