updated
This commit is contained in:
Binary file not shown.
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
1085
py_app/app/templates/print_module copy.html
Normal file
1085
py_app/app/templates/print_module copy.html
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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 %}
|
||||
Reference in New Issue
Block a user