it prints
This commit is contained in:
6
.gitignore
vendored
6
.gitignore
vendored
@@ -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
|
|
||||||
125
py_app/PRINT_PROGRESS_FEATURE.md
Normal file
125
py_app/PRINT_PROGRESS_FEATURE.md
Normal 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
|
||||||
Binary file not shown.
44
py_app/app/forked_tray.py
Normal file
44
py_app/app/forked_tray.py
Normal 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)
|
||||||
@@ -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')
|
||||||
|
|||||||
35
py_app/app/static/qz-tray-PATCH-NOTES.txt
Normal file
35
py_app/app/static/qz-tray-PATCH-NOTES.txt
Normal 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
2871
py_app/app/static/qz-tray.js
Normal file
File diff suppressed because it is too large
Load Diff
2859
py_app/app/static/qz-tray.js.backup
Normal file
2859
py_app/app/static/qz-tray.js.backup
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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;">×</button>
|
<button type="button" onclick="this.parentElement.parentElement.remove()" style="background: none; border: none; font-size: 20px; cursor: pointer; flex-shrink: 0;">×</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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
1383
py_app/app/templates/print_module.html.backup
Normal file
1383
py_app/app/templates/print_module.html.backup
Normal file
File diff suppressed because it is too large
Load Diff
1374
py_app/app/templates/print_module.html.backup2
Normal file
1374
py_app/app/templates/print_module.html.backup2
Normal file
File diff suppressed because it is too large
Load Diff
7
py_app/instance/pairing_keys.json
Normal file
7
py_app/instance/pairing_keys.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"printer_name": "test 1",
|
||||||
|
"pairing_key": "fanlCYDshm8exebw5gP1Se_l0BR37mwV6FbogxZUE2w",
|
||||||
|
"warranty_until": "2026-09-30"
|
||||||
|
}
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user