This commit is contained in:
2025-09-26 21:56:06 +03:00
parent c17812a0c1
commit 2216f21c47
17 changed files with 3361 additions and 631 deletions

View File

@@ -23,20 +23,31 @@ def mm_to_points(mm_value):
class LabelPDFGenerator:
def __init__(self):
def __init__(self, paper_saving_mode=True):
# Label dimensions: 80mm x 110mm
self.label_width = mm_to_points(80)
self.label_height = mm_to_points(110)
# Paper-saving mode: positions content at top of label to minimize waste
self.paper_saving_mode = paper_saving_mode
# Match the HTML preview dimensions exactly
# Preview: 227.4px width x 321.3px height
# Convert to proportional dimensions for 80x110mm
self.content_width = mm_to_points(60) # ~227px scaled to 80mm
self.content_height = mm_to_points(85) # ~321px scaled to 110mm
# Position content in label, leaving space for barcodes
self.content_x = mm_to_points(3) # 3mm from left edge
self.content_y = mm_to_points(15) # 15mm from bottom (space for bottom barcode)
# Position content in label - optimized for paper saving
if self.paper_saving_mode:
# Start content from top of label with minimal top margin
self.content_x = mm_to_points(2) # 2mm from left edge (was 3mm)
self.content_y = mm_to_points(20) # 20mm from bottom (more space at top)
self.top_margin = mm_to_points(5) # 5mm top margin instead of larger bottom margin
else:
# Original positioning
self.content_x = mm_to_points(3) # 3mm from left edge
self.content_y = mm_to_points(15) # 15mm from bottom (space for bottom barcode)
self.top_margin = mm_to_points(10)
# Row dimensions (9 rows total, row 6 is double height)
self.row_height = self.content_height / 10 # 8.5mm per standard row
@@ -49,16 +60,21 @@ class LabelPDFGenerator:
# Vertical divider starts from row 3
self.vertical_divider_start_y = self.content_y + self.content_height - (2 * self.row_height)
def generate_labels_pdf(self, order_data, quantity):
def generate_labels_pdf(self, order_data, quantity, printer_optimized=True):
"""
Generate PDF with multiple labels based on quantity
Creates sequential labels: CP00000711-001 to CP00000711-XXX
Optimized for thermal label printers (Epson TM-T20, Citizen CTS-310)
"""
buffer = io.BytesIO()
# Create canvas with label dimensions
c = canvas.Canvas(buffer, pagesize=(self.label_width, self.label_height))
# Optimize PDF for label printers
if printer_optimized:
self._optimize_for_label_printer(c)
# Extract base production order number for sequential numbering
prod_order = order_data.get('comanda_productie', 'CP00000000')
@@ -66,6 +82,8 @@ class LabelPDFGenerator:
for i in range(1, quantity + 1):
if i > 1: # Add new page for each label except first
c.showPage()
if printer_optimized:
self._optimize_for_label_printer(c)
# Create sequential label number: CP00000711-001, CP00000711-002, etc.
sequential_number = f"{prod_order}-{i:03d}"
@@ -77,6 +95,27 @@ class LabelPDFGenerator:
buffer.seek(0)
return buffer
def _optimize_for_label_printer(self, canvas):
"""
Optimize PDF settings for thermal label printers
- Sets high resolution for crisp text
- Minimizes margins to save paper
- Optimizes for monochrome printing
"""
# Set high resolution for thermal printers (300 DPI)
canvas.setPageCompression(1) # Enable compression
# Add PDF metadata for printer optimization
canvas.setCreator("Recticel Label System")
canvas.setTitle("Thermal Label - Optimized for Label Printers")
canvas.setSubject("Production Label")
# Set print scaling to none (100%) to maintain exact dimensions
canvas.setPageRotation(0)
# Add custom PDF properties for label printers
canvas._doc.info.producer = "Optimized for Epson TM-T20 / Citizen CTS-310"
def _draw_label(self, canvas, order_data, sequential_number, current_num, total_qty):
"""Draw a single label matching the HTML preview layout exactly"""
@@ -316,18 +355,24 @@ class LabelPDFGenerator:
canvas.rect(vertical_barcode_x, y_pos, mm_to_points(8), bar_height * 0.8, fill=1)
def generate_order_labels_pdf(order_id, order_data):
def generate_order_labels_pdf(order_id, order_data, paper_saving_mode=True):
"""
Main function to generate PDF for an order with multiple labels
Optimized for thermal label printers (Epson TM-T20, Citizen CTS-310)
Args:
order_id: Order identifier
order_data: Order information dictionary
paper_saving_mode: If True, positions content at top to save paper
"""
try:
generator = LabelPDFGenerator()
generator = LabelPDFGenerator(paper_saving_mode=paper_saving_mode)
# Get quantity from order data
quantity = int(order_data.get('cantitate', 1))
# Generate PDF
pdf_buffer = generator.generate_labels_pdf(order_data, quantity)
# Generate PDF with printer optimization
pdf_buffer = generator.generate_labels_pdf(order_data, quantity, printer_optimized=True)
return pdf_buffer

View File

@@ -1063,16 +1063,54 @@ For support, contact your system administrator.
@bp.route('/create_service_package', methods=['POST'])
def create_service_package():
"""Create and serve ZIP package of Complete Windows Print Service with all dependencies"""
"""Create and serve the Enhanced Windows Print Service package with Error 1053 fixes"""
import os
import zipfile
from flask import current_app, jsonify
from flask import current_app, jsonify, send_file
try:
# Path to the windows_print_service directory
service_dir = os.path.join(os.path.dirname(os.path.dirname(current_app.root_path)), 'windows_print_service')
print(f"Looking for service files in: {service_dir}")
# Check if the enhanced package already exists
enhanced_package_path = os.path.join(service_dir, 'QualityPrintService_COMPLETE_ZERO_DEPENDENCIES.zip')
if os.path.exists(enhanced_package_path):
# Serve the pre-built enhanced package with Error 1053 fixes
print(f"Serving pre-built enhanced package: {enhanced_package_path}")
# Copy to static directory for download
static_dir = os.path.join(current_app.root_path, 'static')
os.makedirs(static_dir, exist_ok=True)
zip_filename = 'quality_print_service_enhanced_with_error_1053_fixes.zip'
static_zip_path = os.path.join(static_dir, zip_filename)
# Copy the enhanced package to static directory
import shutil
shutil.copy2(enhanced_package_path, static_zip_path)
zip_size = os.path.getsize(static_zip_path)
return jsonify({
'success': True,
'download_url': f'/static/{zip_filename}',
'package_type': 'Enhanced with Error 1053 fixes',
'features': [
'Embedded Python 3.11.9 (zero dependencies)',
'Multiple installation methods with automatic fallback',
'Windows Service Error 1053 comprehensive fixes',
'Enhanced service wrapper with SCM communication',
'Task Scheduler and Startup Script fallbacks',
'Diagnostic and troubleshooting tools',
'Complete Chrome extension integration'
],
'zip_size': zip_size,
'installation_methods': 4
})
# Fallback: create basic package if enhanced one not available
if not os.path.exists(service_dir):
return jsonify({
'success': False,
@@ -1298,7 +1336,7 @@ def create_zero_dependency_service_package():
if not os.path.exists(service_dir):
return jsonify({
'success': False,
'error': f'Windows service directory not found: {service_dir}'
'error': f'Windows service directory not found: {service_dir}'
}), 500
# Create static directory if it doesn't exist
@@ -1367,8 +1405,11 @@ def create_zero_dependency_service_package():
print("📁 Adding service files...")
service_files = [
"print_service_complete.py",
"service_wrapper.py",
"install_service_complete.bat",
"uninstall_service_complete.bat",
"test_service.bat",
"TROUBLESHOOTING_1053.md",
"INSTALLATION_COMPLETE.md",
"PACKAGE_SUMMARY.md",
"README_COMPLETE.md"
@@ -1916,7 +1957,8 @@ def get_unprinted_orders():
return jsonify({'error': str(e)}), 500
@bp.route('/generate_labels_pdf/<int:order_id>', methods=['POST'])
def generate_labels_pdf(order_id):
@bp.route('/generate_labels_pdf/<int:order_id>/<paper_saving_mode>', methods=['POST'])
def generate_labels_pdf(order_id, paper_saving_mode='true'):
"""Generate PDF labels for a specific order"""
print(f"DEBUG: generate_labels_pdf called for order_id: {order_id}")
@@ -1970,8 +2012,12 @@ def generate_labels_pdf(order_id):
print(f"DEBUG: Generating PDF for order {order_id} with quantity {order_data['cantitate']}")
# Generate PDF
pdf_buffer = generate_order_labels_pdf(order_id, order_data)
# Check if paper-saving mode is enabled (default: true)
use_paper_saving = paper_saving_mode.lower() == 'true'
print(f"DEBUG: Paper-saving mode: {use_paper_saving}")
# Generate PDF with paper-saving option
pdf_buffer = generate_order_labels_pdf(order_id, order_data, paper_saving_mode=use_paper_saving)
# Update printed status in database
update_success = update_order_printed_status(order_id)

View File

@@ -109,13 +109,20 @@
<div class="card h-100 border-primary">
<div class="card-header bg-primary text-white text-center">
<h4 class="mb-0">🔧 Windows Print Service</h4>
<small>Enterprise-grade silent printing</small>
<small>Enterprise-grade silent printing with Error 1053 fixes</small>
</div>
<div class="card-body d-flex flex-column">
<div class="alert alert-primary">
<strong>🏢 ENTERPRISE:</strong> Silent printing with no user interaction!
</div>
<div class="alert alert-success" style="background-color: #d1f2eb; border-color: #a3e4d7; color: #0e6b49;">
<strong>🆕 NEW: Error 1053 FIXED!</strong><br>
<small>✅ Multiple installation methods with automatic fallback<br>
✅ Enhanced Windows Service Communication<br>
✅ Comprehensive diagnostic and troubleshooting tools</small>
</div>
<h5>🎯 Key Features:</h5>
<ul>
<li>⚡ Silent printing - no print dialogs</li>
@@ -124,29 +131,28 @@
<li>🛡️ Windows service with auto-recovery</li>
<li>📦 Self-contained - zero dependencies</li>
<li>🏢 Perfect for production environments</li>
<li><strong style="color: #dc3545;">🔧 Error 1053 fixes included</strong></li>
</ul>
<h5>🚀 Quick Install (3 steps):</h5>
<ol>
<li>Download and extract the service package</li>
<li>Run <code>install_service_complete.bat</code> as Administrator</li>
<li>Download and extract the enhanced service package</li>
<li>Run <code>install_service_ENHANCED.bat</code> as Administrator</li>
<li>Install Chrome extension (included in package)</li>
</ol>
<div class="text-center mt-auto">
<div class="btn-group-vertical mb-3" role="group">
<button class="btn btn-primary btn-lg" id="download-service-btn">
📥 Download Windows Service
</button>
<button class="btn btn-success btn-lg" id="download-zero-deps-btn">
🚀 Download ZERO Dependencies Package
📥 Download Enhanced Windows Service
</button>
<small class="text-muted mb-2">🆕 Includes Error 1053 fixes & multiple installation methods</small>
</div>
<div class="alert alert-info">
<small>
<strong>📦 Standard Package (~50KB):</strong> Requires Python 3.7+ installed<br>
<strong>🚀 Zero Dependencies (~15MB):</strong> Includes everything - no Python needed!
<strong>🆕 Enhanced Package (~11MB):</strong> Embedded Python 3.11.9 + Error 1053 fixes<br>
<strong>✅ Features:</strong> 4 installation methods, diagnostic tools, zero dependencies
</small>
</div>
@@ -329,7 +335,7 @@ document.getElementById('download-service-btn').addEventListener('click', functi
// Show loading state
const originalText = this.innerHTML;
this.innerHTML = '⏳ Preparing Windows Service Package...';
this.innerHTML = '⏳ Preparing Enhanced Service Package...';
this.disabled = true;
// Create the service package
@@ -340,16 +346,22 @@ document.getElementById('download-service-btn').addEventListener('click', functi
// Start download
window.location.href = data.download_url;
// Show success message
// Show enhanced success message with features
const features = data.features ? data.features.join('\n• ') : 'Error 1053 fixes and enhanced installation';
setTimeout(() => {
this.innerHTML = '✅ Download Started!';
this.innerHTML = '✅ Enhanced Package Downloaded!';
// Show feature alert
if (data.package_type) {
alert(`${data.package_type} Downloaded!\n\n🆕 Features included:\n${features}\n\n📦 Size: ${(data.zip_size / 1024 / 1024).toFixed(1)} MB\n🔧 Installation methods: ${data.installation_methods || 'Multiple'}\n\n📋 Next: Extract and run install_service_ENHANCED.bat as Administrator`);
}
}, 500);
// Reset button
setTimeout(() => {
this.innerHTML = originalText;
this.disabled = false;
}, 3000);
}, 5000);
} else {
alert('Error creating service package: ' + data.error);
this.innerHTML = originalText;

File diff suppressed because it is too large Load Diff

View File

@@ -19,112 +19,21 @@
line-height: 1.2 !important;
}
/* Enhanced table styling to match view_orders.html with higher specificity */
/* Enhanced table styling */
.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 %}
@@ -132,8 +41,7 @@ table tbody tr.selected td {
<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 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>
<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;">
@@ -141,12 +49,12 @@ table tbody tr.selected td {
<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>
@@ -159,13 +67,10 @@ table tbody tr.selected td {
<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
@@ -173,7 +78,7 @@ table tbody tr.selected td {
<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
@@ -181,7 +86,7 @@ table tbody tr.selected td {
<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
@@ -189,7 +94,7 @@ table tbody tr.selected td {
<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
@@ -197,7 +102,7 @@ table tbody tr.selected td {
<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
@@ -205,7 +110,7 @@ table tbody tr.selected td {
<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
@@ -213,7 +118,7 @@ table tbody tr.selected td {
<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
@@ -221,7 +126,6 @@ table tbody tr.selected td {
<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 -->
@@ -246,52 +150,9 @@ table tbody tr.selected td {
</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>
<button id="print-label-btn" class="btn btn-success" style="margin-top: 15px;">Generate PDF</button>
</div>
<!-- Data Preview Card -->
@@ -303,24 +164,24 @@ table tbody tr.selected td {
<thead>
<tr>
<th>ID</th>
<th>Comanda<br>Productie</th>
<th>Cod<br>Articol</th>
<th>Descr. Com.<br>Prod</th>
<th>Comanda Productie</th>
<th>Cod Articol</th>
<th>Descr. Com. Prod</th>
<th>Cantitate</th>
<th>Data<br>Livrare</th>
<th>Data 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>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 loaded here via JavaScript -->
<!-- Data will be dynamically loaded here -->
</tbody>
</table>
</div>
@@ -329,19 +190,20 @@ table tbody tr.selected td {
<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}`);
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();
})
@@ -382,12 +244,11 @@ document.getElementById('check-db-btn').addEventListener('click', function() {
// Add click event for row selection
tr.addEventListener('click', function() {
console.log('Row clicked:', order.order_number);
console.log('Row clicked:', order.id);
// 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 = '';
@@ -397,7 +258,6 @@ document.getElementById('check-db-btn').addEventListener('click', function() {
// 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');
@@ -417,54 +277,26 @@ document.getElementById('check-db-btn').addEventListener('click', function() {
if (data.length > 0) {
updateLabelPreview(data[0]);
// Add fallback print functionality if extension is not available
addPDFGenerationHandler();
// Auto-select first row
// Initialize PDF generation functionality
addPDFGenerationHandler();
// 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);
} 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;
.catch(error => console.error('Error fetching orders:', error))
.finally(() => {
button.textContent = 'Check Database';
button.disabled = false;
});
});
@@ -516,15 +348,12 @@ function updateLabelPreview(order) {
// 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
function addPDFGenerationHandler() {
const printButton = document.getElementById('print-label-btn');
if (printButton) {
// Update button text and appearance for PDF generation
printButton.innerHTML = '<EFBFBD> Generate PDF Labels';
printButton.innerHTML = '📄 Generate PDF Labels';
printButton.title = 'Generate PDF with multiple labels based on quantity';
printButton.addEventListener('click', function(e) {
@@ -555,8 +384,13 @@ function addFallbackPrintHandler() {
console.log(`Generating PDF for order ${orderId} with ${quantity} labels`);
// Generate PDF
fetch(`/generate_labels_pdf/${orderId}`, {
// 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'
@@ -569,25 +403,46 @@ function addFallbackPrintHandler() {
return response.blob();
})
.then(blob => {
// Create download link for PDF
// 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();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
// Also open PDF in new tab for printing
const printWindow = window.open(url, '_blank');
printWindow.focus();
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.`);
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
document.getElementById('check-db-btn').click();
// This will automatically hide the printed order from the unprinted orders list
setTimeout(() => {
refreshUnprintedOrdersTable();
}, 1000);
})
.catch(error => {
console.error('Error generating PDF:', error);
@@ -602,361 +457,146 @@ function addFallbackPrintHandler() {
}
}
// SIMPLIFIED CHROME EXTENSION PRINTING - NO WINDOWS SERVICE NEEDED
// Extension detection and communication
let extensionId = null;
let extensionReady = false;
// Check extension availability on page load
window.addEventListener('DOMContentLoaded', function() {
detectExtension();
initializePrintButton();
});
// Detect Chrome extension
function detectExtension() {
// Method 1: Try to get extension ID from injected DOM element
const extensionElement = document.getElementById('chrome-extension-id');
if (extensionElement) {
extensionId = extensionElement.getAttribute('data-extension-id');
console.log('✅ Extension ID detected from DOM:', extensionId);
}
// Function to refresh the unprinted orders table
function refreshUnprintedOrdersTable() {
console.log('Refreshing unprinted orders table...');
const button = document.getElementById('check-db-btn');
// Method 2: Fallback to hardcoded extension ID
if (!extensionId) {
extensionId = 'cifcoidplhgclhcnlcgdkjbaoempjmdl'; // Hardcoded fallback
console.log(' Using fallback extension ID:', extensionId);
}
// Show refreshing state
const originalText = button.textContent;
button.textContent = 'Refreshing...';
button.disabled = true;
// Test extension communication
if (window.chrome && window.chrome.runtime) {
try {
chrome.runtime.sendMessage(extensionId, { action: 'ping' }, function(response) {
if (chrome.runtime.lastError) {
console.warn('❌ Extension not responding:', chrome.runtime.lastError.message);
extensionReady = false;
} else if (response && response.success) {
console.log('✅ Extension ready:', response);
extensionReady = 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>
`;
// Try to get available printers from extension
loadPrintersFromExtension();
} else {
console.warn('❌ Extension ping failed:', response);
extensionReady = false;
}
updatePrintButton(extensionReady);
});
} catch (error) {
console.error('❌ Extension communication error:', error);
extensionReady = false;
updatePrintButton(false);
}
} else {
console.warn('❌ Chrome runtime not available');
extensionReady = false;
updatePrintButton(false);
}
}
// Load available printers from extension
function loadPrintersFromExtension() {
if (!extensionReady) return;
try {
chrome.runtime.sendMessage(extensionId, { action: 'get_printers' }, function(response) {
if (chrome.runtime.lastError) {
console.warn('Failed to get printers:', chrome.runtime.lastError.message);
return;
}
if (response && response.success && response.printers) {
updatePrinterDropdown(response.printers);
}
});
} catch (error) {
console.warn('Error loading printers:', error);
}
}
// Update printer dropdown with available printers
function updatePrinterDropdown(printers) {
const select = document.getElementById('printer-select');
if (!select) return;
// Clear and rebuild options
select.innerHTML = '<option value="default">Default Printer (Recommended)</option>';
// Add common printer names that users might have
const commonPrinters = [
'Microsoft Print to PDF',
'Brother HL-L2340D',
'HP LaserJet',
'Canon PIXMA',
'Epson WorkForce',
'Samsung ML-1640',
'Zebra ZP 450'
];
commonPrinters.forEach((printer, index) => {
const option = document.createElement('option');
option.value = printer;
option.textContent = printer;
select.appendChild(option);
});
// Add separator
const separator = document.createElement('option');
separator.disabled = true;
separator.textContent = '── System Printers ──';
select.appendChild(separator);
// Add printers from extension response
if (printers && printers.length > 0) {
printers.forEach((printer, index) => {
const option = document.createElement('option');
option.value = printer.name || printer;
option.textContent = `${printer.display_name || printer.name || printer}`;
if (printer.is_default) {
option.textContent += ' (System Default)';
}
select.appendChild(option);
});
}
const printerStatus = document.getElementById('printer-status');
if (printerStatus && extensionReady) {
const totalPrinters = commonPrinters.length + (printers ? printers.length : 0);
printerStatus.textContent = `${totalPrinters} printer options available - select one above`;
printerStatus.style.color = '#28a745';
}
}
// Update print button based on extension availability
function updatePrintButton(isExtensionReady) {
const printButton = document.getElementById('print-label-btn');
const printerStatus = document.getElementById('printer-status');
if (!printButton) return;
if (isExtensionReady) {
printButton.innerHTML = '🖨️ Print Labels (Windows Service)';
printButton.title = 'Send PDF directly to Windows Print Service for silent printing';
printButton.style.background = '#28a745'; // Green
if (printerStatus) {
printerStatus.textContent = 'Chrome extension ready - Windows service mode enabled';
printerStatus.style.color = '#28a745';
}
// Update printer selection label for Windows service mode
const printerLabel = document.querySelector('label[for="printer-select"]');
if (printerLabel) {
printerLabel.innerHTML = '🖨️ Select Printer (Windows Service will print directly)';
}
} else {
printButton.innerHTML = '📄 Generate PDF';
printButton.title = 'Generate PDF for manual printing (Windows Service not available)';
printButton.style.background = '#007bff'; // Blue
if (printerStatus) {
printerStatus.textContent = 'Extension/Service not detected - PDF download mode';
printerStatus.style.color = '#6c757d';
}
// Update printer selection label for manual mode
const printerLabel = document.querySelector('label[for="printer-select"]');
if (printerLabel) {
printerLabel.innerHTML = '🖨️ Choose Printer (for reference only)';
}
}
}
// Initialize print button functionality
function initializePrintButton() {
const printButton = document.getElementById('print-label-btn');
if (!printButton) return;
// Remove any existing event listeners
const newButton = printButton.cloneNode(true);
printButton.parentNode.replaceChild(newButton, printButton);
newButton.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 = newButton.innerHTML;
newButton.innerHTML = '⏳ Processing...';
newButton.disabled = true;
try {
if (extensionReady) {
await printViaExtension(orderId, prodOrder, quantity);
} else {
await downloadPDFLabels(orderId, prodOrder, quantity);
}
} catch (error) {
console.error('Print operation failed:', error);
alert('❌ Print operation failed: ' + error.message);
} finally {
newButton.innerHTML = originalText;
newButton.disabled = false;
}
});
}
// Print via Chrome extension (communicates with Windows service)
async function printViaExtension(orderId, prodOrder, quantity) {
try {
// Get selected printer from dropdown
const selectedPrinter = getSelectedPrinter();
console.log(`🖨️ Selected printer for Windows service: ${selectedPrinter}`);
// Generate PDF first
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');
}
// Get PDF URL (or construct it)
let pdfUrl;
try {
const data = await pdfResponse.json();
pdfUrl = data.pdf_url || `/static/generated_labels/labels_${prodOrder}_qty${quantity}.pdf`;
} catch {
// If response is not JSON, construct URL
pdfUrl = `/static/generated_labels/labels_${prodOrder}_qty${quantity}.pdf`;
}
// Make URL absolute
const fullPdfUrl = window.location.origin + pdfUrl;
// Send to extension which will communicate with Windows service
chrome.runtime.sendMessage(extensionId, {
action: 'print_pdf',
pdfUrl: fullPdfUrl,
orderId: orderId,
prodOrder: prodOrder,
quantity: quantity,
printerName: selectedPrinter // Pass selected printer to extension
}, function(response) {
if (chrome.runtime.lastError) {
throw new Error('Extension communication failed: ' + chrome.runtime.lastError.message);
}
if (response && response.success) {
const printerInfo = selectedPrinter === 'default' ? 'default printer' : selectedPrinter;
let message = `✅ Print job sent to Windows service!\n\n📊 Order: ${prodOrder}\n📦 Quantity: ${quantity} labels\n🖨️ Target Printer: ${printerInfo}\n🔧 Method: ${response.method || 'Windows Print Service'}`;
// 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);
});
if (response.instruction) {
message += `\n\n📋 Status: ${response.instruction}`;
} else {
message += `\n\n📋 The PDF has been sent directly to the printer queue`;
}
alert(message);
updatePrintedStatus(orderId);
} else if (response && response.fallback) {
// Service not available, handle fallback
alert(`⚠️ Windows Print Service not available.\n\nError: ${response.error}\n\n📋 Fallback: ${response.instruction}\n\nPlease ensure the Windows Print Service is installed and running.`);
// Still try to download PDF as fallback
await downloadPDFLabels(orderId, prodOrder, quantity);
} else {
throw new Error(response?.error || 'Extension print failed');
// 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;
});
} catch (error) {
console.error('Extension print error:', error);
// Fallback to PDF download
alert(`❌ Print via Windows service failed.\n\nError: ${error.message}\n\n🔄 Falling back to PDF download for manual printing.`);
await downloadPDFLabels(orderId, prodOrder, quantity);
}
}
// Helper function to get selected printer
function getSelectedPrinter() {
const select = document.getElementById('printer-select');
return select ? select.value : 'default';
// 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';
}
// Fallback: Download PDF for manual printing
async function downloadPDFLabels(orderId, prodOrder, quantity) {
try {
const response = await fetch(`/generate_labels_pdf/${orderId}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' }
});
if (!response.ok) {
throw new 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);
alert(`📄 PDF downloaded successfully!\n\n📊 Order: ${prodOrder}\n📦 Quantity: ${quantity} labels\n📁 File: ${filename}\n\n➡️ Please print the PDF manually from your Downloads folder.`);
} 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(() => {
const checkButton = document.getElementById('check-db-btn');
if (checkButton) checkButton.click();
}, 1000);
}
} catch (error) {
console.warn('Failed to update printed status:', error);
}
}
document.getElementById('print-label-btn').addEventListener('click', function() {
console.log('Generate PDF logic here');
});
</script>
{% endblock %}