Enhance printing functionality in DocumentPrinterClient and interfaces
All checks were successful
farmcontrol/farmcontrol-server/pipeline/head This commit looks good

- Introduced job quantity handling in `DocumentPrinterClient` to manage multiple copies of print jobs.
- Added `resolvePrintQuantity` method to ensure valid print quantities.
- Updated `CupsInterface` and `ReceiptInterface` to accept a quantity parameter in the `print` method, allowing for multiple copies to be printed in a single call.
- Improved logging to reflect the number of copies being printed for better user feedback.
This commit is contained in:
Tom Butcher 2026-08-22 20:02:53 +01:00
parent 0dc7ed3f92
commit bb9b472559
3 changed files with 58 additions and 23 deletions

View File

@ -22,6 +22,7 @@ export class DocumentPrinterClient {
this.documentPrinter = documentPrinter; this.documentPrinter = documentPrinter;
this.connection = documentPrinter.connection; this.connection = documentPrinter.connection;
this.queue = []; this.queue = [];
this.jobQuantities = new Map();
this.documentPrinterManager = documentPrinterManager; this.documentPrinterManager = documentPrinterManager;
this.currentJob = null; this.currentJob = null;
this.active = documentPrinter.active == true; 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) { async updateJobState(jobId, state) {
logger.info(`Updating job state for ${jobId}`); logger.info(`Updating job state for ${jobId}`);
const notify = const notify =
@ -389,6 +398,10 @@ export class DocumentPrinterClient {
type: "queued", type: "queued",
progress: null, progress: null,
}); });
this.jobQuantities.set(
documentJob._id,
this.resolvePrintQuantity(documentJob.quantity),
);
// Only add job to queue if it's not already there // Only add job to queue if it's not already there
if (!this.queue.includes(documentJob._id)) { if (!this.queue.includes(documentJob._id)) {
this.queue.push(documentJob._id); this.queue.push(documentJob._id);
@ -512,15 +525,21 @@ export class DocumentPrinterClient {
} }
try { 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.updateJobState(jobId, { type: "printing" });
await this.printerInterface.print(jobId); await this.printerInterface.print(jobId, quantity);
logger.info( 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 // Only remove job from queue after successful printing
this.queue.shift(); this.queue.shift();
this.jobQuantities.delete(jobId);
await this.updateJobState(jobId, { type: "complete" }); await this.updateJobState(jobId, { type: "complete" });
} catch (error) { } catch (error) {
logger.error( logger.error(
@ -531,6 +550,7 @@ export class DocumentPrinterClient {
// Remove failed job from queue to prevent infinite retry loop // Remove failed job from queue to prevent infinite retry loop
// You may want to implement retry logic or error handling here // You may want to implement retry logic or error handling here
this.queue.shift(); this.queue.shift();
this.jobQuantities.delete(jobId);
await this.updateJobState(jobId, { type: "failed" }); await this.updateJobState(jobId, { type: "failed" });
// Continue with next job even if one fails // Continue with next job even if one fails
} }
@ -561,6 +581,7 @@ export class DocumentPrinterClient {
this.state = { type: this.active == false ? "inactive" : "offline" }; this.state = { type: this.active == false ? "inactive" : "offline" };
this.isProcessingQueue = false; this.isProcessingQueue = false;
this.queue = []; // Clear queue on disconnect this.queue = []; // Clear queue on disconnect
this.jobQuantities.clear();
await this.updateDocumentPrinterState(); await this.updateDocumentPrinterState();
clearTimeout(this.reconnectTimeout); clearTimeout(this.reconnectTimeout);
logger.info(`Successfully disconnected from ${this.documentPrinter.name}`); logger.info(`Successfully disconnected from ${this.documentPrinter.name}`);

View File

@ -253,8 +253,11 @@ export default class CupsInterface {
return { success: true }; return { success: true };
} }
async print(jobId) { async print(jobId, quantity = 1) {
logger.info(`Printing job ${jobId} to CUPS printer ${this.name}`); 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) { if (!this.isConnected || !this.cupsPrinter) {
throw new Error("Printer is not connected"); throw new Error("Printer is not connected");
@ -295,22 +298,28 @@ export default class CupsInterface {
data: documentData, data: documentData,
}; };
// Send print job // Send one print job per copy so quantity is honored even when the
logger.debug(`Sending print job ${jobId} to ${this.cupsPrinterUrl}`); // printer does not support the IPP copies attribute.
const response = await this.executeIPP("Print-Job", printJobMessage); logger.debug(
`Sending print job ${jobId} (${copies} ${copies === 1 ? "copy" : "copies"}) to ${this.cupsPrinterUrl}`
// Extract job ID from response );
const ippJobId = response["job-attributes-tag"]?.["job-id"]; let ippJobId;
const jobUri = response["job-attributes-tag"]?.["job-uri"]; 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( 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 { return {
success: true, success: true,
jobId: ippJobId, jobId: ippJobId,
jobUri: jobUri, jobUri: jobUri,
copies,
}; };
} catch (error) { } catch (error) {
logger.error( logger.error(

View File

@ -120,8 +120,11 @@ export default class ReceiptInterface {
return true; return true;
} }
async print(jobId) { async print(jobId, quantity = 1) {
logger.info(`Printing job ${jobId} to receipt printer ${this.name}`); 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) { if (!this.isConnected || !this.receiptPrinter) {
throw new Error("Printer is not connected"); throw new Error("Printer is not connected");
@ -138,22 +141,24 @@ export default class ReceiptInterface {
const imageArray = Array.isArray(images) ? images : [images]; const imageArray = Array.isArray(images) ? images : [images];
for (const image of imageArray) { for (let copy = 0; copy < copies; copy++) {
const imageBuffer = Buffer.isBuffer(image) for (const image of imageArray) {
? Buffer.from(image) const imageBuffer = Buffer.isBuffer(image)
: Buffer.from(image); ? Buffer.from(image)
await this.receiptPrinter.printImageBuffer(imageBuffer); : Buffer.from(image);
this.receiptPrinter.cut(); await this.receiptPrinter.printImageBuffer(imageBuffer);
this.receiptPrinter.cut();
}
} }
// Execute the print job // Execute the print job
await this.receiptPrinter.execute({ waitForResponse: true }); await this.receiptPrinter.execute({ waitForResponse: true });
logger.info( 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) { } catch (error) {
logger.error( logger.error(
`Failed to print job ${jobId} to receipt printer ${this.name}:`, `Failed to print job ${jobId} to receipt printer ${this.name}:`,