it prints

This commit is contained in:
2025-10-02 02:00:40 +03:00
parent b2b225049d
commit 7dc688d972
12 changed files with 9185 additions and 64 deletions

6
.gitignore vendored
View File

@@ -5,7 +5,7 @@ __pycache__/
app/__pycache__/ app/__pycache__/
tray/ tray/
jdk-11.0.20-full/ jdk-11.0.20-full/
jdk-11.0.21+9-jre/
jre-11-windows.zip
# Ignore generated or local files # Ignore generated or local files
py_app/instance/pairing_keys.json backup/
py_app/app/forked_tray.py
backup/print_module.html

View File

@@ -0,0 +1,125 @@
# Print Progress Modal Feature
## Overview
Added a visual progress modal that displays during label printing operations via QZ Tray. The modal shows real-time progress, updates the database upon completion, and refreshes the table view automatically.
## Features Implemented
### 1. Progress Modal UI
- **Modal Overlay**: Full-screen semi-transparent overlay to focus user attention
- **Progress Bar**: Animated progress bar showing percentage completion
- **Status Messages**: Real-time status updates during printing
- **Label Counter**: Shows "X / Y" format for current progress (e.g., "5 / 10")
### 2. Print Flow Improvements
The printing process now follows these steps:
1. **Validation**: Check QZ Tray connection and printer selection
2. **Modal Display**: Show progress modal immediately
3. **Sequential Printing**: Print each label one by one with progress updates
- Update progress bar after each successful print
- Show current label number being printed
- 500ms delay between labels for printer processing
4. **Database Update**: Call `/update_printed_status/<order_id>` endpoint
- Marks the order as printed in the database
- Handles errors gracefully (prints still succeed even if DB update fails)
5. **Table Refresh**: Automatically click "Load Orders" button to refresh the view
6. **Modal Close**: Hide modal after completion
7. **Notification**: Show success notification to user
### 3. Progress Updates
The modal displays different status messages:
- "Preparing to print..." (initial)
- "Printing label X of Y..." (during printing)
- "✅ All labels printed! Updating database..." (after prints complete)
- "✅ Complete! Refreshing table..." (after DB update)
- "⚠️ Labels printed but database update failed" (on DB error)
### 4. Error Handling
- Modal automatically closes on any error
- Error notifications shown to user
- Database update failures don't prevent successful printing
- Graceful degradation if DB update fails
## Technical Details
### CSS Styling
- **Modal**: Fixed position, z-index 9999, centered layout
- **Content Card**: White background, rounded corners, shadow
- **Progress Bar**: Linear gradient blue, smooth transitions
- **Responsive**: Min-width 400px, max-width 500px
### JavaScript Functions Modified
#### `handleQZTrayPrint(selectedRow)`
**Changes:**
- Added modal element references
- Show modal before printing starts
- Update progress bar and counter in loop
- Call database update endpoint after printing
- Handle database update errors
- Refresh table automatically
- Close modal on completion or error
### Backend Integration
#### Endpoint Used: `/update_printed_status/<int:order_id>`
- **Method**: POST
- **Purpose**: Mark order as printed in database
- **Authentication**: Requires superadmin, warehouse_manager, or etichete role
- **Response**: JSON with success/error message
## User Experience Flow
1. User selects an order row in the table
2. User clicks "Print Label (QZ Tray)" button
3. Modal appears showing "Preparing to print..."
4. Progress bar fills as each label prints
5. Counter shows current progress (e.g., "7 / 10")
6. After all labels print: "✅ All labels printed! Updating database..."
7. Database updates with printed status
8. Modal shows "✅ Complete! Refreshing table..."
9. Modal closes automatically
10. Success notification appears
11. Table refreshes showing updated order status
## Benefits
**Visual Feedback**: Users see real-time progress instead of a frozen UI
**Status Clarity**: Clear messages about what's happening
**Automatic Updates**: Database and UI update without manual intervention
**Error Recovery**: Graceful handling of database update failures
**Professional UX**: Modern, polished user interface
**Non-Blocking**: Progress modal doesn't interfere with printing operation
## Files Modified
1. **print_module.html**
- Added modal HTML structure
- Added modal CSS styles
- Updated `handleQZTrayPrint()` function
- Added database update API call
- Added automatic table refresh
## Testing Checklist
- [ ] Modal appears when printing starts
- [ ] Progress bar animates smoothly
- [ ] Counter updates correctly (1/10, 2/10, etc.)
- [ ] All labels print successfully
- [ ] Database updates after printing
- [ ] Table refreshes automatically
- [ ] Modal closes after completion
- [ ] Success notification appears
- [ ] Error handling works (if DB update fails)
- [ ] Modal closes on printing errors
## Future Enhancements
Potential improvements:
- Add "Cancel" button to stop printing mid-process
- Show estimated time remaining
- Add sound notification on completion
- Log printing history with timestamps
- Add printer status monitoring
- Show print queue if multiple orders selected

44
py_app/app/forked_tray.py Normal file
View File

@@ -0,0 +1,44 @@
import os
import json
import uuid
from datetime import datetime, timedelta
from flask import Blueprint, request, render_template, redirect, url_for
bp_tray = Blueprint('forked_tray', __name__)
PAIRING_FILE = os.path.join(os.path.dirname(__file__), '../instance/pairing_keys.json')
# Utility to load pairing keys from file
def load_pairing_keys():
if not os.path.exists(PAIRING_FILE):
return []
with open(PAIRING_FILE, 'r') as f:
return json.load(f)
# Utility to save pairing keys to file
def save_pairing_keys(keys):
with open(PAIRING_FILE, 'w') as f:
json.dump(keys, f, indent=2)
@bp_tray.route('/download_extension', methods=['GET'])
def download_extension():
pairing_keys = load_pairing_keys()
return render_template('download_extension.html', pairing_keys=pairing_keys)
@bp_tray.route('/generate_pairing_key', methods=['POST'])
def generate_pairing_key():
printer_name = request.form.get('printer_name')
if not printer_name:
return redirect(url_for('forked_tray.download_extension'))
# Generate key and warranty
pairing_key = str(uuid.uuid4())
warranty_days = 30
warranty_until = (datetime.utcnow() + timedelta(days=warranty_days)).strftime('%Y-%m-%d')
# Load, append, and save
keys = load_pairing_keys()
keys.append({
'printer_name': printer_name,
'pairing_key': pairing_key,
'warranty_until': warranty_until
})
save_pairing_keys(keys)
return render_template('download_extension.html', pairing_key=pairing_key, printer_name=printer_name, warranty_until=warranty_until, pairing_keys=keys)

View File

@@ -1,17 +1,4 @@
import json import json
from flask import Blueprint
bp = Blueprint('main', __name__)
@bp.route('/get_pairing_keys')
def get_pairing_keys():
"""Return all pairing keys as JSON for client selection."""
keys_path = os.path.join(current_app.instance_path, 'pairing_keys.json')
try:
with open(keys_path, 'r') as f:
keys = json.load(f)
except Exception as e:
print(f"Error loading pairing keys: {e}")
return jsonify({'success': False, 'error': str(e), 'pairing_keys': []}), 500
return jsonify({'success': True, 'pairing_keys': keys})
import os import os
import mariadb import mariadb
from datetime import datetime, timedelta from datetime import datetime, timedelta
@@ -61,6 +48,21 @@ def format_cell_data(cell):
def store_articles(): def store_articles():
return render_template('store_articles.html') return render_template('store_articles.html')
@bp.route('/get_pairing_keys')
def get_pairing_keys():
"""Return all pairing keys as JSON for client selection."""
keys_path = os.path.join(current_app.instance_path, 'pairing_keys.json')
try:
if os.path.exists(keys_path):
with open(keys_path, 'r') as f:
keys = json.load(f)
else:
keys = []
except Exception as e:
print(f"Error loading pairing keys: {e}")
return jsonify([]), 200
return jsonify(keys)
@bp.route('/warehouse_reports') @bp.route('/warehouse_reports')
def warehouse_reports(): def warehouse_reports():
return render_template('warehouse_reports.html') return render_template('warehouse_reports.html')

View File

@@ -0,0 +1,35 @@
QZ TRAY LIBRARY PATCH NOTES
===========================
Version: 2.2.4 (patched for custom QZ Tray with pairing key authentication)
Date: October 2, 2025
CHANGES MADE:
-------------
1. Line ~387: Commented out certificate sending
- Original: _qz.websocket.connection.sendData({ certificate: cert, promise: openPromise });
- Patched: openPromise.resolve(); (resolves immediately without sending certificate)
2. Line ~391-403: Bypassed certificate retrieval
- Original: Called _qz.security.callCert() to get certificate from user
- Patched: Directly calls sendCert(null) without trying to get certificate
3. Comments added to indicate patches
REASON FOR PATCHES:
------------------
The custom QZ Tray server has certificate validation COMPLETELY DISABLED.
It uses ONLY pairing key (HMAC) authentication instead of certificates.
The original qz-tray.js library expects certificate-based authentication and
fails when the server doesn't respond to certificate requests.
COMPATIBILITY:
-------------
- Works with custom QZ Tray server (forked version with certificate validation disabled)
- NOT compatible with standard QZ Tray servers
- Connects to both ws://localhost:8181 and wss://localhost:8182
- Authentication handled by server-side pairing keys
BACKUP:
-------
Original unpatched version saved as: qz-tray.js.backup

2871
py_app/app/static/qz-tray.js Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -35,6 +35,71 @@
background-color: #007bff !important; background-color: #007bff !important;
color: white !important; color: white !important;
} }
/* Print Progress Modal Styles */
.print-progress-modal {
display: none;
position: fixed;
z-index: 9999;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.6);
justify-content: center;
align-items: center;
}
.print-progress-content {
background-color: white;
padding: 30px;
border-radius: 10px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
min-width: 400px;
max-width: 500px;
text-align: center;
}
.print-progress-content h3 {
margin: 0 0 20px 0;
color: #333;
font-size: 24px;
}
.progress-info {
margin-bottom: 15px;
color: #666;
font-size: 16px;
}
.progress-bar-container {
width: 100%;
height: 30px;
background-color: #f0f0f0;
border-radius: 15px;
overflow: hidden;
margin-bottom: 15px;
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.1);
}
.progress-bar {
height: 100%;
background: linear-gradient(90deg, #007bff 0%, #0056b3 100%);
width: 0%;
transition: width 0.3s ease;
border-radius: 15px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: bold;
}
.progress-details {
font-size: 18px;
font-weight: bold;
color: #007bff;
}
</style> </style>
{% endblock %} {% endblock %}
@@ -44,6 +109,20 @@
<div class="card scan-form-card" style="display: flex; flex-direction: column; justify-content: flex-start; align-items: center; min-height: 700px; width: 330px; flex-shrink: 0; position: relative; padding: 15px;"> <div class="card scan-form-card" style="display: flex; flex-direction: column; justify-content: flex-start; align-items: center; min-height: 700px; width: 330px; flex-shrink: 0; position: relative; padding: 15px;">
<div class="label-view-title" style="width: 100%; text-align: center; padding: 0 0 15px 0; font-size: 18px; font-weight: bold; letter-spacing: 0.5px;">Label View</div> <div class="label-view-title" style="width: 100%; text-align: center; padding: 0 0 15px 0; font-size: 18px; font-weight: bold; letter-spacing: 0.5px;">Label View</div>
<!-- Add link to pairing key management -->
<div style="width: 100%; text-align: center; margin-bottom: 10px;">
<a href="{{ url_for('main.download_extension') }}" class="btn btn-info btn-sm" target="_blank">🔑 Manage Pairing Keys</a>
</div>
<!-- Client/Printer selection dropdown -->
<div style="width: 100%; text-align: center; margin-bottom: 10px;">
<label for="client-select" style="font-weight: 600;">Select Printer/Client:</label>
<select id="client-select" class="form-control form-control-sm" style="width: 80%; margin: 0 auto;"></select>
</div>
<!-- Display pairing key for selected client -->
<div id="pairing-key-display" style="width: 100%; text-align: center; margin-bottom: 15px; font-size: 13px; color: #007bff; font-family: monospace;"></div>
<!-- Label Preview Section --> <!-- Label Preview Section -->
<div id="label-preview" style="border: 1px solid #ddd; padding: 10px; position: relative; background: #fafafa; width: 301px; height: 434.7px;"> <div id="label-preview" style="border: 1px solid #ddd; padding: 10px; position: relative; background: #fafafa; width: 301px; height: 434.7px;">
<!-- Label content rectangle --> <!-- Label content rectangle -->
@@ -209,11 +288,19 @@
<div style="font-size: 10px; color: #495057; margin-bottom: 10px; line-height: 1.3;"> <div style="font-size: 10px; color: #495057; margin-bottom: 10px; line-height: 1.3;">
Professional printing solution • ZPL thermal labels • Direct hardware access Professional printing solution • ZPL thermal labels • Direct hardware access
</div> </div>
<a href="https://qz.io/download/" target="_blank" class="btn btn-info btn-sm" style="font-size: 10px; padding: 4px 12px; text-decoration: none;"> <div style="margin-bottom: 10px;">
<button onclick="initializeQZTray()" class="btn btn-primary btn-sm" style="font-size: 10px; padding: 4px 12px;">
🔄 Reconnect to QZ Tray
</button>
<button onclick="testQZConnection()" class="btn btn-info btn-sm" style="font-size: 10px; padding: 4px 12px;">
🔍 Test Connection
</button>
</div>
<a href="https://qz.io/download/" target="_blank" class="btn btn-secondary btn-sm" style="font-size: 10px; padding: 4px 12px; text-decoration: none;">
📥 Download QZ Tray (Free) 📥 Download QZ Tray (Free)
</a> </a>
<div style="font-size: 9px; color: #6c757d; margin-top: 8px; line-height: 1.2;"> <div style="font-size: 9px; color: #6c757d; margin-top: 8px; line-height: 1.2;">
<strong>Setup:</strong> 1. Install QZ Tray → 2. Start the service → 3. Refresh this page <strong>Setup:</strong> 1. Install QZ Tray → 2. Start the service → 3. Click "Reconnect"
</div> </div>
</div> </div>
</div> </div>
@@ -253,10 +340,28 @@
</div> </div>
</div> </div>
<!-- Printing Progress Modal -->
<div id="print-progress-modal" class="print-progress-modal" style="display: none;">
<div class="print-progress-content">
<h3>🖨️ Printing Labels</h3>
<div class="progress-info">
<span id="progress-text">Preparing to print...</span>
</div>
<div class="progress-bar-container">
<div id="progress-bar" class="progress-bar"></div>
</div>
<div class="progress-details">
<span id="progress-count">0 / 0</span>
</div>
</div>
</div>
<!-- QZ Tray JavaScript Library --> <!-- QZ Tray JavaScript Library -->
<!-- Add html2canvas library for capturing preview as image --> <!-- Add html2canvas library for capturing preview as image -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/qz-tray@2.2.4/qz-tray.js"></script> <!-- PATCHED QZ Tray library - works with custom server using pairing key authentication -->
<script src="{{ url_for('static', filename='qz-tray.js') }}"></script>
<!-- Original CDN version (disabled): <script src="https://cdn.jsdelivr.net/npm/qz-tray@2.2.4/qz-tray.js"></script> -->
<script> <script>
// Simplified notification system // Simplified notification system
@@ -271,25 +376,31 @@ function showNotification(message, type = 'info') {
top: 20px; top: 20px;
right: 20px; right: 20px;
z-index: 9999; z-index: 9999;
max-width: 350px; max-width: 450px;
padding: 15px; padding: 15px;
border-radius: 5px; border-radius: 5px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1); box-shadow: 0 4px 6px rgba(0,0,0,0.1);
`; `;
// Convert newlines to <br> tags for proper display
const formattedMessage = message.replace(/\n/g, '<br>');
notification.innerHTML = ` notification.innerHTML = `
<div style="display: flex; align-items: center; justify-content: space-between;"> <div style="display: flex; align-items: flex-start; justify-content: space-between;">
<span style="flex: 1; padding-right: 10px;">${message}</span> <span style="flex: 1; padding-right: 10px; white-space: pre-wrap; font-family: monospace; font-size: 12px;">${formattedMessage}</span>
<button type="button" onclick="this.parentElement.parentElement.remove()" style="background: none; border: none; font-size: 20px; cursor: pointer;">&times;</button> <button type="button" onclick="this.parentElement.parentElement.remove()" style="background: none; border: none; font-size: 20px; cursor: pointer; flex-shrink: 0;">&times;</button>
</div> </div>
`; `;
document.body.appendChild(notification); document.body.appendChild(notification);
// Longer timeout for error messages
const timeout = type === 'error' ? 15000 : 5000;
setTimeout(() => { setTimeout(() => {
if (notification.parentElement) { if (notification.parentElement) {
notification.remove(); notification.remove();
} }
}, 5000); }, timeout);
} }
// Database loading functionality // Database loading functionality
@@ -404,6 +515,28 @@ document.getElementById('check-db-btn').addEventListener('click', function() {
}); });
}); });
// Fetch pairing keys and populate dropdown
fetch('/get_pairing_keys')
.then(response => response.json())
.then(keys => {
const select = document.getElementById('client-select');
select.innerHTML = '';
keys.forEach(key => {
const option = document.createElement('option');
option.value = key.pairing_key;
option.textContent = key.printer_name;
select.appendChild(option);
});
// Show first key by default
if (keys.length > 0) {
document.getElementById('pairing-key-display').textContent = 'Pairing Key: ' + keys[0].pairing_key;
}
});
// Update pairing key display on selection
document.getElementById('client-select').addEventListener('change', function() {
document.getElementById('pairing-key-display').textContent = 'Pairing Key: ' + this.value;
});
// Update label preview with order data // Update label preview with order data
function updateLabelPreview(order) { function updateLabelPreview(order) {
const customerName = order.customer_name || 'N/A'; const customerName = order.customer_name || 'N/A';
@@ -472,15 +605,51 @@ let availablePrinters = [];
// Initialize QZ Tray connection // Initialize QZ Tray connection
async function initializeQZTray() { async function initializeQZTray() {
try { try {
// Check if QZ Tray is available console.log('🔍 Checking for QZ Tray...');
// Check if QZ Tray library is loaded
if (typeof qz === 'undefined') { if (typeof qz === 'undefined') {
throw new Error('QZ Tray library not loaded'); console.error('QZ Tray library not loaded');
const errorMsg = '❌ QZ Tray Library Not Loaded\n\n' +
'The patched QZ Tray JavaScript library failed to load.\n' +
'Check the browser console for errors and refresh the page.\n\n' +
'Path: /static/qz-tray.js (patched version for pairing key auth)';
document.getElementById('qztray-status').textContent = 'Library Error';
document.getElementById('qztray-status').className = 'badge badge-danger';
showNotification(errorMsg, 'error');
return false;
} }
// Connect to QZ Tray console.log('✅ QZ Tray library loaded');
// Using PATCHED qz-tray.js that works with our custom QZ Tray server
// The patched library automatically skips certificate validation
// Our custom server uses ONLY pairing key (HMAC) authentication
console.log('🔒 Using patched qz-tray.js for pairing-key authentication...');
// No security setup needed - the patched library handles it
// Original qz-tray.js required setCertificatePromise, but our patch bypasses it
console.log('✅ Ready to connect to custom QZ Tray server');
console.log('🔌 Attempting to connect to QZ Tray on client PC...');
console.log('📍 Will try: ws://localhost:8181 (insecure) then wss://localhost:8182 (secure)');
// Set connection closed callback
qz.websocket.setClosedCallbacks(function() {
console.warn('⚠️ QZ Tray connection closed');
document.getElementById('qztray-status').textContent = 'Disconnected';
document.getElementById('qztray-status').className = 'badge badge-warning';
});
// Connect to QZ Tray running on client PC
console.log('⏳ Connecting...');
await qz.websocket.connect(); await qz.websocket.connect();
qzTray = qz; qzTray = qz;
const version = await qz.api.getVersion();
console.log('✅ QZ Tray connected successfully');
console.log('📋 QZ Tray Version:', version);
// Update status // Update status
document.getElementById('qztray-status').textContent = 'Connected'; document.getElementById('qztray-status').textContent = 'Connected';
document.getElementById('qztray-status').className = 'badge badge-success'; document.getElementById('qztray-status').className = 'badge badge-success';
@@ -488,20 +657,227 @@ async function initializeQZTray() {
// Load available printers // Load available printers
await loadQZTrayPrinters(); await loadQZTrayPrinters();
console.log('✅ QZ Tray connected successfully'); showNotification(`🖨️ QZ Tray v${version} connected successfully!`, 'success');
showNotification('🖨️ QZ Tray connected successfully!', 'success');
return true; return true;
} catch (error) { } catch (error) {
console.error('❌ QZ Tray connection failed:', error); console.error('❌ QZ Tray connection failed:', error);
document.getElementById('qztray-status').textContent = 'Not Connected'; console.error('Error details:', {
message: error.message,
type: typeof error,
errorName: error.name,
stack: error.stack
});
// Detailed error messages based on actual error
let errorMsg = '❌ Cannot Connect to QZ Tray\n\n';
let statusText = 'Not Connected';
const errorStr = error.toString().toLowerCase();
const messageStr = (error.message || '').toLowerCase();
if (messageStr.includes('unable to establish') ||
errorStr.includes('unable to establish') ||
messageStr.includes('failed to connect') ||
errorStr.includes('websocket') ||
messageStr.includes('econnrefused')) {
errorMsg += '🔌 Connection Refused\n\n';
errorMsg += 'QZ Tray is not responding on this computer.\n\n';
errorMsg += '✅ Steps to fix:\n';
errorMsg += '1. Check if QZ Tray is installed on THIS computer\n';
errorMsg += '2. Look for QZ Tray icon in system tray (bottom-right)\n';
errorMsg += '3. If not running, start QZ Tray application\n';
errorMsg += '4. If installed but not working, restart QZ Tray\n';
errorMsg += '5. Download from: https://qz.io/download/\n\n';
errorMsg += '🔍 Technical: Trying to connect to ports 8181/8182';
statusText = 'Not Running';
} else if (messageStr.includes('certificate') ||
errorStr.includes('certificate') ||
messageStr.includes('security') ||
messageStr.includes('ssl') ||
messageStr.includes('tls')) {
errorMsg += '🔒 Certificate Security Issue\n\n';
errorMsg += 'QZ Tray uses a self-signed certificate for security.\n\n';
errorMsg += '✅ Steps to fix:\n';
errorMsg += '1. Open new tab: https://localhost:8182\n';
errorMsg += '2. Accept/Trust the security certificate\n';
errorMsg += '3. Come back and click "Reconnect to QZ Tray"\n\n';
errorMsg += '🔍 This is normal and safe for QZ Tray';
statusText = 'Certificate Error';
} else {
errorMsg += '⚠️ Unexpected Error\n\n';
errorMsg += 'Error: ' + error.message + '\n\n';
errorMsg += '🔍 Troubleshooting:\n';
errorMsg += '1. Open browser console (F12) for details\n';
errorMsg += '2. Click "Test Connection" for diagnostics\n';
errorMsg += '3. Make sure QZ Tray is running\n';
errorMsg += '4. Try restarting your browser';
}
document.getElementById('qztray-status').textContent = statusText;
document.getElementById('qztray-status').className = 'badge badge-danger'; document.getElementById('qztray-status').className = 'badge badge-danger';
showNotification('❌ QZ Tray not available. Please install and start QZ Tray.', 'error'); showNotification(errorMsg, 'error');
return false; return false;
} }
} }
// Manual test connection function
async function testQZConnection() {
console.log('🔍 ========== QZ TRAY CONNECTION TEST ==========');
const statusElement = document.getElementById('qztray-status');
const originalStatus = statusElement.textContent;
statusElement.textContent = 'Testing...';
statusElement.className = 'badge badge-warning';
let testResults = [];
try {
// Test 1: Check if library is loaded
console.log('Test 1: Checking library...');
if (typeof qz === 'undefined') {
testResults.push('❌ Test 1: Library NOT loaded from CDN');
throw new Error('QZ Tray library not loaded from CDN');
}
testResults.push('✅ Test 1: Library loaded successfully');
console.log('✅ Test 1 PASSED: Library loaded from local (patched version)');
// Using patched qz-tray.js - no security setup needed
console.log('🔒 Using patched library for custom QZ Tray...');
// Test 2: Check if already connected
console.log('Test 2: Checking existing connection...');
if (qz.websocket.isActive()) {
testResults.push('✅ Test 2: Already connected!');
console.log('✅ Test 2 PASSED: Already connected');
const version = await qz.api.getVersion();
testResults.push(`✅ Version: ${version}`);
console.log(`📋 QZ Tray Version: ${version}`);
const printers = await qz.printers.find();
testResults.push(`✅ Found ${printers.length} printer(s)`);
console.log(`🖨️ Printers found: ${printers.length}`);
showNotification(testResults.join('\n'), 'success');
statusElement.textContent = 'Connected';
statusElement.className = 'badge badge-success';
console.log('========== TEST COMPLETED: ALL PASSED ==========');
return;
}
testResults.push('⚠️ Test 2: Not currently connected');
console.log('⚠️ Test 2: Not connected, will attempt connection...');
// Test 3: Try to connect
console.log('Test 3: Attempting WebSocket connection...');
testResults.push('🔌 Test 3: Connecting to QZ Tray...');
console.log('🔌 Connecting to WebSocket...');
await qz.websocket.connect();
testResults.push('✅ Test 3: WebSocket connected!');
console.log('✅ Test 3 PASSED: WebSocket connected');
// Test 4: Get version
console.log('Test 4: Getting QZ Tray version...');
const version = await qz.api.getVersion();
testResults.push(`✅ Test 4: QZ Tray v${version}`);
console.log(`✅ Test 4 PASSED: Version ${version}`);
// Test 5: List printers
console.log('Test 5: Fetching printer list...');
const printers = await qz.printers.find();
testResults.push(`✅ Test 5: Found ${printers.length} printer(s)`);
console.log(`✅ Test 5 PASSED: ${printers.length} printers found`);
if (printers.length > 0) {
console.log('📋 Available printers:', printers);
testResults.push('📋 Printers: ' + printers.slice(0, 3).join(', ') + (printers.length > 3 ? '...' : ''));
}
// Success!
qzTray = qz;
statusElement.textContent = 'Connected';
statusElement.className = 'badge badge-success';
console.log('========== TEST COMPLETED: ALL PASSED ==========');
showNotification(testResults.join('\n') + '\n\n✅ All tests passed!', 'success');
// Reload printers
await loadQZTrayPrinters();
} catch (error) {
console.error('❌ TEST FAILED:', error);
console.error('Error type:', typeof error);
console.error('Error name:', error.name);
console.error('Error message:', error.message);
console.error('Error stack:', error.stack);
statusElement.textContent = originalStatus;
statusElement.className = 'badge badge-danger';
const errorStr = error.toString().toLowerCase();
const messageStr = (error.message || '').toLowerCase();
let errorMsg = '❌ Connection Test Results:\n\n';
errorMsg += testResults.join('\n') + '\n\n';
if (typeof qz === 'undefined') {
errorMsg += '❌ FAILED: Library Load Error\n\n';
errorMsg += '📚 The QZ Tray JavaScript library did not load.\n\n';
errorMsg += 'Fix:\n';
errorMsg += '• Check browser console for errors (F12)\n';
errorMsg += '• Refresh the page (F5)\n';
errorMsg += '• Check if library loaded:\n';
errorMsg += ' /static/qz-tray.js (patched version)';
} else if (messageStr.includes('unable to establish') ||
errorStr.includes('unable to establish') ||
messageStr.includes('failed to connect') ||
messageStr.includes('websocket') ||
errorStr.includes('websocket error')) {
errorMsg += '❌ FAILED: Cannot Connect to QZ Tray\n\n';
errorMsg += '🔌 QZ Tray is not running on this computer.\n\n';
errorMsg += 'Fix:\n';
errorMsg += '1. Check if QZ Tray is installed\n';
errorMsg += '2. Look in system tray (bottom-right) for QZ icon\n';
errorMsg += '3. If not there, launch QZ Tray app\n';
errorMsg += '4. Download: https://qz.io/download/\n\n';
errorMsg += '💡 QZ Tray must be running on THIS computer,\n';
errorMsg += ' not on the server!';
} else if (messageStr.includes('certificate') ||
errorStr.includes('certificate') ||
messageStr.includes('security')) {
errorMsg += '❌ FAILED: Certificate Error\n\n';
errorMsg += '🔒 Browser security blocking connection.\n\n';
errorMsg += 'Fix:\n';
errorMsg += '1. Open: https://localhost:8182\n';
errorMsg += '2. Click "Advanced" or "Proceed"\n';
errorMsg += '3. Accept the certificate\n';
errorMsg += '4. Return here and test again\n\n';
errorMsg += '💡 Self-signed certificates are normal for QZ Tray';
} else {
errorMsg += '❌ FAILED: Unexpected Error\n\n';
errorMsg += 'Error: ' + error.message + '\n\n';
errorMsg += 'Next steps:\n';
errorMsg += '1. Check browser console (F12)\n';
errorMsg += '2. Verify QZ Tray is running\n';
errorMsg += '3. Restart browser and QZ Tray\n';
errorMsg += '4. Check firewall settings';
}
console.log('========== TEST COMPLETED: FAILED ==========');
showNotification(errorMsg, 'error');
}
}
// Load available printers from QZ Tray // Load available printers from QZ Tray
async function loadQZTrayPrinters() { async function loadQZTrayPrinters() {
try { try {
@@ -543,7 +919,7 @@ async function loadQZTrayPrinters() {
// Generate PDF and send to thermal printer // Generate PDF and send to thermal printer
async function generatePDFAndPrint(selectedPrinter, orderData, pieceNumber, totalPieces) { async function generatePDFAndPrint(selectedPrinter, orderData, pieceNumber, totalPieces) {
try { try {
console.log('📄 Generating PDF for thermal printing...'); console.log(`📄 Generating PDF for thermal printing... (${pieceNumber}/${totalPieces})`);
// Prepare data for PDF generation // Prepare data for PDF generation
const pdfData = { const pdfData = {
@@ -553,37 +929,44 @@ async function generatePDFAndPrint(selectedPrinter, orderData, pieceNumber, tota
total_pieces: totalPieces total_pieces: totalPieces
}; };
// Generate PDF via server endpoint // Call backend to generate PDF
const response = await fetch('/generate_label_pdf', { const response = await fetch('/generate_label_pdf', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json'
}, },
body: JSON.stringify(pdfData) body: JSON.stringify(pdfData)
}); });
if (!response.ok) { if (!response.ok) {
throw new Error(`PDF generation failed: ${response.statusText}`); const errorData = await response.json();
throw new Error(errorData.error || 'Failed to generate PDF');
} }
// Get PDF as array buffer and convert to base64 // Get PDF as blob
const pdfArrayBuffer = await response.arrayBuffer(); const pdfBlob = await response.blob();
const pdfBase64 = arrayBufferToBase64(pdfArrayBuffer);
console.log('🖨️ Sending PDF to thermal printer with correct sizing...'); // Convert blob to base64 for QZ Tray
const pdfBase64 = await new Promise((resolve, reject) => {
// Create a more specific thermal printer configuration const reader = new FileReader();
const config = qz.configs.create(selectedPrinter, { reader.onloadend = () => {
margins: { top: 0, right: 0, bottom: 0, left: 0 }, // Remove data:application/pdf;base64, prefix
size: { const base64 = reader.result.split(',')[1];
width: 3.15, // 80mm = 3.15 inches resolve(base64);
height: 4.33, // 110mm = 4.33 inches };
units: 'in' reader.onerror = reject;
}, reader.readAsDataURL(pdfBlob);
orientation: 'portrait'
}); });
// Send PDF with explicit sizing for thermal labels - use raw base64 console.log(`🖨️ Sending PDF to printer ${selectedPrinter}...`);
// Configure QZ Tray for PDF printing
const config = qz.configs.create(selectedPrinter, {
scaleContent: false,
rasterize: false
});
// Prepare PDF data for QZ Tray
const data = [{ const data = [{
type: 'pdf', type: 'pdf',
format: 'base64', format: 'base64',
@@ -591,7 +974,7 @@ async function generatePDFAndPrint(selectedPrinter, orderData, pieceNumber, tota
}]; }];
await qz.print(config, data); await qz.print(config, data);
console.log('✅ PDF sent to printer successfully'); console.log(`✅ PDF sent to printer successfully (${pieceNumber}/${totalPieces})`);
} catch (error) { } catch (error) {
console.error('❌ Error generating/printing PDF:', error); console.error('❌ Error generating/printing PDF:', error);
@@ -895,6 +1278,11 @@ function generateHTMLLabel(orderData, pieceNumber, totalPieces) {
// Handle QZ Tray printing // Handle QZ Tray printing
async function handleQZTrayPrint(selectedRow) { async function handleQZTrayPrint(selectedRow) {
const modal = document.getElementById('print-progress-modal');
const progressBar = document.getElementById('progress-bar');
const progressText = document.getElementById('progress-text');
const progressCount = document.getElementById('progress-count');
try { try {
if (!qzTray) { if (!qzTray) {
await initializeQZTray(); await initializeQZTray();
@@ -927,43 +1315,76 @@ async function handleQZTrayPrint(selectedRow) {
const quantity = orderData.cantitate; const quantity = orderData.cantitate;
console.log(`🖨️ Printing ${quantity} labels via QZ Tray to ${selectedPrinter}`); console.log(`🖨️ Printing ${quantity} labels via QZ Tray to ${selectedPrinter}`);
const button = document.getElementById('print-label-btn'); // Show progress modal
const originalText = button.textContent; modal.style.display = 'flex';
button.textContent = `Printing 0/${quantity}...`; progressText.textContent = 'Preparing to print...';
button.disabled = true; progressBar.style.width = '0%';
progressCount.textContent = `0 / ${quantity}`;
try { try {
// Print each label sequentially // Print each label sequentially
for (let i = 1; i <= quantity; i++) { for (let i = 1; i <= quantity; i++) {
button.textContent = `Printing ${i}/${quantity}...`; progressText.textContent = `Printing label ${i} of ${quantity}...`;
progressCount.textContent = `${i - 1} / ${quantity}`;
progressBar.style.width = `${((i - 1) / quantity) * 100}%`;
// Generate PDF and send to printer // Generate PDF and send to printer
await generatePDFAndPrint(selectedPrinter, orderData, i, quantity); await generatePDFAndPrint(selectedPrinter, orderData, i, quantity);
// Update progress after successful print
progressCount.textContent = `${i} / ${quantity}`;
progressBar.style.width = `${(i / quantity) * 100}%`;
// Small delay between labels for printer processing // Small delay between labels for printer processing
if (i < quantity) { if (i < quantity) {
await new Promise(resolve => setTimeout(resolve, 500)); await new Promise(resolve => setTimeout(resolve, 500));
} }
} }
// All labels printed successfully
progressText.textContent = '✅ All labels printed! Updating database...';
// Update database to mark order as printed
try {
const updateResponse = await fetch(`/update_printed_status/${orderData.id}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
});
if (!updateResponse.ok) {
console.error('Failed to update printed status in database');
progressText.textContent = '⚠️ Labels printed but database update failed';
await new Promise(resolve => setTimeout(resolve, 2000));
} else {
progressText.textContent = '✅ Complete! Refreshing table...';
await new Promise(resolve => setTimeout(resolve, 1000));
}
} catch (dbError) {
console.error('Database update error:', dbError);
progressText.textContent = '⚠️ Labels printed but database update failed';
await new Promise(resolve => setTimeout(resolve, 2000));
}
// Close modal
modal.style.display = 'none';
// Show success notification
showNotification(`✅ Successfully printed ${quantity} labels!`, 'success'); showNotification(`✅ Successfully printed ${quantity} labels!`, 'success');
// Mark order as printed and refresh table // Refresh table to show updated status
setTimeout(() => { document.getElementById('check-db-btn').click();
document.getElementById('check-db-btn').click();
}, 1000);
} catch (printError) { } catch (printError) {
modal.style.display = 'none';
throw new Error(`Print failed: ${printError.message}`); throw new Error(`Print failed: ${printError.message}`);
} }
} catch (error) { } catch (error) {
modal.style.display = 'none';
console.error('QZ Tray print error:', error); console.error('QZ Tray print error:', error);
showNotification('❌ QZ Tray print error: ' + error.message, 'error'); showNotification('❌ QZ Tray print error: ' + error.message, 'error');
} finally {
const button = document.getElementById('print-label-btn');
button.textContent = originalText;
button.disabled = false;
} }
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,7 @@
[
{
"printer_name": "test 1",
"pairing_key": "fanlCYDshm8exebw5gP1Se_l0BR37mwV6FbogxZUE2w",
"warranty_until": "2026-09-30"
}
]