Files
Virtual-Box/projects/Plastico/CHANGES_REFERENCE.md
2025-12-02 16:27:21 +00:00

11 KiB

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:

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:

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:

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):

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:

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:

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

-- 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

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)

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

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

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

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:

<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>
// 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;
    }
};