1500 lines
67 KiB
HTML
1500 lines
67 KiB
HTML
{% extends "base.html" %}
|
||
|
||
{% block head %}
|
||
<style>
|
||
#label-preview {
|
||
background: #fafafa;
|
||
position: relative;
|
||
overflow: visible;
|
||
}
|
||
|
||
/* Enhanced table styling */
|
||
.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;
|
||
}
|
||
|
||
.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;
|
||
}
|
||
|
||
/* Print Progress Modal Styles */
|
||
|
||
</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>
|
||
|
||
<!-- Pairing Keys Section - Only show dropdown if multiple keys exist -->
|
||
<div style="width: 100%; text-align: center; margin-bottom: 15px;">
|
||
<!-- Dropdown for multiple keys (hidden by default) -->
|
||
<div id="client-select-container" style="display: none; margin-bottom: 8px;">
|
||
<label for="client-select" style="font-size: 11px; font-weight: 600; display: block; margin-bottom: 4px;">Select Printer/Client:</label>
|
||
<select id="client-select" class="form-control form-control-sm" style="width: 85%; margin: 0 auto; font-size: 11px;"></select>
|
||
</div>
|
||
<!-- Manage Keys Button -->
|
||
<a href="{{ url_for('main.download_extension') }}" class="btn btn-info btn-sm" target="_blank" style="font-size: 11px; padding: 4px 12px;">🔑 Manage Keys</a>
|
||
</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 ROMANIA 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 -->
|
||
<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>
|
||
<div style="position: absolute; top: 224.91px; left: 0; right: 0; height: 1px; background: #999;"></div>
|
||
<div style="position: absolute; top: 257.04px; left: 0; right: 0; height: 1px; background: #999;"></div>
|
||
<div style="position: absolute; top: 289.17px; left: 0; right: 0; height: 1px; background: #999;"></div>
|
||
|
||
<!-- Vertical dividing line -->
|
||
<div style="position: absolute; left: 90.96px; top: 64.26px; width: 1px; height: 257.04px; background: #999;"></div>
|
||
|
||
<!-- Row 3: 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: 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: 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: Description (double height) -->
|
||
<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;">
|
||
Product 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: 8px; color: #000; text-align: center; padding: 2px; overflow: hidden;">
|
||
<!-- Description will be populated here -->
|
||
</div>
|
||
|
||
<!-- Row 7: 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: 10px; font-weight: bold; color: #000;">
|
||
<!-- Size value will be populated here -->
|
||
</div>
|
||
|
||
<!-- Row 8: 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: 9px; font-weight: bold; color: #000;">
|
||
<!-- Article code will be populated here -->
|
||
</div>
|
||
|
||
<!-- Row 9: Production Order -->
|
||
<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: 10px; font-weight: bold; color: #000;">
|
||
<!-- Production order will be populated here -->
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Barcode Frame - positioned 10px below rectangle, centered, 90% of label width -->
|
||
<div id="barcode-frame" style="position: absolute; top: 395px; left: 50%; transform: translateX(-50%); width: 90%; max-width: 270px; height: 50px; background: white; display: flex; flex-direction: column; align-items: center; justify-content: center;">
|
||
<!-- Code 128 Barcode representation -->
|
||
<svg id="barcode-display" style="width: 100%; height: 40px;"></svg>
|
||
|
||
<!-- 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, rotated 90 degrees, spans the height of main rectangle -->
|
||
<div id="vertical-barcode-frame" style="position: absolute; top: 50px; left: 270px; width: 321.3px; height: 40px; background: white; display: flex; align-items: center; justify-content: center; transform: rotate(90deg); transform-origin: left center;">
|
||
<!-- Vertical Code 128 Barcode representation -->
|
||
<svg id="vertical-barcode-display" style="width: 100%; height: 35px;"></svg>
|
||
|
||
<!-- Vertical barcode text -->
|
||
<div id="vertical-barcode-text" style="position: absolute; bottom: -15px; font-size: 7px; font-family: 'Courier New', monospace; text-align: center; font-weight: bold; width: 100%;">
|
||
<!-- Vertical barcode text will be populated here -->
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Print Options -->
|
||
<div style="width: 100%; margin-top: 20px;">
|
||
<!-- Print Method Selection -->
|
||
<div style="margin-bottom: 15px;" role="group" aria-labelledby="print-method-label">
|
||
<div id="print-method-label" style="font-size: 12px; font-weight: 600; color: #495057; margin-bottom: 8px;">
|
||
📄 Print Method:
|
||
</div>
|
||
|
||
<div class="form-check" style="margin-bottom: 6px;">
|
||
<input class="form-check-input" type="radio" name="printMethod" id="qzTrayPrint" value="qztray" checked>
|
||
<label class="form-check-label" for="qzTrayPrint" style="font-size: 11px; line-height: 1.2;">
|
||
<strong>🖨️ Direct Print</strong> <span id="qztray-status" class="badge badge-success" style="font-size: 9px; padding: 2px 6px;">Ready</span>
|
||
</label>
|
||
</div>
|
||
|
||
<div class="form-check" id="pdf-option-container" style="display: none; margin-bottom: 6px;">
|
||
<input class="form-check-input" type="radio" name="printMethod" id="pdfGenerate" value="pdf">
|
||
<label class="form-check-label" for="pdfGenerate" style="font-size: 11px; line-height: 1.2;">
|
||
<strong>📄 PDF Export</strong> <span class="text-muted" style="font-size: 10px;">(fallback)</span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Printer Selection for QZ Tray (Compact) -->
|
||
<div id="qztray-printer-selection" style="margin-bottom: 10px;">
|
||
<label for="qztray-printer-select" style="font-size: 11px; font-weight: 600; color: #495057; margin-bottom: 3px; display: block;">
|
||
Printer:
|
||
</label>
|
||
<select id="qztray-printer-select" class="form-control form-control-sm" style="font-size: 11px; padding: 3px 6px;">
|
||
<option value="">Loading...</option>
|
||
</select>
|
||
</div>
|
||
|
||
<!-- Print Button -->
|
||
<div style="width: 100%; text-align: center; margin-bottom: 10px;">
|
||
<button id="print-label-btn" class="btn btn-success" style="font-size: 13px; padding: 8px 24px; border-radius: 5px; font-weight: 600;">
|
||
<20>️ Print Labels
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Print Information -->
|
||
<div style="width: 100%; text-align: center; color: #6c757d; font-size: 10px; line-height: 1.3;">
|
||
<small>(e.g., CP00000711-001, 002, ...)</small>
|
||
</div>
|
||
|
||
<!-- QZ Tray Installation Info - Simplified -->
|
||
<div id="qztray-info" style="width: 100%; margin-top: 15px; padding-top: 15px; border-top: 1px solid #e9ecef;">
|
||
<div style="background: #f8f9fa; border: 1px solid #dee2e6; border-radius: 6px; padding: 10px; text-align: center;">
|
||
<div style="font-size: 10px; color: #495057; margin-bottom: 8px;">
|
||
QZ Tray is required for direct printing
|
||
</div>
|
||
<a href="https://filebrowser.moto-adv.com/filebrowser/api/public/dl/Fk0ZaiEY/QP_Tray/qz-tray-2.2.6-SNAPSHOT-x86_64.exe?token=TJ7gSu3CRcWWQuyFLoZv5I8j4diDjP47DDqWRtM0oKAx-2_orj1stfKPJsuuqKR9mE2GQNm1jlZ0BPR7lfZ3gHmu56SkY9fC5AJlC9n_80oX643ojlGc-U7XVb1SDd0w" class="btn btn-outline-secondary btn-sm" style="font-size: 10px; padding: 4px 16px;">
|
||
📥 Download QZ Tray
|
||
</a>
|
||
</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">Load Orders</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>
|
||
|
||
<!-- JavaScript Libraries -->
|
||
<!-- JsBarcode library for real barcode generation -->
|
||
<script src="https://cdn.jsdelivr.net/npm/jsbarcode@3.11.5/dist/JsBarcode.all.min.js"></script>
|
||
<!-- Add html2canvas library for capturing preview as image -->
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
|
||
<!-- PATCHED QZ Tray library - works with custom server using pairing key authentication -->
|
||
<script src="{{ url_for('static', filename='qz-tray.js') }}"></script>
|
||
<!-- Original CDN version (disabled): <script src="https://cdn.jsdelivr.net/npm/qz-tray@2.2.4/qz-tray.js"></script> -->
|
||
|
||
<script>
|
||
// Simplified notification system
|
||
function showNotification(message, type = 'info') {
|
||
const existingNotifications = document.querySelectorAll('.notification');
|
||
existingNotifications.forEach(n => n.remove());
|
||
|
||
const notification = document.createElement('div');
|
||
notification.className = `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: 450px;
|
||
padding: 15px;
|
||
border-radius: 5px;
|
||
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
||
`;
|
||
|
||
// Convert newlines to <br> tags for proper display
|
||
const formattedMessage = message.replace(/\n/g, '<br>');
|
||
|
||
notification.innerHTML = `
|
||
<div style="display: flex; align-items: flex-start; justify-content: space-between;">
|
||
<span style="flex: 1; padding-right: 10px; white-space: pre-wrap; font-family: monospace; font-size: 12px;">${formattedMessage}</span>
|
||
<button type="button" onclick="this.parentElement.parentElement.remove()" style="background: none; border: none; font-size: 20px; cursor: pointer; flex-shrink: 0;">×</button>
|
||
</div>
|
||
`;
|
||
|
||
document.body.appendChild(notification);
|
||
|
||
// Longer timeout for error messages
|
||
const timeout = type === 'error' ? 15000 : 5000;
|
||
setTimeout(() => {
|
||
if (notification.parentElement) {
|
||
notification.remove();
|
||
}
|
||
}, timeout);
|
||
}
|
||
|
||
// Wait for DOM to be ready before attaching event listeners
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
console.log('🚀 DOM Content Loaded - Initializing page...');
|
||
|
||
// Database loading functionality
|
||
document.getElementById('check-db-btn').addEventListener('click', function() {
|
||
const button = this;
|
||
const originalText = button.textContent;
|
||
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 = '';
|
||
|
||
if (data.length === 0) {
|
||
tbody.innerHTML = '<tr><td colspan="15" style="text-align: center; padding: 20px; color: #28a745;"><strong>✅ All orders have been printed!</strong><br><small>No unprinted orders remaining.</small></td></tr>';
|
||
clearLabelPreview();
|
||
return;
|
||
}
|
||
|
||
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;">✅ Yes</span>' :
|
||
'<span style="color: #dc3545;">❌ No</span>'}
|
||
</td>
|
||
<td style="font-size: 9px; color: #6c757d;">
|
||
${order.created_at ? new Date(order.created_at).toLocaleString() : '-'}
|
||
</td>
|
||
`;
|
||
|
||
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');
|
||
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]')) {
|
||
firstRow.click();
|
||
}
|
||
}, 100);
|
||
|
||
showNotification(`✅ Loaded ${data.length} unprinted orders`, 'success');
|
||
})
|
||
.catch(error => {
|
||
console.error('Error loading orders:', error);
|
||
const tbody = document.getElementById('unprinted-orders-table');
|
||
tbody.innerHTML = '<tr><td colspan="15" style="text-align: center; padding: 20px; color: #dc3545;"><strong>❌ Failed to load data</strong><br><small>' + error.message + '</small></td></tr>';
|
||
showNotification('❌ Failed to load orders: ' + error.message, 'error');
|
||
})
|
||
.finally(() => {
|
||
button.textContent = originalText;
|
||
button.disabled = false;
|
||
});
|
||
});
|
||
|
||
// Functions that don't need DOM elements (can be outside DOMContentLoaded)
|
||
|
||
// Update label preview with 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 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 cod_articol column)
|
||
const articleCode = order.cod_articol || '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 horizontal barcode with CP format (e.g., CP00000711/001)
|
||
// Show the first piece number (001) in preview
|
||
const horizontalBarcodeData = comandaProductie ? `${comandaProductie}/001` : 'N/A';
|
||
document.getElementById('barcode-text').textContent = horizontalBarcodeData;
|
||
|
||
// Generate horizontal barcode visual using JsBarcode
|
||
console.log('🔍 Attempting to generate horizontal barcode:', horizontalBarcodeData);
|
||
console.log('🔍 JsBarcode available?', typeof JsBarcode !== 'undefined');
|
||
|
||
if (horizontalBarcodeData !== 'N/A' && typeof JsBarcode !== 'undefined') {
|
||
try {
|
||
const barcodeElement = document.querySelector("#barcode-display");
|
||
console.log('🔍 Horizontal barcode element:', barcodeElement);
|
||
|
||
JsBarcode("#barcode-display", horizontalBarcodeData, {
|
||
format: "CODE128",
|
||
width: 2,
|
||
height: 40,
|
||
displayValue: false,
|
||
margin: 2
|
||
});
|
||
console.log('✅ Horizontal barcode generated successfully');
|
||
} catch (e) {
|
||
console.error('❌ Failed to generate horizontal barcode:', e);
|
||
}
|
||
} else {
|
||
console.warn('⚠️ Skipping horizontal barcode generation:',
|
||
horizontalBarcodeData === 'N/A' ? 'No data' : 'JsBarcode not loaded');
|
||
}
|
||
|
||
// Update vertical barcode with client order format (e.g., Abcderd/65)
|
||
const verticalBarcodeData = comAchizClient && nrLinie ? `${comAchizClient}/${nrLinie}` : '000000/00';
|
||
document.getElementById('vertical-barcode-text').textContent = verticalBarcodeData;
|
||
|
||
// Generate vertical barcode visual using JsBarcode (will be rotated by CSS)
|
||
console.log('🔍 Attempting to generate vertical barcode:', verticalBarcodeData);
|
||
|
||
if (verticalBarcodeData !== '000000/00' && typeof JsBarcode !== 'undefined') {
|
||
try {
|
||
const verticalElement = document.querySelector("#vertical-barcode-display");
|
||
console.log('🔍 Vertical barcode element:', verticalElement);
|
||
|
||
JsBarcode("#vertical-barcode-display", verticalBarcodeData, {
|
||
format: "CODE128",
|
||
width: 1.5,
|
||
height: 35,
|
||
displayValue: false,
|
||
margin: 2
|
||
});
|
||
console.log('✅ Vertical barcode generated successfully');
|
||
} catch (e) {
|
||
console.error('❌ Failed to generate vertical barcode:', e);
|
||
}
|
||
} else {
|
||
console.warn('⚠️ Skipping vertical barcode generation:',
|
||
verticalBarcodeData === '000000/00' ? 'Default value' : 'JsBarcode not loaded');
|
||
}
|
||
}
|
||
|
||
// 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';
|
||
|
||
// Clear barcode SVGs
|
||
const horizontalBarcode = document.getElementById('barcode-display');
|
||
const verticalBarcode = document.getElementById('vertical-barcode-display');
|
||
if (horizontalBarcode) horizontalBarcode.innerHTML = '';
|
||
if (verticalBarcode) verticalBarcode.innerHTML = '';
|
||
}
|
||
|
||
// QZ Tray Integration
|
||
let qzTray = null;
|
||
let availablePrinters = [];
|
||
|
||
// Initialize QZ Tray connection
|
||
async function initializeQZTray() {
|
||
try {
|
||
console.log('🔍 Checking for QZ Tray...');
|
||
|
||
// Check if QZ Tray library is loaded
|
||
if (typeof qz === 'undefined') {
|
||
console.error('❌ QZ Tray library not loaded');
|
||
const errorMsg = '❌ QZ Tray Library Not Loaded\n\n' +
|
||
'The patched QZ Tray JavaScript library failed to load.\n' +
|
||
'Check the browser console for errors and refresh the page.\n\n' +
|
||
'Path: /static/qz-tray.js (patched version for pairing key auth)';
|
||
document.getElementById('qztray-status').textContent = 'Library Error';
|
||
document.getElementById('qztray-status').className = 'badge badge-danger';
|
||
showNotification(errorMsg, 'error');
|
||
return false;
|
||
}
|
||
|
||
console.log('✅ QZ Tray library loaded');
|
||
|
||
// Using PATCHED qz-tray.js that works with our custom QZ Tray server
|
||
// The patched library automatically skips certificate validation
|
||
// Our custom server uses ONLY pairing key (HMAC) authentication
|
||
console.log('🔒 Using patched qz-tray.js for pairing-key authentication...');
|
||
|
||
// No security setup needed - the patched library handles it
|
||
// Original qz-tray.js required setCertificatePromise, but our patch bypasses it
|
||
|
||
console.log('✅ Ready to connect to custom QZ Tray server');
|
||
console.log('🔌 Attempting to connect to QZ Tray on client PC...');
|
||
console.log('📍 Will try: ws://localhost:8181 (insecure) then wss://localhost:8182 (secure)');
|
||
|
||
// Set connection closed callback
|
||
qz.websocket.setClosedCallbacks(function() {
|
||
console.warn('⚠️ QZ Tray connection closed');
|
||
document.getElementById('qztray-status').textContent = 'Disconnected';
|
||
document.getElementById('qztray-status').className = 'badge badge-warning';
|
||
});
|
||
|
||
// Connect to QZ Tray running on client PC
|
||
console.log('⏳ Connecting...');
|
||
await qz.websocket.connect();
|
||
qzTray = qz;
|
||
|
||
const version = await qz.api.getVersion();
|
||
console.log('✅ QZ Tray connected successfully');
|
||
console.log('📋 QZ Tray Version:', version);
|
||
|
||
// Update status
|
||
document.getElementById('qztray-status').textContent = 'Ready';
|
||
document.getElementById('qztray-status').className = 'badge badge-success';
|
||
|
||
// Show printer selection, hide PDF option
|
||
document.getElementById('qztray-printer-selection').style.display = 'block';
|
||
document.getElementById('pdf-option-container').style.display = 'none';
|
||
|
||
// Load available printers
|
||
await loadQZTrayPrinters();
|
||
|
||
showNotification(`🖨️ QZ Tray v${version} connected successfully!`, 'success');
|
||
|
||
return true;
|
||
} catch (error) {
|
||
console.error('❌ QZ Tray connection failed:', error);
|
||
console.error('Error details:', {
|
||
message: error.message,
|
||
type: typeof error,
|
||
errorName: error.name,
|
||
stack: error.stack
|
||
});
|
||
|
||
// Detailed error messages based on actual error
|
||
let errorMsg = '❌ Cannot Connect to QZ Tray\n\n';
|
||
let statusText = 'Not Connected';
|
||
|
||
const errorStr = error.toString().toLowerCase();
|
||
const messageStr = (error.message || '').toLowerCase();
|
||
|
||
if (messageStr.includes('unable to establish') ||
|
||
errorStr.includes('unable to establish') ||
|
||
messageStr.includes('failed to connect') ||
|
||
errorStr.includes('websocket') ||
|
||
messageStr.includes('econnrefused')) {
|
||
|
||
errorMsg += '🔌 Connection Refused\n\n';
|
||
errorMsg += 'QZ Tray is not responding on this computer.\n\n';
|
||
errorMsg += '✅ Steps to fix:\n';
|
||
errorMsg += '1. Check if QZ Tray is installed on THIS computer\n';
|
||
errorMsg += '2. Look for QZ Tray icon in system tray (bottom-right)\n';
|
||
errorMsg += '3. If not running, start QZ Tray application\n';
|
||
errorMsg += '4. If installed but not working, restart QZ Tray\n';
|
||
errorMsg += '5. Download from: https://qz.io/download/\n\n';
|
||
errorMsg += '🔍 Technical: Trying to connect to ports 8181/8182';
|
||
statusText = 'Not Running';
|
||
|
||
} else if (messageStr.includes('certificate') ||
|
||
errorStr.includes('certificate') ||
|
||
messageStr.includes('security') ||
|
||
messageStr.includes('ssl') ||
|
||
messageStr.includes('tls')) {
|
||
|
||
errorMsg += '🔒 Certificate Security Issue\n\n';
|
||
errorMsg += 'QZ Tray uses a self-signed certificate for security.\n\n';
|
||
errorMsg += '✅ Steps to fix:\n';
|
||
errorMsg += '1. Open new tab: https://localhost:8182\n';
|
||
errorMsg += '2. Accept/Trust the security certificate\n';
|
||
errorMsg += '3. Come back and click "Reconnect to QZ Tray"\n\n';
|
||
errorMsg += '🔍 This is normal and safe for QZ Tray';
|
||
statusText = 'Certificate Error';
|
||
|
||
} else {
|
||
errorMsg += '⚠️ Unexpected Error\n\n';
|
||
errorMsg += 'Error: ' + error.message + '\n\n';
|
||
errorMsg += '🔍 Troubleshooting:\n';
|
||
errorMsg += '1. Open browser console (F12) for details\n';
|
||
errorMsg += '2. Click "Test Connection" for diagnostics\n';
|
||
errorMsg += '3. Make sure QZ Tray is running\n';
|
||
errorMsg += '4. Try restarting your browser';
|
||
}
|
||
|
||
document.getElementById('qztray-status').textContent = statusText;
|
||
document.getElementById('qztray-status').className = 'badge badge-danger';
|
||
|
||
// QZ Tray not available - show PDF option as fallback
|
||
document.getElementById('qztray-printer-selection').style.display = 'none';
|
||
document.getElementById('pdf-option-container').style.display = 'block';
|
||
|
||
// Switch to PDF method since QZ Tray is unavailable
|
||
document.getElementById('pdfGenerate').checked = true;
|
||
document.getElementById('qzTrayPrint').disabled = true;
|
||
|
||
showNotification(errorMsg, 'error');
|
||
return false;
|
||
}
|
||
}
|
||
|
||
// Manual test connection function
|
||
async function testQZConnection() {
|
||
console.log('🔍 ========== QZ TRAY CONNECTION TEST ==========');
|
||
|
||
const statusElement = document.getElementById('qztray-status');
|
||
const originalStatus = statusElement.textContent;
|
||
statusElement.textContent = 'Testing...';
|
||
statusElement.className = 'badge badge-warning';
|
||
|
||
let testResults = [];
|
||
|
||
try {
|
||
// Test 1: Check if library is loaded
|
||
console.log('Test 1: Checking library...');
|
||
if (typeof qz === 'undefined') {
|
||
testResults.push('❌ Test 1: Library NOT loaded from CDN');
|
||
throw new Error('QZ Tray library not loaded from CDN');
|
||
}
|
||
testResults.push('✅ Test 1: Library loaded successfully');
|
||
console.log('✅ Test 1 PASSED: Library loaded from local (patched version)');
|
||
|
||
// Using patched qz-tray.js - no security setup needed
|
||
console.log('🔒 Using patched library for custom QZ Tray...');
|
||
|
||
// Test 2: Check if already connected
|
||
console.log('Test 2: Checking existing connection...');
|
||
if (qz.websocket.isActive()) {
|
||
testResults.push('✅ Test 2: Already connected!');
|
||
console.log('✅ Test 2 PASSED: Already connected');
|
||
const version = await qz.api.getVersion();
|
||
testResults.push(`✅ Version: ${version}`);
|
||
console.log(`📋 QZ Tray Version: ${version}`);
|
||
|
||
const printers = await qz.printers.find();
|
||
testResults.push(`✅ Found ${printers.length} printer(s)`);
|
||
console.log(`🖨️ Printers found: ${printers.length}`);
|
||
|
||
showNotification(testResults.join('\n'), 'success');
|
||
statusElement.textContent = 'Connected';
|
||
statusElement.className = 'badge badge-success';
|
||
console.log('========== TEST COMPLETED: ALL PASSED ==========');
|
||
return;
|
||
}
|
||
testResults.push('⚠️ Test 2: Not currently connected');
|
||
console.log('⚠️ Test 2: Not connected, will attempt connection...');
|
||
|
||
// Test 3: Try to connect
|
||
console.log('Test 3: Attempting WebSocket connection...');
|
||
testResults.push('🔌 Test 3: Connecting to QZ Tray...');
|
||
console.log('🔌 Connecting to WebSocket...');
|
||
|
||
await qz.websocket.connect();
|
||
testResults.push('✅ Test 3: WebSocket connected!');
|
||
console.log('✅ Test 3 PASSED: WebSocket connected');
|
||
|
||
// Test 4: Get version
|
||
console.log('Test 4: Getting QZ Tray version...');
|
||
const version = await qz.api.getVersion();
|
||
testResults.push(`✅ Test 4: QZ Tray v${version}`);
|
||
console.log(`✅ Test 4 PASSED: Version ${version}`);
|
||
|
||
// Test 5: List printers
|
||
console.log('Test 5: Fetching printer list...');
|
||
const printers = await qz.printers.find();
|
||
testResults.push(`✅ Test 5: Found ${printers.length} printer(s)`);
|
||
console.log(`✅ Test 5 PASSED: ${printers.length} printers found`);
|
||
|
||
if (printers.length > 0) {
|
||
console.log('📋 Available printers:', printers);
|
||
testResults.push('📋 Printers: ' + printers.slice(0, 3).join(', ') + (printers.length > 3 ? '...' : ''));
|
||
}
|
||
|
||
// Success!
|
||
qzTray = qz;
|
||
statusElement.textContent = 'Connected';
|
||
statusElement.className = 'badge badge-success';
|
||
|
||
console.log('========== TEST COMPLETED: ALL PASSED ==========');
|
||
showNotification(testResults.join('\n') + '\n\n✅ All tests passed!', 'success');
|
||
|
||
// Reload printers
|
||
await loadQZTrayPrinters();
|
||
|
||
} catch (error) {
|
||
console.error('❌ TEST FAILED:', error);
|
||
console.error('Error type:', typeof error);
|
||
console.error('Error name:', error.name);
|
||
console.error('Error message:', error.message);
|
||
console.error('Error stack:', error.stack);
|
||
|
||
statusElement.textContent = originalStatus;
|
||
statusElement.className = 'badge badge-danger';
|
||
|
||
const errorStr = error.toString().toLowerCase();
|
||
const messageStr = (error.message || '').toLowerCase();
|
||
|
||
let errorMsg = '❌ Connection Test Results:\n\n';
|
||
errorMsg += testResults.join('\n') + '\n\n';
|
||
|
||
if (typeof qz === 'undefined') {
|
||
errorMsg += '❌ FAILED: Library Load Error\n\n';
|
||
errorMsg += '📚 The QZ Tray JavaScript library did not load.\n\n';
|
||
errorMsg += 'Fix:\n';
|
||
errorMsg += '• Check browser console for errors (F12)\n';
|
||
errorMsg += '• Refresh the page (F5)\n';
|
||
errorMsg += '• Check if library loaded:\n';
|
||
errorMsg += ' /static/qz-tray.js (patched version)';
|
||
|
||
} else if (messageStr.includes('unable to establish') ||
|
||
errorStr.includes('unable to establish') ||
|
||
messageStr.includes('failed to connect') ||
|
||
messageStr.includes('websocket') ||
|
||
errorStr.includes('websocket error')) {
|
||
|
||
errorMsg += '❌ FAILED: Cannot Connect to QZ Tray\n\n';
|
||
errorMsg += '🔌 QZ Tray is not running on this computer.\n\n';
|
||
errorMsg += 'Fix:\n';
|
||
errorMsg += '1. Check if QZ Tray is installed\n';
|
||
errorMsg += '2. Look in system tray (bottom-right) for QZ icon\n';
|
||
errorMsg += '3. If not there, launch QZ Tray app\n';
|
||
errorMsg += '4. Download: https://qz.io/download/\n\n';
|
||
errorMsg += '💡 QZ Tray must be running on THIS computer,\n';
|
||
errorMsg += ' not on the server!';
|
||
|
||
} else if (messageStr.includes('certificate') ||
|
||
errorStr.includes('certificate') ||
|
||
messageStr.includes('security')) {
|
||
|
||
errorMsg += '❌ FAILED: Certificate Error\n\n';
|
||
errorMsg += '🔒 Browser security blocking connection.\n\n';
|
||
errorMsg += 'Fix:\n';
|
||
errorMsg += '1. Open: https://localhost:8182\n';
|
||
errorMsg += '2. Click "Advanced" or "Proceed"\n';
|
||
errorMsg += '3. Accept the certificate\n';
|
||
errorMsg += '4. Return here and test again\n\n';
|
||
errorMsg += '💡 Self-signed certificates are normal for QZ Tray';
|
||
|
||
} else {
|
||
errorMsg += '❌ FAILED: Unexpected Error\n\n';
|
||
errorMsg += 'Error: ' + error.message + '\n\n';
|
||
errorMsg += 'Next steps:\n';
|
||
errorMsg += '1. Check browser console (F12)\n';
|
||
errorMsg += '2. Verify QZ Tray is running\n';
|
||
errorMsg += '3. Restart browser and QZ Tray\n';
|
||
errorMsg += '4. Check firewall settings';
|
||
}
|
||
|
||
console.log('========== TEST COMPLETED: FAILED ==========');
|
||
showNotification(errorMsg, 'error');
|
||
}
|
||
}
|
||
|
||
// Load available printers from QZ Tray
|
||
async function loadQZTrayPrinters() {
|
||
try {
|
||
if (!qzTray) return;
|
||
|
||
const printers = await qzTray.printers.find();
|
||
availablePrinters = printers;
|
||
|
||
const printerSelect = document.getElementById('qztray-printer-select');
|
||
printerSelect.innerHTML = '<option value="">Select a printer...</option>';
|
||
|
||
printers.forEach(printer => {
|
||
const option = document.createElement('option');
|
||
option.value = printer;
|
||
option.textContent = printer;
|
||
printerSelect.appendChild(option);
|
||
});
|
||
|
||
console.log(`📄 Found ${printers.length} printers:`, printers);
|
||
|
||
// Auto-select first thermal printer if available
|
||
const thermalPrinter = printers.find(p =>
|
||
p.toLowerCase().includes('thermal') ||
|
||
p.toLowerCase().includes('label') ||
|
||
p.toLowerCase().includes('zebra') ||
|
||
p.toLowerCase().includes('epson')
|
||
);
|
||
|
||
if (thermalPrinter) {
|
||
printerSelect.value = thermalPrinter;
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error('Error loading printers:', error);
|
||
showNotification('⚠️ Failed to load printers: ' + error.message, 'warning');
|
||
}
|
||
}
|
||
|
||
// Generate PDF and send to thermal printer
|
||
async function generatePDFAndPrint(selectedPrinter, orderData, pieceNumber, totalPieces) {
|
||
try {
|
||
console.log(`📄 Generating PDF for thermal printing... (${pieceNumber}/${totalPieces})`);
|
||
|
||
// Prepare data for PDF generation
|
||
const pdfData = {
|
||
...orderData,
|
||
quantity: 1, // Single label per print job
|
||
piece_number: pieceNumber,
|
||
total_pieces: totalPieces
|
||
};
|
||
|
||
// Call backend to generate PDF
|
||
const response = await fetch('/generate_label_pdf', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify(pdfData)
|
||
});
|
||
|
||
if (!response.ok) {
|
||
const errorData = await response.json();
|
||
throw new Error(errorData.error || 'Failed to generate PDF');
|
||
}
|
||
|
||
// Get PDF as blob
|
||
const pdfBlob = await response.blob();
|
||
|
||
// Convert blob to base64 for QZ Tray
|
||
const pdfBase64 = await new Promise((resolve, reject) => {
|
||
const reader = new FileReader();
|
||
reader.onloadend = () => {
|
||
// Remove data:application/pdf;base64, prefix
|
||
const base64 = reader.result.split(',')[1];
|
||
resolve(base64);
|
||
};
|
||
reader.onerror = reject;
|
||
reader.readAsDataURL(pdfBlob);
|
||
});
|
||
|
||
console.log(`🖨️ Sending PDF to printer ${selectedPrinter}...`);
|
||
|
||
// Configure QZ Tray for PDF printing
|
||
const config = qz.configs.create(selectedPrinter, {
|
||
scaleContent: false,
|
||
rasterize: false
|
||
});
|
||
|
||
// Prepare PDF data for QZ Tray
|
||
const data = [{
|
||
type: 'pdf',
|
||
format: 'base64',
|
||
data: pdfBase64
|
||
}];
|
||
|
||
await qz.print(config, data);
|
||
console.log(`✅ PDF sent to printer successfully (${pieceNumber}/${totalPieces})`);
|
||
|
||
} catch (error) {
|
||
console.error('❌ Error generating/printing PDF:', error);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
// Helper function to convert array buffer to base64
|
||
function arrayBufferToBase64(buffer) {
|
||
const bytes = new Uint8Array(buffer);
|
||
const binary = bytes.reduce((data, byte) => data + String.fromCharCode(byte), '');
|
||
return btoa(binary);
|
||
}
|
||
|
||
// Helper function to convert blob to base64 (kept for compatibility)
|
||
function blobToBase64(blob) {
|
||
return new Promise((resolve, reject) => {
|
||
const reader = new FileReader();
|
||
reader.onload = () => {
|
||
const base64 = reader.result.split(',')[1]; // Remove data URL prefix
|
||
resolve(base64);
|
||
};
|
||
reader.onerror = reject;
|
||
reader.readAsDataURL(blob);
|
||
});
|
||
}
|
||
|
||
// Update preview with order data for specific piece
|
||
function updatePreview(orderData, pieceNumber, totalPieces) {
|
||
// Extract and format the data
|
||
const {
|
||
customer_name = 'N/A',
|
||
cantitate = '0',
|
||
com_achiz_client = '',
|
||
nr_linie_com_client = '',
|
||
data_livrare = null,
|
||
dimensiune = 'N/A',
|
||
descr_com_prod = 'N/A',
|
||
cod_articol = 'N/A',
|
||
comanda_productie = ''
|
||
} = orderData;
|
||
|
||
const deliveryDate = data_livrare ? new Date(data_livrare).toLocaleDateString() : 'N/A';
|
||
const clientOrder = com_achiz_client && nr_linie_com_client ? `${com_achiz_client}-${nr_linie_com_client}` : 'N/A';
|
||
const horizontalBarcode = comanda_productie ? `${comanda_productie}-${String(pieceNumber).padStart(3, '0')}` : 'N/A';
|
||
const verticalBarcode = clientOrder;
|
||
const prodOrder = comanda_productie && cantitate ? `${comanda_productie}-${cantitate}` : 'N/A';
|
||
|
||
// Update preview elements with correct IDs
|
||
const customerRowElement = document.getElementById('customer-name-row');
|
||
if (customerRowElement) customerRowElement.textContent = customer_name;
|
||
|
||
const quantityElement = document.getElementById('quantity-ordered-value');
|
||
if (quantityElement) quantityElement.textContent = cantitate;
|
||
|
||
const clientOrderElement = document.getElementById('client-order-info');
|
||
if (clientOrderElement) clientOrderElement.textContent = clientOrder;
|
||
|
||
const deliveryDateElement = document.getElementById('delivery-date-value');
|
||
if (deliveryDateElement) deliveryDateElement.textContent = deliveryDate;
|
||
|
||
const descriptionElement = document.getElementById('description-value');
|
||
if (descriptionElement) descriptionElement.textContent = descr_com_prod;
|
||
|
||
const sizeElement = document.getElementById('size-value');
|
||
if (sizeElement) sizeElement.textContent = dimensiune;
|
||
|
||
const articleCodeElement = document.getElementById('article-code-value');
|
||
if (articleCodeElement) articleCodeElement.textContent = cod_articol;
|
||
|
||
const prodOrderElement = document.getElementById('prod-order-value');
|
||
if (prodOrderElement) prodOrderElement.textContent = prodOrder;
|
||
|
||
const barcodeTextElement = document.getElementById('barcode-text');
|
||
if (barcodeTextElement) barcodeTextElement.textContent = horizontalBarcode;
|
||
|
||
const verticalBarcodeTextElement = document.getElementById('vertical-barcode-text');
|
||
if (verticalBarcodeTextElement) verticalBarcodeTextElement.textContent = verticalBarcode;
|
||
}
|
||
function generateHTMLLabel(orderData, pieceNumber, totalPieces) {
|
||
const {
|
||
customer_name = 'N/A',
|
||
cantitate = '0',
|
||
com_achiz_client = '',
|
||
nr_linie_com_client = '',
|
||
data_livrare = null,
|
||
dimensiune = 'N/A',
|
||
descr_com_prod = 'N/A',
|
||
cod_articol = 'N/A',
|
||
comanda_productie = ''
|
||
} = orderData;
|
||
|
||
// Format data for label (matching preview format)
|
||
const deliveryDate = data_livrare ? new Date(data_livrare).toLocaleDateString() : 'N/A';
|
||
const clientOrder = com_achiz_client && nr_linie_com_client ? `${com_achiz_client}-${nr_linie_com_client}` : 'N/A';
|
||
const horizontalBarcode = comanda_productie ? `${comanda_productie}-${String(pieceNumber).padStart(3, '0')}` : 'N/A';
|
||
const verticalBarcode = clientOrder;
|
||
const prodOrder = comanda_productie && cantitate ? `${comanda_productie}-${cantitate}` : 'N/A';
|
||
|
||
const htmlContent = `
|
||
<!DOCTYPE html>
|
||
<html>
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<style>
|
||
@page {
|
||
size: 80mm 110mm;
|
||
margin: 0;
|
||
}
|
||
* {
|
||
box-sizing: border-box;
|
||
}
|
||
body {
|
||
margin: 0;
|
||
padding: 0;
|
||
font-family: Arial, sans-serif;
|
||
width: 80mm;
|
||
height: 110mm;
|
||
position: relative;
|
||
background: white;
|
||
font-size: 12px;
|
||
}
|
||
.label-container {
|
||
position: absolute;
|
||
top: 5mm;
|
||
left: 2mm;
|
||
width: 76mm;
|
||
height: 100mm;
|
||
border: 2px solid black;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
.header {
|
||
text-align: center;
|
||
font-size: 14px;
|
||
font-weight: bold;
|
||
padding: 3mm 2mm;
|
||
border-bottom: 1px solid black;
|
||
}
|
||
.customer-row {
|
||
text-align: center;
|
||
font-size: 12px;
|
||
padding: 2mm;
|
||
border-bottom: 1px solid black;
|
||
min-height: 8mm;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
.data-section {
|
||
display: flex;
|
||
flex: 1;
|
||
flex-direction: column;
|
||
}
|
||
.data-row {
|
||
display: flex;
|
||
height: 8mm;
|
||
border-bottom: 1px solid black;
|
||
}
|
||
.data-row.double {
|
||
height: 16mm;
|
||
}
|
||
.label-column {
|
||
width: 30mm;
|
||
padding: 1mm 2mm;
|
||
font-size: 8px;
|
||
border-right: 1px solid black;
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
.value-column {
|
||
flex: 1;
|
||
padding: 1mm 2mm;
|
||
font-size: 10px;
|
||
font-weight: bold;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
text-align: center;
|
||
}
|
||
.description-value {
|
||
font-size: 8px !important;
|
||
line-height: 1.2;
|
||
overflow: hidden;
|
||
}
|
||
.barcode-section {
|
||
position: absolute;
|
||
bottom: 2mm;
|
||
left: 2mm;
|
||
width: 50mm;
|
||
height: 15mm;
|
||
border: 2px solid black;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: center;
|
||
align-items: center;
|
||
background: white;
|
||
}
|
||
.barcode-visual {
|
||
font-family: 'Courier New', monospace;
|
||
font-size: 16px;
|
||
font-weight: bold;
|
||
line-height: 1;
|
||
margin-bottom: 2mm;
|
||
}
|
||
.barcode-text {
|
||
font-family: 'Courier New', monospace;
|
||
font-size: 8px;
|
||
font-weight: bold;
|
||
}
|
||
.vertical-barcode {
|
||
position: absolute;
|
||
right: 2mm;
|
||
top: 25mm;
|
||
width: 12mm;
|
||
height: 60mm;
|
||
border: 2px solid black;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: white;
|
||
}
|
||
.vertical-bars {
|
||
font-family: 'Courier New', monospace;
|
||
font-size: 12px;
|
||
font-weight: bold;
|
||
writing-mode: vertical-rl;
|
||
text-orientation: sideways;
|
||
margin: 2mm 0;
|
||
}
|
||
.vertical-text {
|
||
font-family: 'Courier New', monospace;
|
||
font-size: 6px;
|
||
font-weight: bold;
|
||
writing-mode: vertical-rl;
|
||
text-orientation: mixed;
|
||
transform: rotate(180deg);
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="label-container">
|
||
<div class="header">INNOFA ROMANIA SRL</div>
|
||
|
||
<div class="customer-row">${customer_name}</div>
|
||
|
||
<div class="data-section">
|
||
<div class="data-row">
|
||
<div class="label-column">Quantity ordered</div>
|
||
<div class="value-column">${cantitate}</div>
|
||
</div>
|
||
|
||
<div class="data-row">
|
||
<div class="label-column">Customer order</div>
|
||
<div class="value-column">${clientOrder}</div>
|
||
</div>
|
||
|
||
<div class="data-row">
|
||
<div class="label-column">Delivery date</div>
|
||
<div class="value-column">${deliveryDate}</div>
|
||
</div>
|
||
|
||
<div class="data-row double">
|
||
<div class="label-column">Product description</div>
|
||
<div class="value-column description-value">${descr_com_prod}</div>
|
||
</div>
|
||
|
||
<div class="data-row">
|
||
<div class="label-column">Size</div>
|
||
<div class="value-column">${dimensiune}</div>
|
||
</div>
|
||
|
||
<div class="data-row">
|
||
<div class="label-column">Article code</div>
|
||
<div class="value-column">${cod_articol}</div>
|
||
</div>
|
||
|
||
<div class="data-row">
|
||
<div class="label-column">Prod. order</div>
|
||
<div class="value-column">${prodOrder}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="barcode-section">
|
||
<div class="barcode-visual">||||| || ||| || ||||| ||| || |||||</div>
|
||
<div class="barcode-text">${horizontalBarcode}</div>
|
||
</div>
|
||
|
||
<div class="vertical-barcode">
|
||
<div class="vertical-bars">| | | | | | |</div>
|
||
<div class="vertical-text">${verticalBarcode}</div>
|
||
</div>
|
||
</body>
|
||
</html>`;
|
||
|
||
return htmlContent;
|
||
}
|
||
|
||
|
||
// Handle QZ Tray printing with enhanced controller
|
||
async function handleQZTrayPrint(selectedRow) {
|
||
try {
|
||
if (!qzTray) {
|
||
await initializeQZTray();
|
||
if (!qzTray) {
|
||
throw new Error('QZ Tray not available');
|
||
}
|
||
}
|
||
|
||
const selectedPrinter = document.getElementById('qztray-printer-select').value;
|
||
if (!selectedPrinter) {
|
||
showNotification('⚠️ Please select a printer first', 'warning');
|
||
return;
|
||
}
|
||
|
||
// Extract order data from row
|
||
const cells = selectedRow.querySelectorAll('td');
|
||
const orderData = {
|
||
id: cells[0].textContent,
|
||
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()
|
||
};
|
||
|
||
const quantity = orderData.cantitate;
|
||
|
||
console.log(`🖨️ Printing ${quantity} labels via QZ Tray to ${selectedPrinter}`);
|
||
showNotification(`🖨️ Printing ${quantity} labels...`, 'info');
|
||
|
||
// Print each label sequentially
|
||
for (let i = 1; i <= quantity; i++) {
|
||
console.log(`Printing label ${i} of ${quantity}...`);
|
||
|
||
try {
|
||
// Generate PDF and send to printer
|
||
await generatePDFAndPrint(selectedPrinter, orderData, i, quantity);
|
||
console.log(`✓ Label ${i} printed successfully`);
|
||
|
||
} catch (printError) {
|
||
console.error(`✗ Label ${i} failed:`, printError);
|
||
throw new Error(`Label ${i} failed: ${printError.message}`);
|
||
}
|
||
|
||
// Small delay between labels
|
||
if (i < quantity) {
|
||
await new Promise(resolve => setTimeout(resolve, 500));
|
||
}
|
||
}
|
||
|
||
// All labels printed successfully
|
||
console.log('All labels completed successfully');
|
||
showNotification(`✅ Successfully printed ${quantity} labels!`, 'success');
|
||
|
||
// 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');
|
||
showNotification('⚠️ Labels printed but database update failed', 'warning');
|
||
} else {
|
||
console.log('Database updated successfully');
|
||
}
|
||
} catch (dbError) {
|
||
console.error('Database update error:', dbError);
|
||
showNotification('⚠️ Labels printed but database update failed', 'warning');
|
||
}
|
||
|
||
// Refresh table to show updated status
|
||
document.getElementById('check-db-btn').click();
|
||
|
||
} catch (error) {
|
||
console.error('QZ Tray print error:', error);
|
||
showNotification('❌ QZ Tray print error: ' + error.message, 'error');
|
||
}
|
||
}
|
||
|
||
// Print Button Handler - Routes to appropriate print method
|
||
document.getElementById('print-label-btn').addEventListener('click', async function(e) {
|
||
e.preventDefault();
|
||
|
||
// Get selected order
|
||
const selectedRow = document.querySelector('.print-module-table tbody tr.selected');
|
||
if (!selectedRow) {
|
||
showNotification('⚠️ Please select an order first from the table below.', 'warning');
|
||
return;
|
||
}
|
||
|
||
// Get selected print method
|
||
const printMethod = document.querySelector('input[name="printMethod"]:checked').value;
|
||
|
||
if (printMethod === 'qztray') {
|
||
await handleQZTrayPrint(selectedRow);
|
||
} else {
|
||
handlePDFGeneration(selectedRow);
|
||
}
|
||
});
|
||
|
||
// Handle PDF generation
|
||
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`);
|
||
|
||
// Generate PDF with paper-saving mode enabled (optimized for thermal printers)
|
||
fetch(`/generate_labels_pdf/${orderId}/true`, {
|
||
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
|
||
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
|
||
showNotification(`✅ PDF generated successfully!\n📊 Order: ${prodOrder}\n📦 Labels: ${quantity} pieces`, 'success');
|
||
|
||
// Refresh the orders table to reflect printed status
|
||
setTimeout(() => {
|
||
document.getElementById('check-db-btn').click();
|
||
}, 1000);
|
||
})
|
||
.catch(error => {
|
||
console.error('Error generating PDF:', error);
|
||
showNotification('❌ Failed to generate PDF labels. Error: ' + error.message, 'error');
|
||
})
|
||
.finally(() => {
|
||
// Reset button state
|
||
button.textContent = originalText;
|
||
button.disabled = false;
|
||
});
|
||
}
|
||
|
||
// UI Control Functions
|
||
function updatePrintMethodUI() {
|
||
const printMethod = document.querySelector('input[name="printMethod"]:checked').value;
|
||
const printerSelection = document.getElementById('qztray-printer-selection');
|
||
const printButton = document.getElementById('print-label-btn');
|
||
|
||
if (printMethod === 'qztray') {
|
||
// QZ Tray is selected - printer selection should be visible if QZ Tray is connected
|
||
// (visibility is controlled by initializeQZTray based on connection status)
|
||
printButton.textContent = '🖨️ Print Labels';
|
||
printButton.className = 'btn btn-primary';
|
||
} else {
|
||
// PDF is selected
|
||
printerSelection.style.display = 'none';
|
||
printButton.textContent = '📄 Generate PDF';
|
||
printButton.className = 'btn btn-success';
|
||
}
|
||
}
|
||
|
||
// Close the DOMContentLoaded block that was started earlier
|
||
// Fetch pairing keys and populate dropdown (only show if multiple keys)
|
||
fetch('/get_pairing_keys')
|
||
.then(response => response.json())
|
||
.then(keys => {
|
||
const select = document.getElementById('client-select');
|
||
const selectContainer = document.getElementById('client-select-container');
|
||
|
||
if (keys.length === 0) {
|
||
console.warn('⚠️ No pairing keys found');
|
||
return;
|
||
}
|
||
|
||
if (keys.length === 1) {
|
||
// Only one key - use it automatically, don't show dropdown
|
||
window.selectedPairingKey = keys[0].pairing_key;
|
||
console.log(`✅ Using single pairing key for: ${keys[0].printer_name}`);
|
||
selectContainer.style.display = 'none';
|
||
} else {
|
||
// Multiple keys - show dropdown for selection
|
||
select.innerHTML = '';
|
||
keys.forEach(key => {
|
||
const option = document.createElement('option');
|
||
option.value = key.pairing_key;
|
||
option.textContent = key.printer_name;
|
||
select.appendChild(option);
|
||
});
|
||
window.selectedPairingKey = keys[0].pairing_key;
|
||
selectContainer.style.display = 'block';
|
||
console.log(`📋 ${keys.length} pairing keys available - dropdown shown`);
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error('❌ Failed to fetch pairing keys:', error);
|
||
});
|
||
|
||
// Update pairing key on selection
|
||
document.getElementById('client-select').addEventListener('change', function() {
|
||
window.selectedPairingKey = this.value;
|
||
console.log(`🔑 Pairing key changed: ${this.options[this.selectedIndex].text}`);
|
||
});
|
||
|
||
// Add change listeners to radio buttons
|
||
document.querySelectorAll('input[name="printMethod"]').forEach(radio => {
|
||
radio.addEventListener('change', updatePrintMethodUI);
|
||
});
|
||
|
||
// Initialize UI
|
||
updatePrintMethodUI();
|
||
|
||
// Initialize QZ Tray
|
||
setTimeout(initializeQZTray, 1000);
|
||
|
||
// Load orders
|
||
setTimeout(() => {
|
||
document.getElementById('check-db-btn').click();
|
||
}, 500);
|
||
}); // End DOMContentLoaded
|
||
</script>
|
||
|
||
{% endblock %} |