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

397 lines
11 KiB
Plaintext

# 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
<div v-if="showResumePrompt" class="resume-dialog">
<h3>Resume Work Order?</h3>
<p>{{ resumeData.sku }} ({{ resumeData.id }})</p>
<p>Current Progress: {{ resumeData.goodParts }} / {{ resumeData.target }} parts</p>
<p>{{ resumeData.progressPercent }}% complete ({{ resumeData.cycleCount }} cycles)</p>
<button @click="resumeWorkOrder" class="btn-resume">
Resume from {{ resumeData.goodParts }} parts
</button>
<button @click="confirmRestart" class="btn-restart">
Restart from Zero
</button>
</div>
```
```javascript
// In template <script>
scope.$watch('msg', function(msg) {
if (msg.topic === "resumePrompt") {
scope.showResumePrompt = true;
scope.resumeData = msg.payload;
}
});
scope.resumeWorkOrder = function() {
scope.send({
action: "resume-work-order",
payload: scope.resumeData
});
scope.showResumePrompt = false;
};
scope.confirmRestart = function() {
if (confirm("Are you sure? This will reset all progress to zero.")) {
scope.send({
action: "restart-work-order",
payload: scope.resumeData
});
scope.showResumePrompt = false;
}
};
```