960 lines
47 KiB
HTML
960 lines
47 KiB
HTML
{% 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: 2e6 !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 to match view_orders.html with higher specificity */
|
||
.card.scan-table-card table.print-module-table.scan-table {
|
||
width: 100% !important;
|
||
margin-bottom: 1rem !important;
|
||
color: #212529 !important;
|
||
border-collapse: collapse !important;
|
||
}
|
||
|
||
.card.scan-table-card table.print-module-table.scan-table thead th {
|
||
vertical-align: bottom !important;
|
||
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 tbody td {
|
||
padding: 0.25rem 0.4rem !important;
|
||
vertical-align: middle !important;
|
||
border-top: 1px solid #dee2e6 !important;
|
||
font-size: 9px !important;
|
||
line-height: 1.2 !important;
|
||
}
|
||
|
||
/* HOVER EFFECTS - Higher specificity */
|
||
.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:not(.selected):hover td {
|
||
background-color: #f8f9fa !important;
|
||
}
|
||
|
||
/* ROW SELECTION STYLES - Maximum specificity */
|
||
.card.scan-table-card table.print-module-table.scan-table tbody tr.selected td {
|
||
background-color: #007bff !important;
|
||
background: #007bff !important;
|
||
color: white !important;
|
||
border-color: #0056b3 !important;
|
||
}
|
||
|
||
.card.scan-table-card table.print-module-table.scan-table tbody tr.selected:hover td {
|
||
background-color: #0056b3 !important;
|
||
background: #0056b3 !important;
|
||
color: white !important;
|
||
border-color: #004085 !important;
|
||
}
|
||
|
||
.card.scan-table-card table.print-module-table.scan-table tbody tr.selected td span {
|
||
color: white !important;
|
||
}
|
||
|
||
/* Additional universal overrides for selection */
|
||
tr.selected td {
|
||
background-color: #007bff !important;
|
||
background: #007bff !important;
|
||
color: white !important;
|
||
}
|
||
|
||
tbody tr.selected td {
|
||
background-color: #007bff !important;
|
||
background: #007bff !important;
|
||
color: white !important;
|
||
}
|
||
|
||
table tbody tr.selected td {
|
||
background-color: #007bff !important;
|
||
background: #007bff !important;
|
||
color: white !important;
|
||
}
|
||
|
||
/* COLUMN WIDTH SPECIFICATIONS - Higher specificity */
|
||
.card.scan-table-card table.print-module-table.scan-table th:nth-child(1),
|
||
.card.scan-table-card table.print-module-table.scan-table td:nth-child(1) { width: 50px !important; } /* ID */
|
||
.card.scan-table-card table.print-module-table.scan-table th:nth-child(2),
|
||
.card.scan-table-card table.print-module-table.scan-table td:nth-child(2) { width: 80px !important; } /* Comanda Productie */
|
||
.card.scan-table-card table.print-module-table.scan-table th:nth-child(3),
|
||
.card.scan-table-card table.print-module-table.scan-table td:nth-child(3) { width: 80px !important; } /* Cod Articol */
|
||
.card.scan-table-card table.print-module-table.scan-table th:nth-child(4),
|
||
.card.scan-table-card table.print-module-table.scan-table td:nth-child(4) { width: 150px !important; } /* Descr Com Prod */
|
||
.card.scan-table-card table.print-module-table.scan-table th:nth-child(5),
|
||
.card.scan-table-card table.print-module-table.scan-table td:nth-child(5) { width: 70px !important; } /* Cantitate */
|
||
.card.scan-table-card table.print-module-table.scan-table th:nth-child(6),
|
||
.card.scan-table-card table.print-module-table.scan-table td:nth-child(6) { width: 80px !important; } /* Data Livrare */
|
||
.card.scan-table-card table.print-module-table.scan-table th:nth-child(7),
|
||
.card.scan-table-card table.print-module-table.scan-table td:nth-child(7) { width: 75px !important; } /* Dimensiune */
|
||
.card.scan-table-card table.print-module-table.scan-table th:nth-child(8),
|
||
.card.scan-table-card table.print-module-table.scan-table td:nth-child(8) { width: 90px !important; } /* Com Achiz Client */
|
||
.card.scan-table-card table.print-module-table.scan-table th:nth-child(9),
|
||
.card.scan-table-card table.print-module-table.scan-table td:nth-child(9) { width: 70px !important; } /* Nr Linie */
|
||
.card.scan-table-card table.print-module-table.scan-table th:nth-child(10),
|
||
.card.scan-table-card table.print-module-table.scan-table td:nth-child(10) { width: 100px !important; } /* Customer Name */
|
||
.card.scan-table-card table.print-module-table.scan-table th:nth-child(11),
|
||
.card.scan-table-card table.print-module-table.scan-table td:nth-child(11) { width: 90px !important; } /* Customer Art Nr */
|
||
.card.scan-table-card table.print-module-table.scan-table th:nth-child(12),
|
||
.card.scan-table-card table.print-module-table.scan-table td:nth-child(12) { width: 70px !important; } /* Open Order */
|
||
.card.scan-table-card table.print-module-table.scan-table th:nth-child(13),
|
||
.card.scan-table-card table.print-module-table.scan-table td:nth-child(13) { width: 50px !important; } /* Line */
|
||
.card.scan-table-card table.print-module-table.scan-table th:nth-child(14),
|
||
.card.scan-table-card table.print-module-table.scan-table td:nth-child(14) { width: 70px !important; } /* Printed */
|
||
.card.scan-table-card table.print-module-table.scan-table th:nth-child(15),
|
||
.card.scan-table-card table.print-module-table.scan-table td:nth-child(15) { width: 100px !important; } /* Created */
|
||
</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: 480px; width: 330px; flex-shrink: 0; position: relative;">
|
||
<div class="label-view-title" style="width: 100%; text-align: center; padding: 18px 0 0 0; font-size: 18px; font-weight: bold; letter-spacing: 0.5px;">Label View</div>
|
||
<h3 style="position: absolute; top: 15px; left: 15px; display: none;">Label Preview</h3>
|
||
<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>
|
||
<!-- Row 9 for Prod Order (final row) -->
|
||
|
||
<!-- 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>
|
||
|
||
<!-- Printer Selection and Service Setup -->
|
||
<div style="width: 100%; margin-top: 15px; padding: 0 15px;">
|
||
<!-- Printer Selection -->
|
||
<div class="mb-3">
|
||
<label for="printer-select" class="form-label" style="font-size: 13px; font-weight: 600; color: #495057; margin-bottom: 5px;">
|
||
🖨️ Choose Printer
|
||
</label>
|
||
<select id="printer-select" class="form-control" style="font-size: 12px; padding: 6px 10px;">
|
||
<option value="default">Default Printer</option>
|
||
<option value="detecting" disabled>Detecting printers...</option>
|
||
</select>
|
||
<small id="printer-status" class="text-muted" style="font-size: 10px;">
|
||
Checking for available printers...
|
||
</small>
|
||
</div>
|
||
|
||
<!-- Service Installation Link -->
|
||
<div class="text-center">
|
||
<div style="background: #fff3cd; border: 1px solid #ffeaa7; border-radius: 6px; padding: 10px; margin-bottom: 10px;">
|
||
<div style="font-size: 11px; color: #856404; margin-bottom: 8px;">
|
||
<strong><EFBFBD> Upgrade to Silent Printing</strong>
|
||
</div>
|
||
<a href="{{ url_for('main.download_extension') }}" class="btn btn-warning btn-sm" target="_blank" style="font-size: 11px; padding: 4px 12px;">
|
||
📥 Install Print Service & Extension
|
||
</a>
|
||
<div style="font-size: 9px; color: #6c757d; margin-top: 5px;">
|
||
5-minute setup • Auto-starts with Windows • Silent printing
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Print Button Section -->
|
||
<div style="width: 100%; display: flex; justify-content: center; align-items: center; gap: 12px; margin-top: 15px;">
|
||
<label for="print-label-btn" style="font-size: 14px; font-weight: 500; color: var(--app-card-text); margin-bottom: 0;">Generate PDF Labels (80x110mm)</label>
|
||
<button id="print-label-btn" class="btn btn-success" style="font-size: 14px; padding: 8px 28px; border-radius: 6px;">📄 Generate PDF</button>
|
||
</div>
|
||
<div style="width: 100%; text-align: center; margin-top: 8px; color: #6c757d; font-size: 12px;">
|
||
Creates sequential labels based on quantity (e.g., CP00000711-001 to CP00000711-063)
|
||
</div>
|
||
<div style="width: 100%; text-align: center; margin-top: 12px;">
|
||
<small style="font-size: 11px; color: #6c757d;">
|
||
<20> PDF labels can be printed directly from your browser or saved for later use
|
||
</small>
|
||
</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<br>Productie</th>
|
||
<th>Cod<br>Articol</th>
|
||
<th>Descr. Com.<br>Prod</th>
|
||
<th>Cantitate</th>
|
||
<th>Data<br>Livrare</th>
|
||
<th>Dimensiune</th>
|
||
<th>Com.Achiz.<br>Client</th>
|
||
<th>Nr.<br>Linie</th>
|
||
<th>Customer<br>Name</th>
|
||
<th>Customer<br>Art. Nr.</th>
|
||
<th>Open<br>Order</th>
|
||
<th>Line</th>
|
||
<th>Printed</th>
|
||
<th>Created</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="unprinted-orders-table">
|
||
<!-- Data will be loaded here via JavaScript -->
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
document.getElementById('check-db-btn').addEventListener('click', function() {
|
||
console.log('Check Database button clicked');
|
||
|
||
// Show loading state
|
||
const button = this;
|
||
const originalText = button.textContent;
|
||
button.textContent = 'Loading...';
|
||
button.disabled = true;
|
||
|
||
fetch('/get_unprinted_orders')
|
||
.then(response => {
|
||
console.log('Response status:', response.status);
|
||
if (!response.ok) {
|
||
throw new Error(`HTTP error! status: ${response.status}`);
|
||
}
|
||
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.order_number);
|
||
|
||
// Remove selection from other rows
|
||
document.querySelectorAll('.print-module-table tbody tr').forEach(row => {
|
||
row.classList.remove('selected');
|
||
// Clear inline styles
|
||
const cells = row.querySelectorAll('td');
|
||
cells.forEach(cell => {
|
||
cell.style.backgroundColor = '';
|
||
cell.style.color = '';
|
||
});
|
||
});
|
||
|
||
// Select this row
|
||
this.classList.add('selected');
|
||
console.log('Row selected, classes:', this.className);
|
||
|
||
// 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]);
|
||
|
||
// Add fallback print functionality if extension is not available
|
||
addPDFGenerationHandler();
|
||
|
||
// Auto-select first row
|
||
setTimeout(() => {
|
||
const firstRow = document.querySelector('.print-module-table tbody tr');
|
||
if (firstRow) {
|
||
firstRow.classList.add('selected');
|
||
}
|
||
}, 100);
|
||
} else {
|
||
document.getElementById('customer-name-row').textContent = 'No data 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';
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error('Error fetching data:', error);
|
||
document.getElementById('customer-name-row').textContent = 'Error loading data';
|
||
document.getElementById('quantity-ordered-value').textContent = 'Error';
|
||
document.getElementById('client-order-info').textContent = 'Error';
|
||
document.getElementById('delivery-date-value').textContent = 'Error';
|
||
document.getElementById('size-value').textContent = 'Error';
|
||
document.getElementById('description-value').textContent = 'Error';
|
||
document.getElementById('article-code-value').textContent = 'Error';
|
||
document.getElementById('prod-order-value').textContent = 'Error';
|
||
document.getElementById('barcode-text').textContent = 'Error';
|
||
document.getElementById('vertical-barcode-text').textContent = '000000-00';
|
||
|
||
// Reset button state
|
||
button.textContent = originalText;
|
||
button.disabled = false;
|
||
})
|
||
.catch(error => {
|
||
console.error('Error fetching orders:', error);
|
||
|
||
// Show error message to user
|
||
alert('Failed to load orders from database. Error: ' + error.message);
|
||
|
||
// Reset button state
|
||
button.textContent = originalText;
|
||
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;
|
||
}
|
||
|
||
// PDF Generation System - No printer setup needed
|
||
// Labels are generated as PDF files for universal compatibility
|
||
|
||
// Legacy function name - now handles PDF generation
|
||
function addFallbackPrintHandler() {
|
||
// Initialize PDF print button
|
||
const printButton = document.getElementById('print-label-btn');
|
||
|
||
if (printButton) {
|
||
// Update button text and appearance for PDF generation
|
||
printButton.innerHTML = '<27> Generate PDF Labels';
|
||
printButton.title = 'Generate PDF with multiple labels based on quantity';
|
||
|
||
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;
|
||
}
|
||
|
||
const orderId = selectedRow.dataset.orderId;
|
||
const quantityCell = selectedRow.querySelector('td:nth-child(5)'); // Cantitate column
|
||
const quantity = quantityCell ? parseInt(quantityCell.textContent) : 1;
|
||
const prodOrderCell = selectedRow.querySelector('td:nth-child(2)'); // Comanda Productie column
|
||
const prodOrder = prodOrderCell ? prodOrderCell.textContent.trim() : 'N/A';
|
||
|
||
if (!orderId) {
|
||
alert('Could not determine order ID. Please refresh and try again.');
|
||
return;
|
||
}
|
||
|
||
// Show loading state
|
||
const originalText = this.textContent;
|
||
this.textContent = 'Generating PDF...';
|
||
this.disabled = true;
|
||
|
||
console.log(`Generating PDF for order ${orderId} with ${quantity} labels`);
|
||
|
||
// Generate PDF
|
||
fetch(`/generate_labels_pdf/${orderId}`, {
|
||
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 download link for PDF
|
||
const url = window.URL.createObjectURL(blob);
|
||
const a = document.createElement('a');
|
||
a.href = url;
|
||
a.download = `labels_${prodOrder}_${quantity}pcs.pdf`;
|
||
document.body.appendChild(a);
|
||
a.click();
|
||
window.URL.revokeObjectURL(url);
|
||
document.body.removeChild(a);
|
||
|
||
// Also open PDF in new tab for printing
|
||
const printWindow = window.open(url, '_blank');
|
||
printWindow.focus();
|
||
|
||
// Show success message
|
||
alert(`✅ PDF generated successfully!\n📊 Order: ${prodOrder}\n📦 Labels: ${quantity} pieces\n\nThe PDF has been downloaded and opened for printing.`);
|
||
|
||
// Refresh the orders table to reflect printed status
|
||
document.getElementById('check-db-btn').click();
|
||
})
|
||
.catch(error => {
|
||
console.error('Error generating PDF:', error);
|
||
alert('❌ Failed to generate PDF labels. Error: ' + error.message);
|
||
})
|
||
.finally(() => {
|
||
// Reset button state
|
||
this.textContent = originalText;
|
||
this.disabled = false;
|
||
});
|
||
});
|
||
}
|
||
}
|
||
|
||
// Windows Print Service Integration
|
||
const PRINT_SERVICE_URL = 'http://localhost:8765';
|
||
let printServiceAvailable = false;
|
||
let availablePrinters = [];
|
||
|
||
// Check print service availability on page load
|
||
window.addEventListener('DOMContentLoaded', function() {
|
||
checkPrintServiceAvailability();
|
||
initializePrinterDropdown();
|
||
});
|
||
|
||
function initializePrinterDropdown() {
|
||
const select = document.getElementById('printer-select');
|
||
if (!select) return;
|
||
|
||
select.innerHTML = `
|
||
<option value="default">Default Printer</option>
|
||
<option value="detecting" disabled>Detecting printers...</option>
|
||
`;
|
||
}
|
||
|
||
async function checkPrintServiceAvailability() {
|
||
const printerStatus = document.getElementById('printer-status');
|
||
|
||
try {
|
||
printerStatus.textContent = 'Checking Windows Print Service...';
|
||
console.log(`🔍 Checking Windows Print Service at: ${PRINT_SERVICE_URL}/health`);
|
||
|
||
const response = await fetch(`${PRINT_SERVICE_URL}/health`, {
|
||
method: 'GET',
|
||
signal: AbortSignal.timeout(5000)
|
||
});
|
||
|
||
console.log(`📡 Service response status: ${response.status}`);
|
||
|
||
if (response.ok) {
|
||
const data = await response.json();
|
||
console.log('📋 Service response data:', data);
|
||
|
||
printServiceAvailable = true;
|
||
console.log('✅ Windows Print Service is available and responding!');
|
||
updatePrintButtonForService(true);
|
||
updatePrinterStatus(`Windows Print Service detected (${data.platform || 'Unknown platform'})`);
|
||
await loadAvailablePrinters();
|
||
} else {
|
||
throw new Error(`Service responded with status ${response.status}`);
|
||
}
|
||
} catch (error) {
|
||
printServiceAvailable = false;
|
||
console.error('❌ Windows Print Service check failed:', error);
|
||
console.log(`🔧 Troubleshooting:
|
||
1. Is the service running? Check: sc query QualityLabelPrinting
|
||
2. Is port 8765 accessible? Try: http://localhost:8765/health in new tab
|
||
3. Service logs: C:\\Program Files\\QualityLabelPrinting\\PrintService\\print_service.log`);
|
||
|
||
updatePrintButtonForService(false);
|
||
updatePrinterStatus(`Windows Print Service not detected - ${error.message}`);
|
||
}
|
||
}
|
||
|
||
async function loadAvailablePrinters() {
|
||
try {
|
||
const response = await fetch(`${PRINT_SERVICE_URL}/printers`);
|
||
if (response.ok) {
|
||
const data = await response.json();
|
||
availablePrinters = data.printers || [];
|
||
updatePrinterDropdown();
|
||
updatePrinterStatus(`${availablePrinters.length} printer(s) detected via Windows service`);
|
||
}
|
||
} catch (error) {
|
||
console.warn('Failed to load printers:', error);
|
||
updatePrinterStatus('Failed to detect printers - using default');
|
||
}
|
||
}
|
||
|
||
function updatePrinterDropdown() {
|
||
const select = document.getElementById('printer-select');
|
||
if (!select) return;
|
||
|
||
// Clear and rebuild options
|
||
select.innerHTML = '<option value="default">Default Printer (Recommended)</option>';
|
||
|
||
availablePrinters.forEach((printer, index) => {
|
||
const option = document.createElement('option');
|
||
option.value = printer.name || printer;
|
||
option.textContent = `${printer.name || printer}`;
|
||
if (printer.is_default) {
|
||
option.textContent += ' (Default)';
|
||
option.selected = true;
|
||
}
|
||
select.appendChild(option);
|
||
});
|
||
|
||
if (availablePrinters.length === 0) {
|
||
const option = document.createElement('option');
|
||
option.value = 'none';
|
||
option.textContent = 'No printers detected';
|
||
option.disabled = true;
|
||
select.appendChild(option);
|
||
}
|
||
}
|
||
|
||
function updatePrinterStatus(message) {
|
||
const status = document.getElementById('printer-status');
|
||
if (status) {
|
||
status.textContent = message;
|
||
}
|
||
}
|
||
|
||
function getSelectedPrinter() {
|
||
const select = document.getElementById('printer-select');
|
||
return select ? select.value : 'default';
|
||
}
|
||
|
||
function updatePrintButtonForService(serviceAvailable) {
|
||
const printButton = document.getElementById('print-label-btn');
|
||
if (!printButton) return;
|
||
|
||
if (serviceAvailable) {
|
||
printButton.innerHTML = '🖨️ Print Labels (Silent)';
|
||
printButton.title = 'Print labels directly to selected printer using Windows service';
|
||
printButton.style.background = '#28a745'; // Green for direct print
|
||
} else {
|
||
printButton.innerHTML = '📄 Generate PDF';
|
||
printButton.title = 'Generate PDF for manual printing (Windows service not available)';
|
||
printButton.style.background = '#007bff'; // Blue for PDF download
|
||
}
|
||
}
|
||
|
||
// Enhanced print function with Windows service support - NEW SERVER-SIDE APPROACH
|
||
async function printLabelsWithService(orderId, prodOrder, quantity) {
|
||
console.log(`🖨️ printLabelsWithService called - Order: ${orderId}, Quantity: ${quantity}`);
|
||
|
||
try {
|
||
// Get selected printer from dropdown
|
||
const selectedPrinter = getSelectedPrinter();
|
||
console.log(`🖨️ Selected printer: ${selectedPrinter}`);
|
||
|
||
// Use new server-side endpoint that bypasses CORS
|
||
const printData = {
|
||
printer_name: selectedPrinter
|
||
};
|
||
|
||
console.log('📋 Print request data:', printData);
|
||
console.log(`📡 Sending to server endpoint: /print_labels_silent/${orderId}`);
|
||
|
||
// Send to Flask server which handles Windows service communication
|
||
const response = await fetch(`/print_labels_silent/${orderId}`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify(printData)
|
||
});
|
||
|
||
console.log(`📨 Server response status: ${response.status}`);
|
||
|
||
const result = await response.json();
|
||
console.log('📋 Server response data:', result);
|
||
|
||
if (response.ok && result.success) {
|
||
// Success - labels printed silently
|
||
const printerName = selectedPrinter === 'default' ? 'default printer' : selectedPrinter;
|
||
console.log(`✅ Print successful to printer: ${printerName}`);
|
||
|
||
alert(`✅ Labels printed successfully!\n\n📊 Order: ${prodOrder}\n📦 Quantity: ${quantity} labels\n🖨️ Printer: ${printerName}\n📋 Sequential: ${prodOrder}-001 to ${prodOrder}-${String(quantity).padStart(3, '0')}\n\n🎯 Printed silently via Windows service!`);
|
||
|
||
// Update order status in database
|
||
await updatePrintedStatus(orderId);
|
||
|
||
return true;
|
||
} else if (response.status === 503 && result.fallback === 'pdf_download') {
|
||
// Windows service not available - inform user and suggest fallback
|
||
console.warn('⚠️ Windows service not available, showing service setup info');
|
||
|
||
alert(`⚠️ Windows Print Service Not Available\n\n${result.error}\n\n📋 To enable silent printing:\n1. Install the Windows Print Service\n2. Start the service: sc start QualityLabelPrinting\n3. Restart your browser\n\n💡 For now, use the "Generate PDF" button for manual printing.`);
|
||
|
||
// Mark service as unavailable for this session
|
||
printServiceAvailable = false;
|
||
updatePrintButtonForService(false);
|
||
|
||
throw new Error('Windows Print Service not available');
|
||
} else {
|
||
console.error('❌ Server returned error:', result);
|
||
throw new Error(result.error || `Print operation failed with status ${response.status}`);
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error('❌ Server-side print error:', error);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
// Fallback PDF download function
|
||
async function downloadPDFLabels(orderId, prodOrder, quantity) {
|
||
try {
|
||
// Generate PDF
|
||
const response = await fetch(`/generate_labels_pdf/${orderId}`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' }
|
||
});
|
||
|
||
if (!response.ok) {
|
||
const data = await response.json().catch(() => ({}));
|
||
throw new Error(data.error || `HTTP ${response.status}`);
|
||
}
|
||
|
||
// Get filename from response headers
|
||
const contentDisposition = response.headers.get('Content-Disposition');
|
||
let filename = `labels_${prodOrder}_qty${quantity}.pdf`;
|
||
if (contentDisposition) {
|
||
const matches = contentDisposition.match(/filename="?([^"]+)"?/);
|
||
if (matches) filename = matches[1];
|
||
}
|
||
|
||
const blob = await response.blob();
|
||
|
||
// Download PDF
|
||
const url = window.URL.createObjectURL(blob);
|
||
const a = document.createElement('a');
|
||
a.href = url;
|
||
a.download = filename;
|
||
document.body.appendChild(a);
|
||
a.click();
|
||
window.URL.revokeObjectURL(url);
|
||
document.body.removeChild(a);
|
||
|
||
// Show success message
|
||
alert(`📄 PDF downloaded successfully!\n\n📊 Order: ${prodOrder}\n📦 Quantity: ${quantity} labels\n📁 File: ${filename}\n📋 Sequential: ${prodOrder}-001 to ${prodOrder}-${String(quantity).padStart(3, '0')}\n\n➡️ Please print the PDF manually`);
|
||
|
||
return true;
|
||
|
||
} catch (error) {
|
||
console.error('PDF download error:', error);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
// Update printed status in database
|
||
async function updatePrintedStatus(orderId) {
|
||
try {
|
||
const response = await fetch(`/update_printed_status/${orderId}`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' }
|
||
});
|
||
|
||
if (response.ok) {
|
||
// Refresh the orders table
|
||
setTimeout(() => {
|
||
document.getElementById('check-db-btn').click();
|
||
}, 1000);
|
||
}
|
||
} catch (error) {
|
||
console.warn('Failed to update printed status:', error);
|
||
}
|
||
}
|
||
|
||
// PDF generation handler
|
||
// Helper to get extension ID injected by content script
|
||
function getInjectedExtensionId() {
|
||
const el = document.getElementById('chrome-extension-id');
|
||
if (el) return el.getAttribute('data-extension-id');
|
||
return null;
|
||
}
|
||
|
||
function addPDFGenerationHandler() {
|
||
const printButton = document.getElementById('print-label-btn');
|
||
if (!printButton) return;
|
||
|
||
printButton.addEventListener('click', async 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;
|
||
}
|
||
|
||
const orderId = selectedRow.dataset.orderId;
|
||
const prodOrder = selectedRow.querySelector('td:nth-child(2)').textContent.trim();
|
||
const quantity = selectedRow.querySelector('td:nth-child(5)').textContent.trim();
|
||
if (!orderId) {
|
||
alert('Error: Could not determine order ID.');
|
||
return;
|
||
}
|
||
|
||
// Show loading state
|
||
const originalText = printButton.innerHTML;
|
||
const originalColor = printButton.style.background;
|
||
printButton.innerHTML = '⏳ Processing...';
|
||
printButton.disabled = true;
|
||
|
||
try {
|
||
// Step 1: Generate PDF and get its URL
|
||
const pdfResponse = await fetch(`/generate_labels_pdf/${orderId}`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' }
|
||
});
|
||
if (!pdfResponse.ok) throw new Error('Failed to generate PDF');
|
||
// Try to get the PDF URL from the response (assume server returns a URL or we can construct it)
|
||
// If not, fallback to download
|
||
let pdfUrl = '';
|
||
try {
|
||
const data = await pdfResponse.json();
|
||
pdfUrl = data.pdf_url || '';
|
||
} catch {
|
||
// If not JSON, fallback to constructing the URL
|
||
pdfUrl = `/static/generated_labels/labels_${prodOrder}_${quantity}pcs.pdf`;
|
||
}
|
||
|
||
// Step 2: Prepare print job for Chrome extension
|
||
const selectedPrinter = getSelectedPrinter();
|
||
const printJob = {
|
||
pdfUrl: window.location.origin + pdfUrl,
|
||
printer: selectedPrinter,
|
||
orderId: orderId,
|
||
prodOrder: prodOrder,
|
||
quantity: quantity
|
||
};
|
||
|
||
// Step 3: Get extension ID from injected DOM
|
||
const extensionId = getInjectedExtensionId();
|
||
|
||
// Step 4: Send message to Chrome extension
|
||
if (window.chrome && window.chrome.runtime && window.chrome.runtime.sendMessage && extensionId) {
|
||
window.chrome.runtime.sendMessage(
|
||
extensionId,
|
||
{ action: 'print_pdf', ...printJob },
|
||
function(response) {
|
||
if (response && response.success) {
|
||
alert('✅ Labels sent to printer!\nOrder: ' + prodOrder + '\nQuantity: ' + quantity + '\nPrinter: ' + selectedPrinter);
|
||
updatePrintedStatus(orderId);
|
||
} else {
|
||
alert('❌ Failed to print via extension. PDF will be downloaded.');
|
||
downloadPDFLabels(orderId, prodOrder, quantity);
|
||
}
|
||
}
|
||
);
|
||
} else {
|
||
// Fallback: Download PDF
|
||
alert('ℹ️ Chrome extension not detected or extension ID not injected. PDF will be downloaded.');
|
||
await downloadPDFLabels(orderId, prodOrder, quantity);
|
||
}
|
||
} catch (error) {
|
||
console.error('Print operation failed:', error);
|
||
alert('❌ Print operation failed: ' + error.message);
|
||
} finally {
|
||
printButton.innerHTML = originalText;
|
||
printButton.style.background = originalColor;
|
||
printButton.disabled = false;
|
||
}
|
||
});
|
||
}
|
||
</script>
|
||
{% endblock %} |