Add enhanced print controller with pause/resume and error recovery

Features:
- Real-time progress modal with visual progress bar and counter
- Detailed event log with timestamps and color-coded status
- Pause/Resume functionality for checking printer/paper
- Reprint Last button for damaged labels
- Cancel button for emergency stops
- Automatic error detection and recovery
- Auto-pause on print failures (paper out/jam)
- Automatic retry of failed labels after resume
- Smart state management tracking current/last/failed labels
- Sequential label numbering maintained through errors
- Database update only on successful completion
- Proper modal display with .show class toggle
This commit is contained in:
2025-10-02 18:01:55 +03:00
parent c7266c32ee
commit dc337b6505
2 changed files with 612 additions and 64 deletions

View File

@@ -45,18 +45,22 @@
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.6);
background-color: rgba(0, 0, 0, 0.7);
justify-content: center;
align-items: center;
}
.print-progress-modal.show {
display: flex !important;
}
.print-progress-content {
background-color: white;
padding: 30px;
border-radius: 10px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
min-width: 400px;
max-width: 500px;
border-radius: 15px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
min-width: 500px;
max-width: 600px;
text-align: center;
}
@@ -70,36 +74,131 @@
margin-bottom: 15px;
color: #666;
font-size: 16px;
min-height: 24px;
}
.progress-bar-container {
width: 100%;
height: 30px;
background-color: #f0f0f0;
border-radius: 15px;
height: 35px;
background-color: #e9ecef;
border-radius: 18px;
overflow: hidden;
margin-bottom: 15px;
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.1);
box-shadow: inset 0 2px 6px rgba(0, 0, 0, 0.15);
position: relative;
}
.progress-bar {
height: 100%;
background: linear-gradient(90deg, #007bff 0%, #0056b3 100%);
background: linear-gradient(90deg, #28a745 0%, #20c997 100%);
width: 0%;
transition: width 0.3s ease;
border-radius: 15px;
transition: width 0.4s ease;
border-radius: 18px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: bold;
font-size: 14px;
}
.progress-bar.error {
background: linear-gradient(90deg, #dc3545 0%, #c82333 100%);
}
.progress-bar.paused {
background: linear-gradient(90deg, #ffc107 0%, #ff9800 100%);
}
.progress-details {
font-size: 18px;
font-size: 20px;
font-weight: bold;
color: #007bff;
color: #28a745;
margin-bottom: 15px;
}
.progress-details.error {
color: #dc3545;
}
.print-status-log {
max-height: 150px;
overflow-y: auto;
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 8px;
padding: 10px;
margin: 15px 0;
text-align: left;
font-family: monospace;
font-size: 12px;
}
.print-status-log div {
padding: 3px 0;
color: #495057;
}
.print-status-log div.success {
color: #28a745;
}
.print-status-log div.error {
color: #dc3545;
font-weight: bold;
}
.print-status-log div.warning {
color: #ffc107;
}
.print-control-buttons {
display: flex;
gap: 10px;
justify-content: center;
margin-top: 20px;
}
.print-control-btn {
padding: 10px 20px;
border: none;
border-radius: 8px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
}
.print-control-btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
.print-control-btn.pause {
background: #ffc107;
color: #000;
}
.print-control-btn.resume {
background: #28a745;
color: white;
}
.print-control-btn.reprint {
background: #17a2b8;
color: white;
}
.print-control-btn.cancel {
background: #dc3545;
color: white;
}
.print-control-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
</style>
{% endblock %}
@@ -341,18 +440,39 @@
</div>
<!-- Printing Progress Modal -->
<div id="print-progress-modal" class="print-progress-modal" style="display: none;">
<div id="print-progress-modal" class="print-progress-modal">
<div class="print-progress-content">
<h3>🖨️ Printing Labels</h3>
<h3>🖨️ Print Controller</h3>
<div class="progress-info">
<span id="progress-text">Preparing to print...</span>
</div>
<div class="progress-bar-container">
<div id="progress-bar" class="progress-bar"></div>
<div id="progress-bar" class="progress-bar">0%</div>
</div>
<div class="progress-details">
<span id="progress-count">0 / 0</span>
</div>
<!-- Print Status Log -->
<div class="print-status-log" id="print-status-log">
<div>Waiting to start...</div>
</div>
<!-- Control Buttons -->
<div class="print-control-buttons">
<button id="pause-print-btn" class="print-control-btn pause" style="display: none;">
⏸️ Pause
</button>
<button id="resume-print-btn" class="print-control-btn resume" style="display: none;">
▶️ Resume
</button>
<button id="reprint-last-btn" class="print-control-btn reprint" style="display: none;">
🔄 Reprint Last
</button>
<button id="cancel-print-btn" class="print-control-btn cancel" style="display: none;">
❌ Cancel
</button>
</div>
</div>
</div>
@@ -1276,12 +1396,65 @@ function generateHTMLLabel(orderData, pieceNumber, totalPieces) {
}
// Handle QZ Tray printing
// Handle QZ Tray printing with enhanced controller
let printController = {
isPaused: false,
isCancelled: false,
currentLabel: 0,
totalLabels: 0,
lastPrintedLabel: 0,
failedLabels: [],
orderData: null,
printerName: null
};
function addLogEntry(message, type = 'info') {
const log = document.getElementById('print-status-log');
const entry = document.createElement('div');
entry.className = type;
const timestamp = new Date().toLocaleTimeString();
entry.textContent = `[${timestamp}] ${message}`;
log.appendChild(entry);
log.scrollTop = log.scrollHeight;
}
function updateProgressBar(current, total, status = '') {
const progressBar = document.getElementById('progress-bar');
const progressCount = document.getElementById('progress-count');
const progressText = document.getElementById('progress-text');
const percentage = Math.round((current / total) * 100);
progressBar.style.width = `${percentage}%`;
progressBar.textContent = `${percentage}%`;
progressCount.textContent = `${current} / ${total}`;
if (status) {
progressText.textContent = status;
}
}
async function handleQZTrayPrint(selectedRow) {
const modal = document.getElementById('print-progress-modal');
const progressBar = document.getElementById('progress-bar');
const progressText = document.getElementById('progress-text');
const progressCount = document.getElementById('progress-count');
const log = document.getElementById('print-status-log');
const pauseBtn = document.getElementById('pause-print-btn');
const resumeBtn = document.getElementById('resume-print-btn');
const reprintBtn = document.getElementById('reprint-last-btn');
const cancelBtn = document.getElementById('cancel-print-btn');
// Reset controller state
printController = {
isPaused: false,
isCancelled: false,
currentLabel: 0,
totalLabels: 0,
lastPrintedLabel: 0,
failedLabels: [],
orderData: null,
printerName: null
};
try {
if (!qzTray) {
@@ -1313,76 +1486,188 @@ async function handleQZTrayPrint(selectedRow) {
};
const quantity = orderData.cantitate;
printController.orderData = orderData;
printController.printerName = selectedPrinter;
printController.totalLabels = quantity;
console.log(`🖨️ Printing ${quantity} labels via QZ Tray to ${selectedPrinter}`);
// Show progress modal
modal.style.display = 'flex';
progressText.textContent = 'Preparing to print...';
progressBar.style.width = '0%';
progressCount.textContent = `0 / ${quantity}`;
// Show modal with show class for proper display
modal.classList.add('show');
log.innerHTML = '';
addLogEntry(`Starting print job: ${quantity} labels`, 'info');
addLogEntry(`Printer: ${selectedPrinter}`, 'info');
addLogEntry(`Order: ${orderData.comanda_productie}`, 'info');
updateProgressBar(0, quantity, 'Preparing to print...');
progressBar.classList.remove('error', 'paused');
// Show control buttons
pauseBtn.style.display = 'inline-block';
cancelBtn.style.display = 'inline-block';
reprintBtn.style.display = 'none';
resumeBtn.style.display = 'none';
// Setup button handlers
pauseBtn.onclick = () => {
printController.isPaused = true;
pauseBtn.style.display = 'none';
resumeBtn.style.display = 'inline-block';
progressBar.classList.add('paused');
addLogEntry('Print job paused by user', 'warning');
updateProgressBar(printController.currentLabel, quantity, '⏸️ Paused - Check printer paper');
};
resumeBtn.onclick = () => {
printController.isPaused = false;
resumeBtn.style.display = 'none';
pauseBtn.style.display = 'inline-block';
progressBar.classList.remove('paused');
addLogEntry('Print job resumed', 'success');
};
reprintBtn.onclick = async () => {
if (printController.lastPrintedLabel > 0) {
addLogEntry(`Reprinting label ${printController.lastPrintedLabel}...`, 'info');
try {
await generatePDFAndPrint(selectedPrinter, orderData, printController.lastPrintedLabel, quantity);
addLogEntry(`Label ${printController.lastPrintedLabel} reprinted successfully`, 'success');
} catch (error) {
addLogEntry(`Reprint failed: ${error.message}`, 'error');
}
}
};
cancelBtn.onclick = () => {
printController.isCancelled = true;
addLogEntry('Print job cancelled by user', 'error');
progressBar.classList.add('error');
updateProgressBar(printController.currentLabel, quantity, '❌ Cancelled');
};
try {
// Print each label sequentially
for (let i = 1; i <= quantity; i++) {
progressText.textContent = `Printing label ${i} of ${quantity}...`;
progressCount.textContent = `${i - 1} / ${quantity}`;
progressBar.style.width = `${((i - 1) / quantity) * 100}%`;
if (printController.isCancelled) {
addLogEntry('Print job terminated', 'error');
break;
}
// Generate PDF and send to printer
await generatePDFAndPrint(selectedPrinter, orderData, i, quantity);
// Wait while paused
while (printController.isPaused && !printController.isCancelled) {
await new Promise(resolve => setTimeout(resolve, 500));
}
// Update progress after successful print
progressCount.textContent = `${i} / ${quantity}`;
progressBar.style.width = `${(i / quantity) * 100}%`;
if (printController.isCancelled) break;
printController.currentLabel = i;
updateProgressBar(i - 1, quantity, `Printing label ${i} of ${quantity}...`);
addLogEntry(`Sending label ${i} to printer...`, 'info');
try {
// Generate PDF and send to printer
await generatePDFAndPrint(selectedPrinter, orderData, i, quantity);
printController.lastPrintedLabel = i;
updateProgressBar(i, quantity, `Label ${i} printed successfully`);
addLogEntry(`✓ Label ${i} printed successfully`, 'success');
// Show reprint button after each successful print
reprintBtn.style.display = 'inline-block';
} catch (printError) {
// Print error detected
progressBar.classList.add('error');
printController.failedLabels.push(i);
addLogEntry(`✗ Label ${i} failed: ${printError.message}`, 'error');
// Pause automatically on error
printController.isPaused = true;
pauseBtn.style.display = 'none';
resumeBtn.style.display = 'inline-block';
progressBar.classList.add('paused');
updateProgressBar(i - 1, quantity, '⚠️ ERROR - Check printer (paper jam/out of paper)');
// Wait for user to resume or cancel
while (printController.isPaused && !printController.isCancelled) {
await new Promise(resolve => setTimeout(resolve, 500));
}
if (!printController.isCancelled) {
// Retry failed label
addLogEntry(`Retrying label ${i}...`, 'warning');
await generatePDFAndPrint(selectedPrinter, orderData, i, quantity);
addLogEntry(`✓ Label ${i} printed successfully (retry)`, 'success');
printController.lastPrintedLabel = i;
progressBar.classList.remove('error');
}
}
// Small delay between labels for printer processing
if (i < quantity) {
if (i < quantity && !printController.isCancelled) {
await new Promise(resolve => setTimeout(resolve, 500));
}
}
// All labels printed successfully
progressText.textContent = '✅ All labels printed! Updating database...';
// Update database to mark order as printed
try {
const updateResponse = await fetch(`/update_printed_status/${orderData.id}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
});
if (!printController.isCancelled) {
// All labels printed successfully
progressBar.classList.remove('paused', 'error');
updateProgressBar(quantity, quantity, '✅ All labels printed! Updating database...');
addLogEntry('All labels completed successfully', 'success');
if (!updateResponse.ok) {
console.error('Failed to update printed status in database');
progressText.textContent = '⚠️ Labels printed but database update failed';
// Hide control buttons
pauseBtn.style.display = 'none';
resumeBtn.style.display = 'none';
cancelBtn.style.display = 'none';
// Update database to mark order as printed
try {
const updateResponse = await fetch(`/update_printed_status/${orderData.id}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
});
if (!updateResponse.ok) {
console.error('Failed to update printed status in database');
addLogEntry('Database update failed', 'warning');
updateProgressBar(quantity, quantity, '⚠️ Labels printed but database update failed');
await new Promise(resolve => setTimeout(resolve, 2000));
} else {
addLogEntry('Database updated successfully', 'success');
updateProgressBar(quantity, quantity, '✅ Complete! Refreshing table...');
await new Promise(resolve => setTimeout(resolve, 1000));
}
} catch (dbError) {
console.error('Database update error:', dbError);
addLogEntry(`Database error: ${dbError.message}`, 'error');
updateProgressBar(quantity, quantity, '⚠️ Labels printed but database update failed');
await new Promise(resolve => setTimeout(resolve, 2000));
} else {
progressText.textContent = '✅ Complete! Refreshing table...';
await new Promise(resolve => setTimeout(resolve, 1000));
}
} catch (dbError) {
console.error('Database update error:', dbError);
progressText.textContent = '⚠️ Labels printed but database update failed';
// Close modal
modal.classList.remove('show');
// Show success notification
showNotification(`✅ Successfully printed ${quantity} labels!`, 'success');
// Refresh table to show updated status
document.getElementById('check-db-btn').click();
} else {
// Job was cancelled
await new Promise(resolve => setTimeout(resolve, 2000));
modal.classList.remove('show');
showNotification(`⚠️ Print job cancelled. ${printController.lastPrintedLabel} of ${quantity} labels printed.`, 'warning');
}
// Close modal
modal.style.display = 'none';
// Show success notification
showNotification(`✅ Successfully printed ${quantity} labels!`, 'success');
// Refresh table to show updated status
document.getElementById('check-db-btn').click();
} catch (printError) {
modal.style.display = 'none';
modal.classList.remove('show');
throw new Error(`Print failed: ${printError.message}`);
}
} catch (error) {
modal.style.display = 'none';
modal.classList.remove('show');
console.error('QZ Tray print error:', error);
showNotification('❌ QZ Tray print error: ' + error.message, 'error');
}