# Quick Reference: Multi-Phase Implementation Changes ## Modified Function Nodes in flows.json ### 1. Machine Cycles (ID: 0d023d87a13bf56f) - Phase 2 **Added:** - 5-second throttling for DB persistence - Global variable `lastWorkOrderDbWrite` to track last write time - Enhanced 4th output to include `progress_percent` **Key Code Addition:** ```javascript const lastDbWrite = global.get("lastWorkOrderDbWrite") || 0; const timeSinceLastWrite = now - lastDbWrite; let persistCycleCount = null; if (timeSinceLastWrite >= 5000) { persistCycleCount = { topic: "UPDATE work_orders SET cycle_count = ?, good_parts = ?, progress_percent = ?, updated_at = NOW() WHERE work_order_id = ?", payload: [cycles, produced, progress, activeOrder.id] }; global.set("lastWorkOrderDbWrite", now); } ``` ### 2. Work Order buttons (ID: 9bbd4fade968036d) - Phases 3 & 4 **Modified Actions:** - `start-work-order`: Now queries DB for existing progress - `complete-work-order`: Persists final counts before marking DONE **New Actions:** - `resume-work-order`: Loads work order with existing database values - `restart-work-order`: Resets work order to zero in database and global state **Key Changes:** **start-work-order:** ```javascript case "start-work-order": { msg._mode = "check-progress"; const order = msg.payload || {}; flow.set("pendingWorkOrder", order); 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]; } ``` **resume-work-order:** ```javascript case "resume-work-order": { msg._mode = "resume"; const order = msg.payload || {}; const existingCycles = Number(order.cycle_count) || 0; global.set("activeWorkOrder", order); global.set("cycleCount", existingCycles); // Load from DB msg.topic = "UPDATE work_orders SET status = ... 'RUNNING' ..."; return [null, null, msg, null]; } ``` **complete-work-order (Phase 4):** ```javascript case "complete-work-order": { const finalCycles = Number(global.get("cycleCount")) || 0; const finalGoodParts = Math.max(0, totalProduced - scrapTotal); 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]; return [null, null, null, msg]; } ``` ### 3. Refresh Trigger (ID: 578c92e75bf0f266) - Phase 3 **Added Modes:** - `check-progress`: Routes to Back to UI for progress check processing - `resume`: Routes to Back to UI and triggers work order list refresh - `restart`: Routes to Back to UI and triggers work order list refresh **Key Addition:** ```javascript if (msg._mode === "resume" || msg._mode === "restart") { const originalMsg = {...msg}; msg._mode = "select"; msg.topic = "SELECT * FROM work_orders ORDER BY updated_at DESC;"; return [msg, originalMsg]; } if (msg._mode === "check-progress") { return [null, msg]; } ``` ### 4. Back to UI (ID: f2bab26e27e2023d) - Phases 3 & 5 **New Modes:** - `check-progress`: Sends resumePrompt to UI if progress exists - `resume`: Sends activeWorkOrder with database values - `restart`: Sends activeWorkOrder with zeroed values **Modified Modes:** - `restore-query`: Enhanced documentation (Phase 5) **Key Additions:** **check-progress:** ```javascript 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; if (cycleCount > 0 || goodParts > 0) { msg.topic = "resumePrompt"; msg.payload = { id: pendingOrder.id, sku: pendingOrder.sku, cycleCount: cycleCount, goodParts: goodParts, progressPercent: Number(row.progress_percent) || 0 }; return [null, msg, null, null]; } } // Auto-start if no progress global.set("cycleCount", 0); return [null, null, null, null]; } ``` --- ## Message Flow Diagram ### Load Work Order (Phase 3) ``` User clicks Load ↓ Work Order buttons (start-work-order) ↓ (output 3, _mode: "check-progress") mariaDB (SELECT cycle_count, good_parts...) ↓ Refresh Trigger (routes check-progress) ↓ (output 2) Back to UI (check-progress) ↓ IF progress exists: → Send resumePrompt to UI → UI shows Resume/Restart dialog ELSE: → Auto-start with zero values ``` ### Resume Work Order (Phase 3) ``` User clicks Resume ↓ Work Order buttons (resume-work-order) ↓ (output 3, _mode: "resume") mariaDB (UPDATE status = 'RUNNING'...) ↓ Refresh Trigger (routes resume + refreshes WO list) ↓ (output 2) Back to UI (resume) ↓ Send activeWorkOrder to Home template (with existing cycle_count and good_parts) ``` ### Machine Cycle (Phase 2) ``` Every machine cycle ↓ Machine Cycles function ↓ Check throttle (5 seconds elapsed?) ↓ IF yes: ↓ (output 4) DB Guard (Cycles) ↓ mariaDB (UPDATE cycle_count, good_parts...) ELSE: → Skip DB write ``` ### Complete Work Order (Phase 4) ``` User clicks Done ↓ Work Order buttons (complete-work-order) ↓ Calculate final counts ↓ (output 4, _mode: "complete") mariaDB (UPDATE status='DONE', cycle_count=?, good_parts=?...) ↓ Refresh Trigger (routes complete + refreshes WO list) ↓ (output 2) Back to UI (complete) ↓ Clear activeWorkOrder from Home template ``` --- ## Database Schema Requirements ```sql -- Required columns in work_orders table CREATE TABLE work_orders ( work_order_id VARCHAR(255) PRIMARY KEY, sku VARCHAR(255), target_qty INT, cycle_count INT DEFAULT 0, -- Added Phase 1 good_parts INT DEFAULT 0, -- Added Phase 1 scrap_parts INT DEFAULT 0, progress_percent INT DEFAULT 0, status VARCHAR(50) DEFAULT 'PENDING', cycle_time DECIMAL(10,2), updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX idx_work_order_id (work_order_id), INDEX idx_status (status) ); ``` --- ## Global Variables Reference | Variable | Type | Purpose | Set By | Used By | |----------|------|---------|--------|---------| | activeWorkOrder | Object | Current work order | Work Order buttons | Machine Cycles, Back to UI | | cycleCount | Number | Current cycle count | Machine Cycles, Work Order buttons | Machine Cycles, Complete | | lastWorkOrderDbWrite | Number | Timestamp of last DB write | Machine Cycles | Machine Cycles (throttling) | | trackingEnabled | Boolean | Production tracking active | Work Order buttons (start/stop) | Machine Cycles | | productionStarted | Boolean | Production has started | Machine Cycles, Work Order buttons | Machine Cycles | | moldActive | Number | Cavities in current mold | Mold settings | Machine Cycles, Complete | | scrapPromptIssuedFor | String | Work order ID for scrap prompt | Machine Cycles | Machine Cycles | ## Flow Context Variables | Variable | Type | Purpose | Set By | Used By | |----------|------|---------|--------|---------| | pendingWorkOrder | Object | Work order awaiting resume/restart decision | Work Order buttons (start) | Back to UI (check-progress) | | lastMachineState | Number | Previous machine state (0 or 1) | Machine Cycles | Machine Cycles (edge detection) | --- ## Testing SQL Queries ### Check Current Progress ```sql SELECT work_order_id, cycle_count, good_parts, progress_percent, status, updated_at FROM work_orders WHERE status = 'RUNNING'; ``` ### Verify Throttling (should update every ~5 seconds) ```sql SELECT work_order_id, cycle_count, good_parts, updated_at FROM work_orders WHERE work_order_id = 'YOUR_WO_ID' ORDER BY updated_at DESC LIMIT 10; ``` ### Check Completed Work Orders ```sql SELECT work_order_id, cycle_count, good_parts, status, updated_at FROM work_orders WHERE status = 'DONE' ORDER BY updated_at DESC; ``` ### Manually Reset Work Order for Testing ```sql UPDATE work_orders SET cycle_count = 0, good_parts = 0, progress_percent = 0, status = 'PENDING' WHERE work_order_id = 'YOUR_WO_ID'; ``` --- ## Troubleshooting ### Work Order Not Persisting 1. Check Node-RED debug output for `[DB PERSIST]` messages 2. Verify 5 seconds have elapsed between cycles 3. Check DB Guard (Cycles) node is receiving messages 4. Verify mariaDB connection is active ### Resume Prompt Not Showing 1. UI template not yet implemented (expected - see IMPLEMENTATION_SUMMARY.md) 2. Check Back to UI debug for `[RESUME PROMPT]` message 3. Verify msg.topic === "resumePrompt" in debug output ### Cycle Count Not Restoring After Restart 1. Check `restore-query` mode in Back to UI 2. Verify work order status is 'RUNNING' in database 3. Check global.cycleCount is being set from database value 4. Look for `[RESTORE]` message in debug output ### Complete Button Not Saving Final Counts 1. Verify complete-work-order SQL includes cycle_count and good_parts 2. Check database after clicking Done 3. Look for `[COMPLETE] Persisting final counts` in debug output --- ## Rollback Commands ```bash cd /home/mdares/projects/Plastico # List available backups ls -lh flows.json.backup* # Rollback to original cp flows.json.backup flows.json # Rollback to specific phase cp flows.json.backup_phase2_YYYYMMDD_HHMMSS flows.json # Verify rollback head -n 1 flows.json # Restart Node-RED to apply changes # (method depends on your setup - systemctl, pm2, docker, etc.) ``` --- ## Next Implementation: UI Dialog To complete Phase 3, add this to Home or Work Orders template: ```html

Resume Work Order?

{{ resumeData.sku }} ({{ resumeData.id }})

Current Progress: {{ resumeData.goodParts }} / {{ resumeData.target }} parts

{{ resumeData.progressPercent }}% complete ({{ resumeData.cycleCount }} cycles)

``` ```javascript // In template