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.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}`);

View File

@ -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(

View File

@ -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}:`,