WebSocket Connection:
qz.websocket.connect()- Establishes connection to QZ Trayqz.websocket.isActive()- Checks if connection is active
Security/Authentication:
qz.security.setCertificatePromise()- Sets up certificate authenticationqz.security.setSignaturePromise()- Sets up signature authenticationqz.security.setSignatureAlgorithm("SHA512")- Sets signing algorithm
Printer Management:
qz.printers.details()- Discovers available printersqz.printers.setPrinterCallbacks()- Sets up basic printer callbacks (limited use)
Print Operations:
qz.configs.create()- Creates printer configurationqz.print()- Sends print jobs (fire-and-forget approach)
- No Print Job Tracking: Current system marks jobs as "printed" before actually printing
- No Status Feedback: No real-time printer status monitoring
- No Error Recovery: Failed jobs are lost, no retry mechanism
- Bulk Processing: Processes all jobs at once without sequencing
- No Connection Status Display: No visual feedback on QZ connection status
- Limited Printer Status: Basic printer callbacks, no comprehensive status monitoring
-- Add print server designation to machines table
ALTER TABLE machines ADD COLUMN is_print_server BOOLEAN DEFAULT false;
-- Add QZ connection tracking
ALTER TABLE machines ADD COLUMN qz_connection_status ENUM('connected', 'disconnected', 'error') DEFAULT 'disconnected';
ALTER TABLE machines ADD COLUMN qz_last_seen_at TIMESTAMP NULL;- Add
is_print_serverboolean field - Add
qz_connection_statusenum field - Add
qz_last_seen_attimestamp field - Add method to get pending print job count for this machine
Printer Status Monitoring:
// Set up comprehensive printer status callbacks
qz.printers.setPrinterCallbacks((evt) => {
// Handle events: "JOB" and "PRINTER" types
// Process severity levels: FATAL, ERROR, WARN, INFO
// Track specific printer status changes
});
// Start listening to specific printers
qz.printers.startListening("PrinterName");
// Get current printer status
qz.printers.getStatus("PrinterName");
// Stop listening when no longer needed
qz.printers.stopListening("PrinterName");Enhanced Print Job Management:
// Sequential print job processing with proper tracking
function processNextPrintJob() {
if (currentPrintJob) return; // One job at a time
const job = getNextPendingJob();
if (!job) return;
currentPrintJob = job;
updateJobStatus(job.id, 'printing');
qz.print(config, data)
.then(() => {
updateJobStatus(job.id, 'printed');
currentPrintJob = null;
processNextPrintJob(); // Process next job
})
.catch((error) => {
handlePrintError(job, error);
currentPrintJob = null;
});
}Print Server Status Component:
<template>
<div class="print-server-status">
<!-- QZ Connection Status -->
<div class="connection-status">
<span class="status-dot" :class="qzStatus"></span>
QZ Print: {{ qzStatusText }}
</div>
<!-- Pending Jobs Counter -->
<div class="pending-jobs" v-if="pendingJobCount > 0">
<Icon name="printer" />
{{ pendingJobCount }} pending jobs
</div>
<!-- Current Print Job -->
<div class="current-job" v-if="currentJob">
Printing: {{ currentJob.name }}
</div>
</div>
</template>enum PrintJobStatusEnum: string
{
case Pending = 'pending';
case Queued = 'queued'; // NEW: In client queue
case Printing = 'printing'; // NEW: Currently printing
case Printed = 'printed';
case Failed = 'failed'; // NEW: Print failed
case Cancelled = 'cancelled'; // NEW: Job cancelled
}class PrintJob extends Model
{
// Add new fields
protected $fillable = [
'printer_id',
'printable_type',
'printable_id',
'type',
'status',
'file',
'printed_at',
'queued_at', // NEW: When job entered client queue
'started_at', // NEW: When printing started
'failed_at', // NEW: When job failed
'error_message', // NEW: Error details
'retry_count', // NEW: Number of retry attempts
'machine_id', // NEW: Which machine is processing
];
// Add status transition methods
public function markAsQueued(): void;
public function markAsPrinting(): void;
public function markAsPrinted(): void;
public function markAsFailed(string $error): void;
}function handlePrinterStatus(evt) {
if (evt.eventType === 'PRINTER') {
switch(evt.statusCode) {
case 'media-empty':
case 'media-jam':
case 'offline':
pausePrintQueue();
showPrinterError(evt.printerName, evt.message);
break;
case 'online':
resumePrintQueue();
clearPrinterError(evt.printerName);
break;
}
}
}
function handleJobStatus(evt) {
if (evt.eventType === 'JOB') {
const job = findJobByName(evt.jobName);
if (job) {
if (evt.severity === 'ERROR' || evt.severity === 'FATAL') {
markJobAsFailed(job.id, evt.message);
}
}
}
}async function retryFailedJob(job, maxRetries = 3) {
if (job.retryCount >= maxRetries) {
markJobAsPermanentlyFailed(job.id);
return;
}
// Wait before retry (exponential backoff)
const delay = Math.pow(2, job.retryCount) * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
incrementRetryCount(job.id);
attemptPrintJob(job);
}// Machine status updates
Route::post('/machines/{machine}/qz-status', [MachineController::class, 'updateQzStatus']);
Route::get('/machines/{machine}/print-stats', [MachineController::class, 'getPrintStats']);
// Enhanced print job management
Route::post('/printers/jobs/{job}/queued', [PrinterController::class, 'markJobAsQueued']);
Route::post('/printers/jobs/{job}/printing', [PrinterController::class, 'markJobAsPrinting']);
Route::post('/printers/jobs/{job}/failed', [PrinterController::class, 'markJobAsFailed']);
Route::post('/printers/jobs/{job}/retry', [PrinterController::class, 'retryJob']);
// Printer status updates
Route::post('/printers/{printer}/status', [PrinterController::class, 'updatePrinterStatus']);{
"data": [
{
"id": 123,
"printer": "Badge_Printer_01",
"type": "badge",
"file": "https://s3-url...",
"status": "pending",
"created_at": "2024-01-01T12:00:00Z",
"queued_at": null,
"started_at": null,
"retry_count": 0,
"paper": {...},
"duplex": true,
"priority": 1
}
],
"meta": {
"total_pending": 5,
"total_printing": 1,
"printer_status": {
"Badge_Printer_01": "online",
"Receipt_Printer_01": "media-empty"
}
}
}class PrintQueueManager {
constructor() {
this.currentJobs = new Map(); // printer -> current job
this.printerStatus = new Map(); // printer -> status
this.queuePaused = false;
}
async processQueue() {
if (this.queuePaused) return;
const availablePrinters = this.getAvailablePrinters();
for (const printer of availablePrinters) {
if (!this.currentJobs.has(printer)) {
const job = await this.getNextJobForPrinter(printer);
if (job) {
this.startPrintJob(printer, job);
}
}
}
}
async startPrintJob(printer, job) {
this.currentJobs.set(printer, job);
try {
await this.markJobAsQueued(job.id);
await this.markJobAsPrinting(job.id);
const config = qz.configs.create(printer);
const data = [{ type: 'pixel', format: 'pdf', flavor: 'file', data: job.file }];
await qz.print(config, data);
await this.markJobAsPrinted(job.id);
} catch (error) {
await this.markJobAsFailed(job.id, error.message);
this.scheduleRetry(job);
} finally {
this.currentJobs.delete(printer);
}
}
}function broadcastMachineStatus() {
const status = {
machine_id: MACHINE_ID,
qz_connected: qz.websocket.isActive(),
pending_jobs: Object.keys(pendingJobs).length,
current_jobs: Array.from(currentJobs.values()),
printer_status: Object.fromEntries(printerStatus),
last_updated: new Date().toISOString()
};
fetch('/pos/auth/machines/status', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(status)
});
}
// Broadcast status every 30 seconds
setInterval(broadcastMachineStatus, 30000);- Database schema updates (print job statuses, machine fields)
- Enhanced PrintJob model with new statuses
- Basic UI status components
- Sequential print job processing
- Enhanced QZ.io printer status monitoring
- Real-time status updates to server
- Retry logic implementation
- Printer error detection and handling
- Print queue pause/resume functionality
- Print job prioritization
- Advanced printer diagnostics
- Historical print job analytics
- Print server load balancing
- Reliability: No more lost print jobs, proper error handling
- Visibility: Real-time status of print operations
- Troubleshooting: Clear error messages and retry capabilities
- Performance: Sequential processing prevents printer overload
- Scalability: Print server designation allows distributed printing
- User Experience: Visual feedback on print system status
- Unit Tests: Print job state transitions and retry logic
- Integration Tests: QZ.io API interactions and error scenarios
- Load Testing: Multiple simultaneous print jobs
- Failure Testing: Printer offline, out of ink, paper jam scenarios
- Recovery Testing: Print server restart, network interruption scenarios