Files
quality_app-v2/app/static/js/qz-printer.js
Quality App Developer e1f3302c6b Implement boxes management module with auto-numbered box creation
- Add boxes_crates database table with BIGINT IDs and 8-digit auto-numbered box_numbers
- Implement boxes CRUD operations (add, edit, update, delete, delete_multiple)
- Create boxes route handlers with POST actions for all operations
- Add boxes.html template with 3-panel layout matching warehouse locations module
- Implement barcode generation and printing with JsBarcode and QZ Tray integration
- Add browser print fallback for when QZ Tray is not available
- Simplify create box form to single button with auto-generation
- Fix JavaScript null reference errors with proper element validation
- Convert tuple data to dictionaries for Jinja2 template compatibility
- Register boxes blueprint in Flask app initialization
2026-01-26 22:08:31 +02:00

296 lines
11 KiB
JavaScript

/**
* QZ Tray Printer Module
* Shared printer functionality for all pages
* Provides printer detection, selection, and printing capabilities
*/
(function() {
'use strict';
// Global printer state
window.qzPrinter = {
connected: false,
availablePrinters: [],
selectedPrinter: '',
/**
* Initialize QZ Tray connection
* @returns {Promise<boolean>} True if connected, false otherwise
*/
initialize: async function() {
try {
console.log('Initializing QZ Tray...');
if (typeof qz === 'undefined') {
console.warn('QZ Tray library not loaded');
return false;
}
// Try to connect
await qz.websocket.connect();
this.connected = true;
console.log('✅ QZ Tray connected');
// Load available printers
await this.loadPrinters();
return true;
} catch (error) {
console.warn('QZ Tray not available:', error.message);
this.connected = false;
return false;
}
},
/**
* Load available printers from QZ Tray
* @returns {Promise<Array>} Array of printer names
*/
loadPrinters: async function() {
try {
if (!this.connected) return [];
const printers = await qz.printers.find();
this.availablePrinters = printers;
console.log('Loaded printers:', printers);
// Auto-select first thermal printer if available
const thermalPrinter = printers.find(p =>
p.toLowerCase().includes('thermal') ||
p.toLowerCase().includes('label') ||
p.toLowerCase().includes('zebra')
);
if (thermalPrinter) {
this.selectedPrinter = thermalPrinter;
console.log('Auto-selected thermal printer:', thermalPrinter);
}
return printers;
} catch (error) {
console.error('Error loading printers:', error);
return [];
}
},
/**
* Get printer selection dropdown HTML
* @param {string} selectId - ID for the select element
* @returns {string} HTML string for printer select dropdown
*/
getPrinterSelectHTML: function(selectId = 'printer-select') {
let html = `<select id="${selectId}" class="form-select form-select-sm">
<option value="">Default Printer</option>`;
this.availablePrinters.forEach(printer => {
const selected = printer === this.selectedPrinter ? ' selected' : '';
html += `<option value="${printer}"${selected}>${printer}</option>`;
});
html += '</select>';
return html;
},
/**
* Update printer selection
* @param {string} printerName - Name of printer to select
*/
selectPrinter: function(printerName) {
this.selectedPrinter = printerName || '';
console.log('Selected printer:', this.selectedPrinter);
},
/**
* Test QZ Tray connection
* @returns {boolean} True if connected
*/
test: function() {
if (!this.connected) {
alert('QZ Tray is not connected.\nBrowser print will be used instead.');
return false;
}
const printerList = this.availablePrinters.length > 0
? this.availablePrinters.join('\n• ')
: 'No printers found';
alert('✅ QZ Tray is connected and ready!\n\nAvailable printers:\n• ' + printerList);
return true;
},
/**
* Print barcode using QZ Tray
* @param {string} barcodeData - Barcode value
* @param {string} printerName - Printer to use (optional, uses selected)
* @param {Object} options - Print options
* @returns {Promise<void>}
*/
printBarcode: async function(barcodeData, printerName, options = {}) {
try {
if (!this.connected) {
throw new Error('QZ Tray not connected');
}
const targetPrinter = printerName || this.selectedPrinter;
console.log('Printing to:', targetPrinter, 'Data:', barcodeData);
// Default print options
const printConfig = {
printer: targetPrinter,
colorType: options.colorType || 'color',
copies: options.copies || 1
};
// Barcode data with default configuration
const printData = [{
type: 'barcode',
format: options.format || 'CODE128',
data: barcodeData,
width: options.width || 2,
height: options.height || 100,
displayValue: options.displayValue !== false
}];
// Add optional label
if (options.label) {
printData.push({
type: 'text',
data: options.label,
position: options.labelPosition || {x: 0.5, y: 2.2},
font: options.font || {family: 'Arial', size: 12, weight: 'bold'}
});
}
// Send to printer
await qz.print(printConfig, printData);
console.log('✅ Print job sent to', targetPrinter);
return true;
} catch (error) {
console.error('QZ Tray printing error:', error);
throw error;
}
},
/**
* Print SVG/HTML barcode using QZ Tray
* @param {string} svgElement - SVG element or selector
* @param {string} barcodeText - Text to display with barcode
* @param {string} printerName - Printer to use (optional)
* @returns {Promise<void>}
*/
printSVGBarcode: async function(svgElement, barcodeText, printerName) {
try {
if (!this.connected) {
throw new Error('QZ Tray not connected');
}
// If no printer specified, use the selected printer or default
let targetPrinter = printerName || this.selectedPrinter;
// Get SVG element
let svgEl = typeof svgElement === 'string'
? document.querySelector(svgElement)
: svgElement;
if (!svgEl) {
throw new Error('Barcode SVG element not found');
}
// Serialize SVG to string and encode as base64
const svgString = new XMLSerializer().serializeToString(svgEl);
const svgBase64 = btoa(svgString);
// If still no printer, get the device default
if (!targetPrinter) {
try {
const defaultPrinter = await qz.printers.getDefault();
targetPrinter = defaultPrinter;
console.log('Using device default printer:', targetPrinter);
} catch (err) {
console.warn('Could not get default printer, using system default');
targetPrinter = ''; // Empty string uses system default
}
}
const printConfig = {
printer: targetPrinter,
colorType: 'color',
copies: 1
};
const printData = [{
type: 'image',
format: 'base64',
data: svgBase64,
width: 3,
height: 1.5,
position: {x: 0.5, y: 0.5}
}];
if (barcodeText) {
printData.push({
type: 'text',
data: barcodeText,
position: {x: 0.5, y: 2.2},
font: {family: 'Arial', size: 12, weight: 'bold'}
});
}
console.log('Printing to thermal printer:', targetPrinter);
await qz.print(printConfig, printData);
console.log('✅ SVG print job sent to', targetPrinter);
return true;
} catch (error) {
console.error('QZ Tray SVG printing error:', error);
throw error;
}
},
/**
* Fallback to browser print
* @param {string} title - Print document title
* @param {string} content - HTML content to print
*/
printBrowser: function(title, content) {
const printWindow = window.open('', '', 'height=400,width=600');
printWindow.document.write('<html><head><title>' + title + '</title>');
printWindow.document.write('<style>');
printWindow.document.write('body { font-family: Arial, sans-serif; text-align: center; padding: 20px; }');
printWindow.document.write('h2 { margin-bottom: 30px; }');
printWindow.document.write('svg { max-width: 100%; height: auto; margin: 20px 0; }');
printWindow.document.write('.content { margin: 20px 0; }');
printWindow.document.write('</style></head><body>');
printWindow.document.write('<h2>' + title + '</h2>');
printWindow.document.write('<div class="content">' + content + '</div>');
printWindow.document.write('<p style="margin-top: 40px; font-size: 14px; color: #666;">');
printWindow.document.write('Printed on: ' + new Date().toLocaleString());
printWindow.document.write('</p></body></html>');
printWindow.document.close();
setTimeout(() => {
printWindow.print();
}, 250);
}
};
// Auto-initialize when document is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function() {
if (typeof qz !== 'undefined') {
window.qzPrinter.initialize();
}
});
} else {
// Document already loaded
if (typeof qz !== 'undefined') {
window.qzPrinter.initialize();
}
}
})();