391 lines
14 KiB
Plaintext
391 lines
14 KiB
Plaintext
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];
|