Files
quality_recticel/backup/print_module_backup.html
2025-10-05 14:32:47 -04:00

1346 lines
61 KiB
HTML
Executable File
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends "base.html" %}
{% block head %}
<style>
#label-preview {
background: #fafafa;
position: relative;
overflow: hidden;
}
/* Inserted custom CSS from user */
.card.scan-table-card table.print-module-table.scan-table thead th {
border-bottom: 2px solid #dee2e6 !important;
background-color: #f8f9fa !important;
padding: 0.25rem 0.4rem !important;
text-align: left !important;
font-weight: 600 !important;
font-size: 10px !important;
line-height: 1.2 !important;
}
/* Enhanced table styling */
.card.scan-table-card table.print-module-table.scan-table {
width: 100% !important;
border-collapse: collapse !important;
}
.card.scan-table-card table.print-module-table.scan-table tbody tr:hover td {
background-color: #f8f9fa !important;
cursor: pointer !important;
}
.card.scan-table-card table.print-module-table.scan-table tbody tr.selected td {
background-color: #007bff !important;
color: white !important;
}
</style>
{% endblock %}
{% block content %}
<div class="scan-container" style="display: flex; flex-direction: row; gap: 20px; width: 100%; align-items: flex-start;">
<!-- Label Preview Card -->
<div class="card scan-form-card" style="display: flex; flex-direction: column; justify-content: flex-start; align-items: center; min-height: 700px; width: 330px; flex-shrink: 0; position: relative; padding: 15px;">
<div class="label-view-title" style="width: 100%; text-align: center; padding: 0 0 15px 0; font-size: 18px; font-weight: bold; letter-spacing: 0.5px;">Label View</div>
<!-- Label Preview Section -->
<div id="label-preview" style="border: 1px solid #ddd; padding: 10px; position: relative; background: #fafafa; width: 301px; height: 434.7px;">
<!-- Label content rectangle -->
<div id="label-content" style="position: absolute; top: 65.7px; left: 11.34px; width: 227.4px; height: 321.3px; border: 2px solid #333; background: white;">
<!-- Top row content: Company name -->
<div style="position: absolute; top: 0; left: 0; right: 0; height: 32.13px; display: flex; align-items: center; justify-content: center; font-weight: bold; font-size: 12px; color: #000; z-index: 10;">
INNOFA RROMANIA SRL
</div>
<!-- Row 2 content: Customer Name -->
<div id="customer-name-row" style="position: absolute; top: 32.13px; left: 0; right: 0; height: 32.13px; display: flex; align-items: center; justify-content: center; font-size: 11px; color: #000;">
<!-- Customer name will be populated here -->
</div>
<!-- Horizontal dividing lines for 9 rows (row 6 is double height) -->
<div style="position: absolute; top: 32.13px; left: 0; right: 0; height: 1px; background: #999;"></div>
<div style="position: absolute; top: 64.26px; left: 0; right: 0; height: 1px; background: #999;"></div>
<div style="position: absolute; top: 96.39px; left: 0; right: 0; height: 1px; background: #999;"></div>
<div style="position: absolute; top: 128.52px; left: 0; right: 0; height: 1px; background: #999;"></div>
<div style="position: absolute; top: 160.65px; left: 0; right: 0; height: 1px; background: #999;"></div>
<!-- Row 6 is double height for Description -->
<div style="position: absolute; top: 224.91px; left: 0; right: 0; height: 1px; background: #999;"></div>
<!-- Row 7 for Size -->
<div style="position: absolute; top: 257.04px; left: 0; right: 0; height: 1px; background: #999;"></div>
<!-- Row 8 for Article Code -->
<div style="position: absolute; top: 289.17px; left: 0; right: 0; height: 1px; background: #999;"></div>
<!-- Vertical dividing line starting from row 3 to bottom at 40% width -->
<div style="position: absolute; left: 90.96px; top: 64.26px; width: 1px; height: 257.04px; background: #999;"></div>
<!-- Row 3 content: Quantity ordered -->
<div style="position: absolute; top: 64.26px; left: 0; width: 90.96px; height: 32.13px; display: flex; align-items: center; padding-left: 5px; font-size: 10px; color: #000;">
Quantity ordered
</div>
<div id="quantity-ordered-value" style="position: absolute; top: 64.26px; left: 90.96px; width: 136.44px; height: 32.13px; display: flex; align-items: center; justify-content: center; font-size: 13px; font-weight: bold; color: #000;">
<!-- Quantity value will be populated here -->
</div>
<!-- Row 4 content: Customer order -->
<div style="position: absolute; top: 96.39px; left: 0; width: 90.96px; height: 32.13px; display: flex; align-items: center; padding-left: 5px; font-size: 10px; color: #000;">
Customer order
</div>
<div id="client-order-info" style="position: absolute; top: 96.39px; left: 90.96px; width: 136.44px; height: 32.13px; display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: bold; color: #000;">
<!-- Client order info will be populated here -->
</div>
<!-- Row 5 content: Delivery date -->
<div style="position: absolute; top: 128.52px; left: 0; width: 90.96px; height: 32.13px; display: flex; align-items: center; padding-left: 5px; font-size: 10px; color: #000;">
Delivery date
</div>
<div id="delivery-date-value" style="position: absolute; top: 128.52px; left: 90.96px; width: 136.44px; height: 32.13px; display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: bold; color: #000;">
<!-- Delivery date value will be populated here -->
</div>
<!-- Row 6 content: Description (double height row) -->
<div style="position: absolute; top: 160.65px; left: 0; width: 90.96px; height: 64.26px; display: flex; align-items: center; padding-left: 5px; font-size: 10px; color: #000;">
Description
</div>
<div id="description-value" style="position: absolute; top: 160.65px; left: 90.96px; width: 136.44px; height: 64.26px; display: flex; align-items: center; justify-content: center; font-size: 11px; font-weight: bold; color: #000; text-align: center; line-height: 1.2; padding: 2px; overflow: hidden; word-wrap: break-word;">
<!-- Description value will be populated here -->
</div>
<!-- Row 7 content: Size -->
<div style="position: absolute; top: 224.91px; left: 0; width: 90.96px; height: 32.13px; display: flex; align-items: center; padding-left: 5px; font-size: 10px; color: #000;">
Size
</div>
<div id="size-value" style="position: absolute; top: 224.91px; left: 90.96px; width: 136.44px; height: 32.13px; display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: bold; color: #000;">
<!-- Size value will be populated here -->
</div>
<!-- Row 8 content: Article Code -->
<div style="position: absolute; top: 257.04px; left: 0; width: 90.96px; height: 32.13px; display: flex; align-items: center; padding-left: 5px; font-size: 10px; color: #000;">
Article Code
</div>
<div id="article-code-value" style="position: absolute; top: 257.04px; left: 90.96px; width: 136.44px; height: 32.13px; display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: bold; color: #000;">
<!-- Article code value will be populated here -->
</div>
<!-- Row 9 content: Prod Order (final row) -->
<div style="position: absolute; top: 289.17px; left: 0; width: 90.96px; height: 32.13px; display: flex; align-items: center; padding-left: 5px; font-size: 10px; color: #000;">
Prod Order
</div>
<div id="prod-order-value" style="position: absolute; top: 289.17px; left: 90.96px; width: 136.44px; height: 32.13px; display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: bold; color: #000;">
<!-- Prod order value will be populated here -->
</div>
</div>
<!-- Barcode Frame - positioned 10px below rectangle with 2mm side margins -->
<div id="barcode-frame" style="position: absolute; top: 395px; left: 7.56px; width: 295.44px; height: 50px; background: white; display: flex; flex-direction: column; align-items: center; justify-content: center;">
<!-- Code 128 Barcode representation -->
<div id="barcode-display" style="width: 100%; height: 45px; background: linear-gradient(90deg, #000 1px, transparent 1px, transparent 2px, #000 2px, transparent 3px, #000 4px, transparent 5px, #000 6px, transparent 7px, #000 8px, transparent 9px, #000 10px, transparent 11px, #000 12px, transparent 13px); background-size: 15px 100%; background-repeat: repeat-x;">
</div>
<!-- Barcode text below the bars -->
<div id="barcode-text" style="font-size: 8px; font-family: 'Courier New', monospace; margin-top: 2px; text-align: center; font-weight: bold;">
<!-- Barcode text will be populated here -->
</div>
</div>
<!-- Vertical Barcode Frame - positioned on the right side of the label -->
<div id="vertical-barcode-frame" style="position: absolute; top: 70px; left: 245px; width: 50px; height: 309.96px; background: white; display: flex; flex-direction: column; align-items: center; justify-content: center;">
<!-- Vertical Code 128 Barcode representation -->
<div id="vertical-barcode-display" style="width: 50px; height: 280px; background: linear-gradient(0deg, #000 1px, transparent 1px, transparent 2px, #000 2px, transparent 3px, #000 4px, transparent 5px, #000 6px, transparent 7px, #000 8px, transparent 9px, #000 10px, transparent 11px, #000 12px, transparent 13px); background-size: 100% 15px; background-repeat: repeat-y;">
</div>
<!-- Vertical barcode text -->
<div id="vertical-barcode-text" style="font-size: 6px; font-family: 'Courier New', monospace; margin-top: 5px; text-align: center; font-weight: bold; writing-mode: vertical-rl; text-orientation: mixed;">
<!-- Vertical barcode text will be populated here -->
</div>
</div>
</div>
<!-- Print Options and Button Section - Inside the Card -->
<div style="width: 100%; margin-top: 20px;">
<!-- Print Method Selection -->
<div style="margin-bottom: 15px;">
<label style="font-size: 12px; font-weight: 600; color: #495057; margin-bottom: 8px; display: block;">
🖨️ Print Method:
</label>
<div class="form-check mb-2">
<input class="form-check-input" type="radio" name="printMethod" id="pdfGenerate" value="pdf" checked>
<label class="form-check-label" for="pdfGenerate" style="font-size: 11px; line-height: 1.3;">
<strong>Generate PDF</strong><br>
<span class="text-muted">Create PDF for manual printing (recommended)</span>
</label>
</div>
</div>
<!-- Print Button -->
<div style="width: 100%; text-align: center; margin-bottom: 15px;">
<button id="print-label-btn" class="btn btn-success" style="font-size: 14px; padding: 10px 30px; border-radius: 6px; font-weight: 600;">
<20> Generate PDF Labels
</button>
</div>
<!-- Print Information -->
<div style="width: 100%; text-align: center; color: #6c757d; font-size: 11px; line-height: 1.4;">
<div style="margin-bottom: 5px;">Creates sequential labels based on quantity</div>
<small>(e.g., CP00000711-001 to CP00000711-063)</small>
</div>
<!-- Direct Print Service Download -->
<div style="width: 100%; text-align: center; margin-top: 20px; padding-top: 15px; border-top: 1px solid #e9ecef;">
<!-- Electron App (Recommended) -->
<div style="background: #d4edda; border: 1px solid #c3e6cb; border-radius: 6px; padding: 12px; margin-bottom: 10px;">
<div style="font-size: 11px; color: #155724; margin-bottom: 8px;">
<strong>🖥️ Quality Print Desktop v1.0.0 (RECOMMENDED)</strong>
</div>
<div style="font-size: 10px; color: #495057; margin-bottom: 10px; line-height: 1.3;">
Professional desktop app • Direct hardware access • Rich formatting
</div>
<button onclick="downloadElectronApp()" class="btn btn-success btn-sm" style="font-size: 10px; padding: 4px 12px; margin-right: 5px;">
📥 Download Linux Version (99MB)
</button>
<button onclick="downloadPortableApp()" class="btn btn-outline-success btn-sm" style="font-size: 10px; padding: 4px 12px; margin-right: 5px;">
💻 Windows Portable (8KB)
</button>
<small style="color: #28a745; font-weight: bold;">✅ READY!</small>
</div>
<!-- Windows Service (Deprecated) -->
<div style="background: #f8d7da; border: 1px solid #f5c6cb; border-radius: 6px; padding: 12px; margin-bottom: 10px;">
<div style="font-size: 11px; color: #721c24; margin-bottom: 8px;">
<strong>📦 Windows Print Service (DEPRECATED)</strong>
</div>
<div style="font-size: 10px; color: #6c757d; margin-bottom: 10px; line-height: 1.3;">
Legacy background service • Limited features • Use desktop app instead
</div>
<a href="{{ url_for('main.download_print_service') }}?package=complete" class="btn btn-outline-secondary btn-sm" download style="font-size: 10px; padding: 4px 12px; text-decoration: none;">
📥 Download Legacy Service
</a>
</div>
<div style="font-size: 9px; color: #6c757d; margin-top: 5px; line-height: 1.2;">
<strong>🏆 Desktop App Benefits:</strong> Direct thermal printing • Rich barcodes/QR codes • Better reliability<br>
<small>✨ Two versions: Full Linux AppImage or Lightweight Windows Portable • Cross-platform support</small>
</div>
</div>
</div>
</div> <!-- Data Preview Card -->
<div class="card scan-table-card" style="min-height: 700px; width: calc(100% - 350px); margin: 0;">
<h3>Data Preview (Unprinted Orders)</h3>
<button id="check-db-btn" class="btn btn-primary mb-3">Check Database</button>
<div class="report-table-container">
<table class="scan-table print-module-table">
<thead>
<tr>
<th>ID</th>
<th>Comanda Productie</th>
<th>Cod Articol</th>
<th>Descr. Com. Prod</th>
<th>Cantitate</th>
<th>Data Livrare</th>
<th>Dimensiune</th>
<th>Com. Achiz. Client</th>
<th>Nr. Linie</th>
<th>Customer Name</th>
<th>Customer Art. Nr.</th>
<th>Open Order</th>
<th>Line</th>
<th>Printed</th>
<th>Created</th>
</tr>
</thead>
<tbody id="unprinted-orders-table">
<!-- Data will be dynamically loaded here -->
</tbody>
</table>
</div>
</div>
</div>
<script>
// Notification system
function showNotification(message, type = 'info') {
// Remove existing notifications
const existingNotifications = document.querySelectorAll('.wcpp-notification');
existingNotifications.forEach(n => n.remove());
// Create notification element
const notification = document.createElement('div');
notification.className = `wcpp-notification alert alert-${type === 'error' ? 'danger' : type === 'success' ? 'success' : type === 'warning' ? 'warning' : 'info'}`;
notification.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
z-index: 9999;
max-width: 350px;
padding: 15px;
border-radius: 5px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
animation: slideIn 0.3s ease-out;
`;
notification.innerHTML = `
<div style="display: flex; align-items: center; justify-content: space-between;">
<span style="flex: 1; padding-right: 10px;">${message}</span>
<button type="button" class="close" onclick="this.parentElement.parentElement.remove()" style="background: none; border: none; font-size: 20px; cursor: pointer;">&times;</button>
</div>
`;
// Add styles for animation
if (!document.querySelector('#wcpp-notification-styles')) {
const styles = document.createElement('style');
styles.id = 'wcpp-notification-styles';
styles.textContent = `
@keyframes slideIn {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
`;
document.head.appendChild(styles);
}
document.body.appendChild(notification);
// Auto-remove after 5 seconds
setTimeout(() => {
if (notification.parentElement) {
notification.remove();
}
}, 5000);
}
// Database loading functionality
document.getElementById('check-db-btn').addEventListener('click', function() {
const button = this;
button.textContent = 'Loading...';
button.disabled = true;
fetch('/get_unprinted_orders')
.then(response => {
if (response.status === 403) {
return response.json().then(errorData => {
throw new Error(`Access Denied: ${errorData.error}`);
});
} else if (!response.ok) {
return response.text().then(text => {
throw new Error(`HTTP ${response.status}: ${text}`);
});
}
return response.json();
})
.then(data => {
console.log('Received data:', data);
const tbody = document.getElementById('unprinted-orders-table');
tbody.innerHTML = '';
data.forEach((order, index) => {
const tr = document.createElement('tr');
tr.dataset.orderId = order.id;
tr.dataset.orderIndex = index;
tr.style.cursor = 'pointer';
tr.innerHTML = `
<td style="font-size: 9px;">${order.id}</td>
<td style="font-size: 9px;"><strong>${order.comanda_productie}</strong></td>
<td style="font-size: 9px;">${order.cod_articol || '-'}</td>
<td style="font-size: 9px;">${order.descr_com_prod}</td>
<td style="text-align: right; font-weight: 600; font-size: 9px;">${order.cantitate}</td>
<td style="text-align: center; font-size: 9px;">
${order.data_livrare ? new Date(order.data_livrare).toLocaleDateString() : '-'}
</td>
<td style="text-align: center; font-size: 9px;">${order.dimensiune || '-'}</td>
<td style="font-size: 9px;">${order.com_achiz_client || '-'}</td>
<td style="text-align: right; font-size: 9px;">${order.nr_linie_com_client || '-'}</td>
<td style="font-size: 9px;">${order.customer_name || '-'}</td>
<td style="font-size: 9px;">${order.customer_article_number || '-'}</td>
<td style="font-size: 9px;">${order.open_for_order || '-'}</td>
<td style="text-align: right; font-size: 9px;">${order.line_number || '-'}</td>
<td style="text-align: center; font-size: 9px;">
${order.printed_labels == 1 ?
'<span style="color: #28a745; font-weight: bold;">&check; Yes</span>' :
'<span style="color: #dc3545;">&cross; No</span>'}
</td>
<td style="font-size: 9px; color: #6c757d;">
${order.created_at ? new Date(order.created_at).toLocaleString() : '-'}
</td>
`;
// Add click event for row selection
tr.addEventListener('click', function(event) {
console.log('Row clicked:', order.id);
// Handle multi-select with Ctrl/Cmd key
if (!event.ctrlKey && !event.metaKey) {
// Single selection - clear all others
document.querySelectorAll('.print-module-table tbody tr').forEach(row => {
row.classList.remove('selected');
const cells = row.querySelectorAll('td');
cells.forEach(cell => {
cell.style.backgroundColor = '';
cell.style.color = '';
});
});
}
// Toggle selection on this row
if (this.classList.contains('selected')) {
// Deselect
this.classList.remove('selected');
const cells = this.querySelectorAll('td');
cells.forEach(cell => {
cell.style.backgroundColor = '';
cell.style.color = '';
});
} else {
// Select
this.classList.add('selected');
const cells = this.querySelectorAll('td');
cells.forEach(cell => {
cell.style.backgroundColor = '#007bff';
cell.style.color = 'white';
});
}
// Update label preview with the first selected order
const selectedRows = document.querySelectorAll('.print-module-table tbody tr.selected');
if (selectedRows.length > 0) {
// Find the order data for the first selected row
const firstSelectedId = selectedRows[0].cells[0].textContent;
const selectedOrder = data.find(o => o.id == firstSelectedId);
if (selectedOrder) {
updateLabelPreview(selectedOrder);
}
}
// Update print button text to show selection count
updatePrintButtonText();
});
tbody.appendChild(tr);
});
// Update the label preview with first row by default
if (data.length > 0) {
updateLabelPreview(data[0]);
// Auto-select first row
setTimeout(() => {
const firstRow = document.querySelector('.print-module-table tbody tr');
if (firstRow) {
firstRow.classList.add('selected');
const cells = firstRow.querySelectorAll('td');
cells.forEach(cell => {
cell.style.backgroundColor = '#007bff';
cell.style.color = 'white';
});
}
}, 100);
}
})
.catch(error => {
console.error('Error fetching orders:', error);
alert('Error loading orders: ' + error.message);
})
.finally(() => {
button.textContent = 'Check Database';
button.disabled = false;
});
});
// Function to update label preview with selected order data
function updateLabelPreview(order) {
const customerName = order.customer_name || 'N/A';
document.getElementById('customer-name-row').textContent = customerName;
// Update quantity ordered value
const quantity = order.cantitate || '0';
document.getElementById('quantity-ordered-value').textContent = quantity;
// Update client order info (Com.Achiz.Client - Nr. Linie)
const comAchizClient = order.com_achiz_client || '';
const nrLinie = order.nr_linie_com_client || '';
const clientOrderInfo = comAchizClient && nrLinie ? `${comAchizClient}-${nrLinie}` : 'N/A';
document.getElementById('client-order-info').textContent = clientOrderInfo;
// Update vertical barcode with client order info or default
const verticalBarcodeData = clientOrderInfo !== 'N/A' ? clientOrderInfo : '000000-00';
document.getElementById('vertical-barcode-text').textContent = verticalBarcodeData;
// Update delivery date (using data_livrare column)
const deliveryDate = order.data_livrare ? new Date(order.data_livrare).toLocaleDateString() : 'N/A';
document.getElementById('delivery-date-value').textContent = deliveryDate;
// Update size (using dimensiune column)
const size = order.dimensiune || 'N/A';
document.getElementById('size-value').textContent = size;
// Update description (Descr. Com. Prod)
const description = order.descr_com_prod || 'N/A';
document.getElementById('description-value').textContent = description;
// Update article code (using customer_article_number column)
const articleCode = order.customer_article_number || 'N/A';
document.getElementById('article-code-value').textContent = articleCode;
// Update prod order (comanda_productie - cantitate)
const comandaProductie = order.comanda_productie || '';
const cantitate = order.cantitate || '';
const prodOrder = comandaProductie && cantitate ? `${comandaProductie}-${cantitate}` : 'N/A';
document.getElementById('prod-order-value').textContent = prodOrder;
// Update barcode with the same prod order data
document.getElementById('barcode-text').textContent = prodOrder;
}
// Initialize print service connection and load available printers
function initializePrintService() {
// Check service status and load printers
checkPrintServiceStatus()
.then(serviceStatus => {
if (serviceStatus) {
console.log('✅ Print service is available');
loadAvailablePrinters();
updateServiceStatusIndicator(true);
} else {
console.log('⚠️ Print service not available');
updateServiceStatusIndicator(false);
}
})
.catch(error => {
console.log('❌ Print service connection failed:', error);
updateServiceStatusIndicator(false);
});
}
// Load available printers from print service
function loadAvailablePrinters() {
fetch('http://localhost:8899/printers', {
method: 'GET',
mode: 'cors'
})
.then(response => response.json())
.then(data => {
const printerSelect = document.getElementById('printerSelect');
// Clear existing options except default ones
const defaultOptions = ['default', 'Epson TM-T20', 'Citizen CTS-310', 'custom'];
Array.from(printerSelect.options).forEach(option => {
if (!defaultOptions.includes(option.value)) {
option.remove();
}
});
// Add available printers
if (data.printers && data.printers.length > 0) {
data.printers.forEach(printer => {
// Don't add if already exists in default options
if (!defaultOptions.includes(printer.name)) {
const option = document.createElement('option');
option.value = printer.name;
option.textContent = `${printer.name}${printer.default ? ' (Default)' : ''}${printer.status !== 'ready' ? ' - Offline' : ''}`;
// Insert before "Other Printer..." option
const customOption = printerSelect.querySelector('option[value="custom"]');
printerSelect.insertBefore(option, customOption);
}
});
// Set default printer if available
const defaultPrinter = data.printers.find(p => p.default);
if (defaultPrinter) {
printerSelect.value = defaultPrinter.name;
}
}
console.log(`Loaded ${data.printers.length} printers from service`);
})
.catch(error => {
console.log('Could not load printers from service:', error);
});
}
// Update service status indicator
function updateServiceStatusIndicator(isAvailable) {
// Find or create status indicator
let statusIndicator = document.getElementById('service-status-indicator');
if (!statusIndicator) {
statusIndicator = document.createElement('div');
statusIndicator.id = 'service-status-indicator';
statusIndicator.style.cssText = 'font-size: 9px; color: #6c757d; margin-top: 5px; text-align: center;';
// Insert after printer selection
const printerSelection = document.getElementById('printerSelection');
printerSelection.appendChild(statusIndicator);
}
if (isAvailable) {
statusIndicator.innerHTML = '🟢 <strong>Print service connected</strong><br><small>Direct printing available</small>';
statusIndicator.style.color = '#28a745';
} else {
statusIndicator.innerHTML = '🔴 <strong>Print service offline</strong><br><small>Install and start the service for direct printing</small>';
statusIndicator.style.color = '#dc3545';
}
}
// PDF Generation System - Simplified to PDF only
function addPDFGenerationHandler() {
const printButton = document.getElementById('print-label-btn');
if (printButton) {
printButton.addEventListener('click', function(e) {
e.preventDefault();
// Get selected order
const selectedRow = document.querySelector('.print-module-table tbody tr.selected');
if (!selectedRow) {
alert('Please select an order first from the table below.');
return;
}
handlePDFGeneration(selectedRow);
});
}
}
// Check WCPP status on page load
async function checkWCPPStatusOnLoad() {
try {
const wcppPrinter = new WCPPThermalPrinter();
const isConnected = await wcppPrinter.checkWCPPStatus();
const statusElement = document.getElementById('wcpp-status');
if (isConnected) {
statusElement.textContent = 'Connected';
statusElement.className = 'badge badge-success';
} else {
statusElement.textContent = 'Not Running';
statusElement.className = 'badge badge-danger';
}
} catch (error) {
const statusElement = document.getElementById('wcpp-status');
statusElement.textContent = 'Error';
statusElement.className = 'badge badge-danger';
}
}
function updatePrintButtonText() {
const printButton = document.getElementById('print-label-btn');
const printMethod = document.querySelector('input[name="printMethod"]:checked').value;
const printerSelection = document.getElementById('printerSelection');
// Count selected rows
const selectedRows = document.querySelectorAll('.print-module-table tbody tr.selected');
const selectedCount = selectedRows.length;
let buttonText = '';
let buttonTitle = '';
if (printMethod === 'wcpp') {
buttonText = selectedCount > 0 ?
`🖨️ Print ${selectedCount} Label${selectedCount > 1 ? 's' : ''} (WCPP)` :
'🖨️ Print via WCPP';
buttonTitle = 'Print selected labels using WebClientPrint Processor';
printerSelection.style.display = 'block';
} else if (printMethod === 'direct') {
buttonText = selectedCount > 0 ?
`🖨️ Print ${selectedCount} Label${selectedCount > 1 ? 's' : ''} Directly` :
'🖨️ Print Directly';
buttonTitle = 'Print directly to thermal label printer';
printerSelection.style.display = 'block';
} else {
buttonText = selectedCount > 0 ?
`📄 Generate PDF (${selectedCount} item${selectedCount > 1 ? 's' : ''})` :
'📄 Generate PDF';
buttonTitle = 'Generate PDF with multiple labels based on quantity';
printerSelection.style.display = 'none';
}
printButton.innerHTML = buttonText;
printButton.title = buttonTitle;
printButton.disabled = selectedCount === 0 && printMethod !== 'pdf';
}
function handleDirectPrint(selectedRow) {
const orderId = selectedRow.dataset.orderId;
const quantityCell = selectedRow.querySelector('td:nth-child(5)');
const quantity = quantityCell ? parseInt(quantityCell.textContent) : 1;
const prodOrderCell = selectedRow.querySelector('td:nth-child(2)');
const prodOrder = prodOrderCell ? prodOrderCell.textContent.trim() : 'N/A';
// Get selected printer
const printerSelect = document.getElementById('printerSelect');
let printerName = printerSelect.value;
if (printerName === 'custom') {
printerName = prompt('Enter your printer name:');
if (!printerName) return;
} else if (printerName === 'default') {
printerName = ''; // Empty for default printer
}
console.log(`Direct printing to: ${printerName || 'default printer'}`);
// Extract order data from selected row
const cells = selectedRow.querySelectorAll('td');
const orderData = {
id: parseInt(orderId),
comanda_productie: cells[1].textContent.trim(),
cod_articol: cells[2].textContent.trim(),
descr_com_prod: cells[3].textContent.trim(),
cantitate: parseInt(cells[4].textContent.trim()),
data_livrare: cells[5].textContent.trim(),
dimensiune: cells[6].textContent.trim(),
com_achiz_client: cells[7].textContent.trim(),
nr_linie_com_client: cells[8].textContent.trim(),
customer_name: cells[9].textContent.trim(),
customer_article_number: cells[10].textContent.trim()
};
// Check for Electron thermal printing first (best option)
if (window.ThermalPrinter) {
console.log('🔥 Using Electron thermal printer');
handleElectronThermalPrint(orderData, printerName, quantity, prodOrder);
return;
}
// Fallback to Windows service (if available)
checkPrintServiceStatus()
.then(serviceStatus => {
if (serviceStatus) {
console.log('🖨️ Using Windows print service');
return handleServicePrint(orderId, printerName, quantity, prodOrder);
} else {
throw new Error('No print service available. Please install the Quality Print Desktop app or Windows service.');
}
})
.catch(error => {
console.error('Direct print error:', error);
let errorMessage = '❌ Direct printing failed: ' + error.message;
if (error.message.includes('service') || error.message.includes('available')) {
errorMessage += '\n\n💡 Solutions:\n' +
'1. Download and install Quality Print Desktop app (recommended)\n' +
'2. Install the Windows print service\n' +
'3. Use PDF generation as alternative';
}
alert(errorMessage);
});
}
// Handle Electron thermal printing (best option)
function handleElectronThermalPrint(orderData, printerName, quantity, prodOrder) {
const button = document.getElementById('print-label-btn');
const originalText = button.textContent;
button.textContent = 'Printing...';
button.disabled = true;
// Print each label sequentially
printSequentialLabels(orderData, printerName, quantity, 0)
.then(() => {
alert(`✅ Successfully printed ${quantity} labels!\n📊 Order: ${prodOrder}\n🖨️ Printer: ${printerName || 'Default Printer'}\n🔥 Using Electron Thermal Printing`);
// Update database status
updatePrintedStatus(orderData.id);
})
.catch(error => {
console.error('Electron thermal print error:', error);
alert(`❌ Thermal printing failed: ${error.message}\n\n💡 Try using PDF generation instead.`);
})
.finally(() => {
button.textContent = originalText;
button.disabled = false;
});
}
// Print labels sequentially for better reliability
async function printSequentialLabels(orderData, printerName, totalQuantity, currentIndex) {
if (currentIndex >= totalQuantity) {
return; // All labels printed
}
const labelNumber = currentIndex + 1;
const sequentialId = `${orderData.comanda_productie}-${labelNumber.toString().padStart(3, '0')}`;
// Generate label data for this specific label
const labelData = [
{
type: 'text',
value: 'INNOFA ROMANIA SRL',
style: { fontWeight: 'bold', fontSize: '12px', textAlign: 'center', marginBottom: '5px' }
},
{
type: 'text',
value: orderData.customer_name || 'N/A',
style: { fontSize: '11px', textAlign: 'center', marginBottom: '3px' }
},
{
type: 'text',
value: '─'.repeat(32),
style: { textAlign: 'center', fontSize: '8px', marginBottom: '2px' }
},
{
type: 'text',
value: `Quantity: ${orderData.cantitate || '0'}`,
style: { fontSize: '10px', marginBottom: '2px' }
},
{
type: 'text',
value: `Customer Order: ${orderData.com_achiz_client || 'N/A'}-${orderData.nr_linie_com_client || '00'}`,
style: { fontSize: '10px', marginBottom: '2px' }
},
{
type: 'text',
value: `Delivery: ${orderData.data_livrare || 'N/A'}`,
style: { fontSize: '10px', marginBottom: '2px' }
},
{
type: 'text',
value: `Size: ${orderData.dimensiune || 'N/A'}`,
style: { fontSize: '10px', marginBottom: '2px' }
},
{
type: 'text',
value: `Article: ${orderData.customer_article_number || 'N/A'}`,
style: { fontSize: '10px', marginBottom: '3px' }
},
{
type: 'text',
value: '─'.repeat(32),
style: { textAlign: 'center', fontSize: '8px', marginBottom: '3px' }
},
{
type: 'barCode',
value: sequentialId,
height: 40,
width: 2,
displayValue: true,
fontsize: 8,
style: { textAlign: 'center', marginBottom: '5px' }
},
{
type: 'text',
value: `Label ${labelNumber}/${totalQuantity}`,
style: { fontSize: '8px', textAlign: 'center', marginBottom: '5px' }
},
{
type: 'text',
value: ' ', // Add spacing
style: { fontSize: '6px' }
}
];
const printOptions = {
printerName: printerName || 'default',
pageSize: '80mm',
margin: '2mm 2mm 2mm 2mm',
copies: 1,
silent: true,
preview: false,
timeOutPerLine: 200
};
console.log(`🏷️ Printing label ${labelNumber}/${totalQuantity}: ${sequentialId}`);
try {
// Print this label
await window.ThermalPrinter.print(labelData, printOptions);
// Small delay between labels
await new Promise(resolve => setTimeout(resolve, 300));
// Print next label
return printSequentialLabels(orderData, printerName, totalQuantity, currentIndex + 1);
} catch (error) {
throw new Error(`Failed to print label ${labelNumber}: ${error.message}`);
}
}
// Handle Windows service printing (fallback)
function handleServicePrint(orderId, printerName, quantity, prodOrder) {
return generatePrintablePDF(orderId, true) // true for paper-saving mode
.then(pdfUrl => {
const printData = {
pdf_url: pdfUrl,
printer: printerName,
copies: 1,
order_id: parseInt(orderId),
paper_saving: true
};
return fetch('http://localhost:8899/print-pdf', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(printData)
});
})
.then(response => {
if (!response.ok) {
throw new Error(`Print service error: ${response.status}`);
}
return response.json();
})
.then(result => {
if (result.success) {
alert(`✅ Successfully sent ${quantity} labels to printer!\n📊 Order: ${prodOrder}\n<EFBFBD> Printer: ${printerName || 'Default'}\n📄 Job ID: ${result.job_id || 'N/A'}`);
// Update database status and refresh table
updatePrintedStatus(parseInt(orderId));
} else {
throw new Error(result.error || 'Unknown print error');
}
});
}
// Helper function to check print service status
function checkPrintServiceStatus() {
return fetch('http://localhost:8899/status', {
method: 'GET',
mode: 'cors'
})
.then(response => {
if (response.ok) {
return response.json().then(data => {
console.log('Print service status:', data);
return data.status === 'running';
});
}
return false;
})
.catch(error => {
console.log('Print service not available:', error.message);
return false;
});
}
// Helper function to generate PDF for printing
function generatePrintablePDF(orderId, paperSaving = true) {
return fetch(`/generate_labels_pdf/${orderId}/${paperSaving ? 'true' : 'false'}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
})
.then(response => {
if (!response.ok) {
throw new Error(`Failed to generate PDF: ${response.status}`);
}
return response.blob();
})
.then(blob => {
// Create a temporary URL for the PDF
return URL.createObjectURL(blob);
});
}
function handlePDFGeneration(selectedRow) {
const orderId = selectedRow.dataset.orderId;
const quantityCell = selectedRow.querySelector('td:nth-child(5)');
const quantity = quantityCell ? parseInt(quantityCell.textContent) : 1;
const prodOrderCell = selectedRow.querySelector('td:nth-child(2)');
const prodOrder = prodOrderCell ? prodOrderCell.textContent.trim() : 'N/A';
const button = document.getElementById('print-label-btn');
const originalText = button.textContent;
button.textContent = 'Generating PDF...';
button.disabled = true;
console.log(`Generating PDF for order ${orderId} with ${quantity} labels`);
// Always use paper-saving mode (optimized for thermal label printers)
const paperSavingMode = 'true';
console.log(`Using paper-saving mode for optimal label printing`);
// Generate PDF with paper-saving mode enabled
fetch(`/generate_labels_pdf/${orderId}/${paperSavingMode}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
})
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.blob();
})
.then(blob => {
// Create blob URL for PDF
const url = window.URL.createObjectURL(blob);
// Create download link for PDF
const a = document.createElement('a');
a.href = url;
a.download = `labels_${prodOrder}_${quantity}pcs.pdf`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
// Also open PDF in new tab for printing
const printWindow = window.open(url, '_blank');
if (printWindow) {
printWindow.focus();
// Wait for PDF to load, then show print dialog and cleanup
setTimeout(() => {
printWindow.print();
// Clean up blob URL after print dialog is shown
setTimeout(() => {
window.URL.revokeObjectURL(url);
}, 2000);
}, 1500);
} else {
// If popup was blocked, clean up immediately
setTimeout(() => {
window.URL.revokeObjectURL(url);
}, 1000);
}
// Show success message
alert(`✅ PDF generated successfully!\n📊 Order: ${prodOrder}\n📦 Labels: ${quantity} pieces\n\nThe PDF has been downloaded and opened for printing.\n\n📋 The order has been marked as printed and removed from the unprinted orders list.`);
// Refresh the orders table to reflect printed status
// This will automatically hide the printed order from the unprinted orders list
setTimeout(() => {
refreshUnprintedOrdersTable();
}, 1000);
})
.catch(error => {
console.error('Error generating PDF:', error);
alert('❌ Failed to generate PDF labels. Error: ' + error.message);
})
.finally(() => {
// Reset button state
button.textContent = originalText;
button.disabled = false;
});
}
function updatePrintedStatus(orderId) {
// Call backend to update printed status for direct printing
fetch(`/update_printed_status/${orderId}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
})
.then(response => response.json())
.then(data => {
console.log('Updated printed status:', data);
// Refresh table
setTimeout(() => {
refreshUnprintedOrdersTable();
}, 1000);
})
.catch(error => {
console.error('Error updating printed status:', error);
});
}
// Function to refresh the unprinted orders table
function refreshUnprintedOrdersTable() {
console.log('Refreshing unprinted orders table...');
const button = document.getElementById('check-db-btn');
// Show refreshing state
const originalText = button.textContent;
button.textContent = 'Refreshing...';
button.disabled = true;
fetch('/get_unprinted_orders')
.then(response => {
if (response.status === 403) {
return response.json().then(errorData => {
throw new Error(`Access Denied: ${errorData.error}`);
});
} else if (!response.ok) {
return response.text().then(text => {
throw new Error(`HTTP ${response.status}: ${text}`);
});
}
return response.json();
})
.then(data => {
console.log('Refreshed data:', data);
const tbody = document.getElementById('unprinted-orders-table');
tbody.innerHTML = '';
if (data.length === 0) {
// No unprinted orders left
tbody.innerHTML = '<tr><td colspan="15" style="text-align: center; padding: 20px; color: #28a745;"><strong>&check; All orders have been printed!</strong><br><small>No unprinted orders remaining.</small></td></tr>';
// Clear label preview
clearLabelPreview();
} else {
// Populate table with remaining unprinted orders
data.forEach((order, index) => {
const tr = document.createElement('tr');
tr.dataset.orderId = order.id;
tr.dataset.orderIndex = index;
tr.style.cursor = 'pointer';
tr.innerHTML = `
<td style="font-size: 9px;">${order.id}</td>
<td style="font-size: 9px;"><strong>${order.comanda_productie}</strong></td>
<td style="font-size: 9px;">${order.cod_articol || '-'}</td>
<td style="font-size: 9px;">${order.descr_com_prod}</td>
<td style="text-align: right; font-weight: 600; font-size: 9px;">${order.cantitate}</td>
<td style="text-align: center; font-size: 9px;">
${order.data_livrare ? new Date(order.data_livrare).toLocaleDateString() : '-'}
</td>
<td style="text-align: center; font-size: 9px;">${order.dimensiune || '-'}</td>
<td style="font-size: 9px;">${order.com_achiz_client || '-'}</td>
<td style="text-align: right; font-size: 9px;">${order.nr_linie_com_client || '-'}</td>
<td style="font-size: 9px;">${order.customer_name || '-'}</td>
<td style="font-size: 9px;">${order.customer_article_number || '-'}</td>
<td style="font-size: 9px;">${order.open_for_order || '-'}</td>
<td style="text-align: right; font-size: 9px;">${order.line_number || '-'}</td>
<td style="text-align: center; font-size: 9px;">
${order.printed_labels == 1 ?
'<span style="color: #28a745; font-weight: bold;">&check; Yes</span>' :
'<span style="color: #dc3545;">&cross; No</span>'}
</td>
<td style="font-size: 9px; color: #6c757d;">
${order.created_at ? new Date(order.created_at).toLocaleString() : '-'}
</td>
`;
// Add click event for row selection
tr.addEventListener('click', function() {
console.log('Row clicked:', order.id);
// Remove selection from other rows
document.querySelectorAll('.print-module-table tbody tr').forEach(row => {
row.classList.remove('selected');
const cells = row.querySelectorAll('td');
cells.forEach(cell => {
cell.style.backgroundColor = '';
cell.style.color = '';
});
});
// Select this row
this.classList.add('selected');
// Force visual selection with inline styles
const cells = this.querySelectorAll('td');
cells.forEach(cell => {
cell.style.backgroundColor = '#007bff';
cell.style.color = 'white';
});
// Update label preview with selected order data
updateLabelPreview(order);
});
tbody.appendChild(tr);
});
// Auto-select first row
setTimeout(() => {
const firstRow = document.querySelector('.print-module-table tbody tr');
if (firstRow && !firstRow.querySelector('td[colspan]')) { // Don't select if it's the "no data" row
firstRow.classList.add('selected');
const cells = firstRow.querySelectorAll('td');
cells.forEach(cell => {
cell.style.backgroundColor = '#007bff';
cell.style.color = 'white';
});
updateLabelPreview(data[0]);
}
}, 100);
}
})
.catch(error => {
console.error('Error refreshing orders:', error);
const tbody = document.getElementById('unprinted-orders-table');
tbody.innerHTML = '<tr><td colspan="15" style="text-align: center; padding: 20px; color: #dc3545;"><strong>&cross; Failed to refresh data</strong><br><small>' + error.message + '</small></td></tr>';
})
.finally(() => {
button.textContent = originalText;
button.disabled = false;
});
}
// Function to clear label preview when no orders are available
function clearLabelPreview() {
document.getElementById('customer-name-row').textContent = 'No orders available';
document.getElementById('quantity-ordered-value').textContent = '0';
document.getElementById('client-order-info').textContent = 'N/A';
document.getElementById('delivery-date-value').textContent = 'N/A';
document.getElementById('size-value').textContent = 'N/A';
document.getElementById('description-value').textContent = 'N/A';
document.getElementById('article-code-value').textContent = 'N/A';
document.getElementById('prod-order-value').textContent = 'N/A';
document.getElementById('barcode-text').textContent = 'N/A';
document.getElementById('vertical-barcode-text').textContent = '000000-00';
}
// Download Electron app function
function downloadElectronApp() {
// Download the Quality Print Desktop v1.0.0
const link = document.createElement('a');
link.href = '/download/desktop-app';
link.download = 'Quality_Print_Desktop_v1.0.0.zip';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
// Show success message
showNotification('📱 Quality Print Desktop v1.0.0 download started!', 'success');
}
function downloadPortableApp() {
// Download the portable Windows version
const link = document.createElement('a');
link.href = '/download/portable-app';
link.download = 'Quality_Print_Desktop_Portable_v1.0.0_Windows.zip';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
// Show success message
showNotification('💻 Portable Windows version download started!', 'success');
}
document.getElementById('print-label-btn').addEventListener('click', async function() {
const selectedMethod = document.querySelector('input[name="printMethod"]:checked').value;
const selectedPrinter = document.getElementById('printerSelect').value;
// Get selected orders from the table
const selectedRows = document.querySelectorAll('#unprinted-orders-table tr.selected');
if (selectedRows.length === 0) {
showNotification('⚠️ Please select at least one order to print', 'warning');
return;
}
const orderData = [];
selectedRows.forEach(row => {
const cells = row.getElementsByTagName('td');
orderData.push({
id: cells[0].textContent,
order_id: cells[1].textContent,
sku: cells[2].textContent,
description: cells[3].textContent,
quantity: cells[4].textContent,
delivery_date: cells[5].textContent,
size: cells[6].textContent,
customer_order: cells[7].textContent,
line_number: cells[8].textContent,
customer_name: cells[9].textContent,
customer_art_nr: cells[10].textContent,
barcode: cells[1].textContent + '-' + cells[2].textContent // Create barcode from order+sku
});
});
if (selectedMethod === 'wcpp') {
// Use WCPP for thermal printing
try {
showNotification('🔄 Checking WCPP connection...', 'info');
// Initialize WCPP printer with selected printer name
const wcppPrinter = new WCPPThermalPrinter();
wcppPrinter.printerName = selectedPrinter;
// Check WCPP status
const isConnected = await wcppPrinter.checkWCPPStatus();
if (!isConnected) {
showNotification('❌ WCPP not running. Please ensure WebClientPrint Processor is installed and running on your computer.', 'error');
return;
}
showNotification('✅ WCPP connected. Printing labels...', 'success');
// Print each selected order
let successCount = 0;
let errorCount = 0;
for (const order of orderData) {
try {
const result = await wcppPrinter.printOrder(order);
if (result.success) {
successCount++;
// Mark order as printed in the backend
await markOrderAsPrinted(order.id);
// Update the row in the table to show it's printed
const row = Array.from(selectedRows).find(r => r.cells[0].textContent === order.id);
if (row) {
row.cells[13].textContent = 'Yes';
row.classList.remove('selected');
}
} else {
errorCount++;
console.error('Print failed for order:', order.id, result.message);
}
} catch (error) {
errorCount++;
console.error('Print error for order:', order.id, error);
}
}
if (successCount > 0) {
showNotification(`🖨️ Successfully printed ${successCount} label(s)${errorCount > 0 ? ` (${errorCount} failed)` : ''}`, 'success');
} else {
showNotification(`❌ Failed to print any labels. Check printer connection.`, 'error');
}
} catch (error) {
console.error('WCPP Print error:', error);
showNotification('❌ WCPP Print error: ' + error.message, 'error');
}
} else if (selectedMethod === 'pdf') {
// Generate PDF logic
showNotification('📄 PDF generation feature coming soon', 'info');
} else {
// Direct print logic
showNotification('🖨️ Direct print feature coming soon', 'info');
}
});
// Function to mark order as printed
async function markOrderAsPrinted(orderId) {
try {
const response = await fetch('/mark_printed', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ order_id: orderId })
});
if (!response.ok) {
console.error('Failed to mark order as printed:', orderId);
}
} catch (error) {
console.error('Error marking order as printed:', error);
}
}
// Add WCPP Test Function
async function testWCPPConnection() {
try {
const wcppPrinter = new WCPPThermalPrinter();
wcppPrinter.printerName = document.getElementById('printerSelect').value;
showNotification('🔄 Testing WCPP connection...', 'info');
const isConnected = await wcppPrinter.checkWCPPStatus();
if (isConnected) {
const result = await wcppPrinter.testPrint();
if (result.success) {
showNotification('✅ WCPP Test successful! Check your printer.', 'success');
} else {
showNotification('❌ WCPP Test failed: ' + result.message, 'error');
}
} else {
showNotification('❌ WCPP not running. Please start WebClientPrint Processor.', 'error');
}
} catch (error) {
showNotification('❌ WCPP Test error: ' + error.message, 'error');
}
}
// Initialize WCPP status check on page load
setTimeout(checkWCPPStatusOnLoad, 1000);
</script>
{% endblock %}