diff --git a/src/documentprinter/documentprinterclient.js b/src/documentprinter/documentprinterclient.js index b121711..a6ffd89 100644 --- a/src/documentprinter/documentprinterclient.js +++ b/src/documentprinter/documentprinterclient.js @@ -22,6 +22,7 @@ export class DocumentPrinterClient { this.documentPrinter = documentPrinter; this.connection = documentPrinter.connection; this.queue = []; + this.jobQuantities = new Map(); this.documentPrinterManager = documentPrinterManager; this.currentJob = null; this.active = documentPrinter.active == true; @@ -297,6 +298,14 @@ export class DocumentPrinterClient { } } + resolvePrintQuantity(quantity) { + const parsed = Number(quantity); + if (!Number.isFinite(parsed) || parsed < 1) { + return 1; + } + return Math.floor(parsed); + } + async updateJobState(jobId, state) { logger.info(`Updating job state for ${jobId}`); const notify = @@ -389,6 +398,10 @@ export class DocumentPrinterClient { type: "queued", progress: null, }); + this.jobQuantities.set( + documentJob._id, + this.resolvePrintQuantity(documentJob.quantity), + ); // Only add job to queue if it's not already there if (!this.queue.includes(documentJob._id)) { this.queue.push(documentJob._id); @@ -512,15 +525,21 @@ export class DocumentPrinterClient { } try { - logger.info(`Printing job ${jobId} for ${this.documentPrinter.name}`); + const quantity = this.resolvePrintQuantity( + this.jobQuantities.get(jobId), + ); + logger.info( + `Printing job ${jobId} (${quantity} ${quantity === 1 ? "copy" : "copies"}) for ${this.documentPrinter.name}`, + ); await this.updateJobState(jobId, { type: "printing" }); - await this.printerInterface.print(jobId); + await this.printerInterface.print(jobId, quantity); logger.info( - `Successfully printed job ${jobId} for ${this.documentPrinter.name}`, + `Successfully printed job ${jobId} (${quantity} ${quantity === 1 ? "copy" : "copies"}) for ${this.documentPrinter.name}`, ); // Only remove job from queue after successful printing this.queue.shift(); + this.jobQuantities.delete(jobId); await this.updateJobState(jobId, { type: "complete" }); } catch (error) { logger.error( @@ -531,6 +550,7 @@ export class DocumentPrinterClient { // Remove failed job from queue to prevent infinite retry loop // You may want to implement retry logic or error handling here this.queue.shift(); + this.jobQuantities.delete(jobId); await this.updateJobState(jobId, { type: "failed" }); // Continue with next job even if one fails } @@ -561,6 +581,7 @@ export class DocumentPrinterClient { this.state = { type: this.active == false ? "inactive" : "offline" }; this.isProcessingQueue = false; this.queue = []; // Clear queue on disconnect + this.jobQuantities.clear(); await this.updateDocumentPrinterState(); clearTimeout(this.reconnectTimeout); logger.info(`Successfully disconnected from ${this.documentPrinter.name}`); diff --git a/src/documentprinter/interfaces/cupsinterface.js b/src/documentprinter/interfaces/cupsinterface.js index 846bca3..8d4bf56 100644 --- a/src/documentprinter/interfaces/cupsinterface.js +++ b/src/documentprinter/interfaces/cupsinterface.js @@ -253,8 +253,11 @@ export default class CupsInterface { return { success: true }; } - async print(jobId) { - logger.info(`Printing job ${jobId} to CUPS printer ${this.name}`); + async print(jobId, quantity = 1) { + const copies = Math.max(1, Math.floor(Number(quantity) || 1)); + logger.info( + `Printing job ${jobId} (${copies} ${copies === 1 ? "copy" : "copies"}) to CUPS printer ${this.name}` + ); if (!this.isConnected || !this.cupsPrinter) { throw new Error("Printer is not connected"); @@ -295,22 +298,28 @@ export default class CupsInterface { data: documentData, }; - // Send print job - logger.debug(`Sending print job ${jobId} to ${this.cupsPrinterUrl}`); - const response = await this.executeIPP("Print-Job", printJobMessage); - - // Extract job ID from response - const ippJobId = response["job-attributes-tag"]?.["job-id"]; - const jobUri = response["job-attributes-tag"]?.["job-uri"]; + // Send one print job per copy so quantity is honored even when the + // printer does not support the IPP copies attribute. + logger.debug( + `Sending print job ${jobId} (${copies} ${copies === 1 ? "copy" : "copies"}) to ${this.cupsPrinterUrl}` + ); + let ippJobId; + let jobUri; + for (let copy = 0; copy < copies; copy++) { + const response = await this.executeIPP("Print-Job", printJobMessage); + ippJobId = response["job-attributes-tag"]?.["job-id"]; + jobUri = response["job-attributes-tag"]?.["job-uri"]; + } logger.info( - `Successfully printed job ${jobId} to CUPS printer ${this.name}. IPP Job ID: ${ippJobId}` + `Successfully printed job ${jobId} (${copies} ${copies === 1 ? "copy" : "copies"}) to CUPS printer ${this.name}. IPP Job ID: ${ippJobId}` ); return { success: true, jobId: ippJobId, jobUri: jobUri, + copies, }; } catch (error) { logger.error( diff --git a/src/documentprinter/interfaces/receiptinterface.js b/src/documentprinter/interfaces/receiptinterface.js index b8bee68..838d1ea 100644 --- a/src/documentprinter/interfaces/receiptinterface.js +++ b/src/documentprinter/interfaces/receiptinterface.js @@ -120,8 +120,11 @@ export default class ReceiptInterface { return true; } - async print(jobId) { - logger.info(`Printing job ${jobId} to receipt printer ${this.name}`); + async print(jobId, quantity = 1) { + const copies = Math.max(1, Math.floor(Number(quantity) || 1)); + logger.info( + `Printing job ${jobId} (${copies} ${copies === 1 ? "copy" : "copies"}) to receipt printer ${this.name}` + ); if (!this.isConnected || !this.receiptPrinter) { throw new Error("Printer is not connected"); @@ -138,22 +141,24 @@ export default class ReceiptInterface { const imageArray = Array.isArray(images) ? images : [images]; - for (const image of imageArray) { - const imageBuffer = Buffer.isBuffer(image) - ? Buffer.from(image) - : Buffer.from(image); - await this.receiptPrinter.printImageBuffer(imageBuffer); - this.receiptPrinter.cut(); + for (let copy = 0; copy < copies; copy++) { + for (const image of imageArray) { + const imageBuffer = Buffer.isBuffer(image) + ? Buffer.from(image) + : Buffer.from(image); + await this.receiptPrinter.printImageBuffer(imageBuffer); + this.receiptPrinter.cut(); + } } // Execute the print job await this.receiptPrinter.execute({ waitForResponse: true }); logger.info( - `Successfully printed job ${jobId} to receipt printer ${this.name}` + `Successfully printed job ${jobId} (${copies} ${copies === 1 ? "copy" : "copies"}) to receipt printer ${this.name}` ); - return { success: true }; + return { success: true, copies }; } catch (error) { logger.error( `Failed to print job ${jobId} to receipt printer ${this.name}:`,