Files
quality_recticel/py_app/app/templates/print_module.html

1133 lines
53 KiB
HTML
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="directPrint" value="direct" checked>
<label class="form-check-label" for="directPrint" style="font-size: 11px; line-height: 1.3;">
<strong>Direct Print</strong><br>
<span class="text-muted">Print directly to thermal label printer</span>
</label>
</div>
<div class="form-check mb-2">
<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.3;">
<strong>Generate PDF</strong><br>
<span class="text-muted">Create PDF for manual printing</span>
</label>
</div>
</div>
<!-- Printer Selection -->
<div id="printerSelection" style="margin-bottom: 15px;">
<label style="font-size: 12px; font-weight: 600; color: #495057; margin-bottom: 5px; display: block;">
Select Printer:
</label>
<select id="printerSelect" class="form-control form-control-sm" style="font-size: 11px; padding: 4px 8px;">
<option value="default">Default Printer</option>
<option value="Epson TM-T20">Epson TM-T20</option>
<option value="Citizen CTS-310">Citizen CTS-310</option>
<option value="custom">Other Printer...</option>
</select>
<small class="text-muted" style="font-size: 10px;">Choose your thermal label printer</small>
</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;">
🖨️ Print 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>
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;">✓ 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>
`;
// 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);
});
// Update the label preview with first row by default
if (data.length > 0) {
updateLabelPreview(data[0]);
// Initialize PDF generation functionality
addPDFGenerationHandler();
// Initialize print service
initializePrintService();
// 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))
.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 - No printer setup needed
// Labels are generated as PDF files for universal compatibility
function addPDFGenerationHandler() {
const printButton = document.getElementById('print-label-btn');
if (printButton) {
// Update button text and appearance based on print method
updatePrintButtonText();
// Add event listeners for radio buttons
document.querySelectorAll('input[name="printMethod"]').forEach(radio => {
radio.addEventListener('change', updatePrintButtonText);
});
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;
}
// Get print method
const printMethod = document.querySelector('input[name="printMethod"]:checked').value;
if (printMethod === 'direct') {
handleDirectPrint(selectedRow);
} else {
handlePDFGeneration(selectedRow);
}
});
}
}
function updatePrintButtonText() {
const printButton = document.getElementById('print-label-btn');
const printMethod = document.querySelector('input[name="printMethod"]:checked').value;
const printerSelection = document.getElementById('printerSelection');
if (printMethod === 'direct') {
printButton.innerHTML = '🖨️ Print Directly';
printButton.title = 'Print directly to thermal label printer';
printerSelection.style.display = 'block';
} else {
printButton.innerHTML = '📄 Generate PDF';
printButton.title = 'Generate PDF with multiple labels based on quantity';
printerSelection.style.display = 'none';
}
}
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>✅ 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;">✓ 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>
`;
// 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>❌ 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', function() {
console.log('Generate PDF logic here');
});
</script>
{% endblock %}