201 lines
5.7 KiB
TypeScript
201 lines
5.7 KiB
TypeScript
import { Printer } from '@node-escpos/core';
|
|
import USB from '@node-escpos/usb-adapter';
|
|
import { Task, Step, Printer as PrinterInterface } from '@shared/index';
|
|
import { StepRepository, PrintHistoryRepository } from '../db/repositories';
|
|
import { Knex } from 'knex';
|
|
import logger from '../logger';
|
|
import { formatUtils } from './format-utils';
|
|
import {
|
|
PRINTER_CONFIG,
|
|
FONT_SIZES,
|
|
BARCODE_CONFIG,
|
|
PAPER_CONFIG,
|
|
ALIGNMENT,
|
|
FONT,
|
|
STYLE,
|
|
} from './printer-constants';
|
|
|
|
export class SerialPrinter implements PrinterInterface {
|
|
private device: USB | null = null;
|
|
private printer: Printer<[]> | null = null;
|
|
private printHistoryRepo: PrintHistoryRepository;
|
|
private stepRepository: StepRepository;
|
|
|
|
constructor(printHistoryRepo: PrintHistoryRepository, stepRepo: StepRepository) {
|
|
this.printHistoryRepo = printHistoryRepo;
|
|
this.stepRepository = stepRepo;
|
|
this.initializeDevice();
|
|
}
|
|
|
|
private initializeDevice() {
|
|
try {
|
|
this.device = new USB();
|
|
const options = { encoding: PRINTER_CONFIG.ENCODING };
|
|
this.printer = new Printer(this.device, options);
|
|
logger.info('Printer device initialized successfully');
|
|
} catch (error) {
|
|
logger.error('Failed to initialize printer device:', error);
|
|
}
|
|
}
|
|
|
|
private async openPrinter(): Promise<void> {
|
|
if (!this.device) {
|
|
throw new Error('Printer device not initialized');
|
|
}
|
|
|
|
try {
|
|
await new Promise<void>((resolve, reject) => {
|
|
this.device?.open((err) => {
|
|
if (err) {
|
|
logger.error('Failed to open printer:', err);
|
|
reject(err);
|
|
return;
|
|
}
|
|
resolve();
|
|
});
|
|
});
|
|
logger.info('Printer opened successfully');
|
|
} catch (error) {
|
|
logger.error('Failed to open printer:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
private async closePrinter(): Promise<void> {
|
|
if (!this.printer) {
|
|
throw new Error('Printer not initialized');
|
|
}
|
|
|
|
try {
|
|
await this.printer.close();
|
|
logger.info('Printer closed successfully');
|
|
} catch (error) {
|
|
logger.error('Failed to close printer:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async getTaskSteps(_db: Knex, task: Task): Promise<Step[]> {
|
|
return await this.stepRepository.findByTaskId(task.id);
|
|
}
|
|
|
|
async printTask(task: Task, db: Knex): Promise<void> {
|
|
if (!this.printer || !this.device) {
|
|
throw new Error('Printer not initialized');
|
|
}
|
|
|
|
const taskSteps = await this.getTaskSteps(db, task);
|
|
|
|
try {
|
|
await this.openPrinter();
|
|
|
|
// Print header with task ID as barcode
|
|
await this.printer
|
|
.font(FONT.DEFAULT)
|
|
.align(ALIGNMENT.CENTER)
|
|
.style(STYLE.BOLD)
|
|
.size(FONT_SIZES.LARGE.width, FONT_SIZES.LARGE.height)
|
|
.text(formatUtils.formatCheckbox(`Task: ${task.name}`))
|
|
.text(formatUtils.createBanner('=', PAPER_CONFIG.BANNER_LENGTH))
|
|
// .text('')
|
|
.align(ALIGNMENT.LEFT);
|
|
|
|
// Print task ID as barcode
|
|
await this.printer
|
|
.barcode(task.id.toString(), BARCODE_CONFIG.TYPE, BARCODE_CONFIG.DIMENSIONS);
|
|
// .text('')
|
|
// .text('');
|
|
|
|
// Print steps
|
|
for (let i = 0; i < taskSteps.length; i++) {
|
|
const step = taskSteps[i];
|
|
const stepSection = formatUtils.formatSection(
|
|
formatUtils.formatStepHeader(step.name, i + 1, task.name, true),
|
|
step.instructions || 'No instructions provided',
|
|
'-'
|
|
);
|
|
|
|
await this.printer
|
|
.size(FONT_SIZES.NORMAL.width, FONT_SIZES.NORMAL.height)
|
|
.text(stepSection[0])
|
|
.text(stepSection[1])
|
|
.size(FONT_SIZES.SMALL.width, FONT_SIZES.SMALL.height)
|
|
.text(stepSection[3]);
|
|
// .text('');
|
|
|
|
// Print step ID as barcode
|
|
await this.printer
|
|
.barcode(step.id.toString(), BARCODE_CONFIG.TYPE, BARCODE_CONFIG.DIMENSIONS);
|
|
// .text('');
|
|
}
|
|
|
|
await this.printer
|
|
.cut(true, PAPER_CONFIG.CUT_LINES);
|
|
|
|
await this.closePrinter();
|
|
|
|
logger.info(`Printed task ${task.id}`);
|
|
|
|
await this.printHistoryRepo.create({
|
|
user_id: PRINTER_CONFIG.DEFAULT_USER_ID,
|
|
task_id: task.id,
|
|
printed_at: new Date(),
|
|
});
|
|
} catch (error) {
|
|
logger.error('Failed to print task:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async printStep(step: Step, db: Knex): Promise<void> {
|
|
if (!this.printer || !this.device) {
|
|
throw new Error('Printer not initialized');
|
|
}
|
|
|
|
try {
|
|
await this.openPrinter();
|
|
|
|
// Get the task name for context
|
|
const task = await this.stepRepository.findTaskById(step.id);
|
|
const stepNumber = await this.stepRepository.findStepNumber(step.id);
|
|
|
|
const stepSection = formatUtils.formatSection(
|
|
formatUtils.formatStepHeader(step.name, stepNumber, task?.name),
|
|
step.instructions || 'No instructions provided'
|
|
);
|
|
|
|
await this.printer
|
|
.font(FONT.DEFAULT)
|
|
.align(ALIGNMENT.CENTER)
|
|
.style(STYLE.BOLD)
|
|
.size(FONT_SIZES.LARGE.width, FONT_SIZES.LARGE.height)
|
|
.text(stepSection[0])
|
|
.text(stepSection[1])
|
|
.text('')
|
|
.align(ALIGNMENT.LEFT)
|
|
.size(FONT_SIZES.NORMAL.width, FONT_SIZES.NORMAL.height)
|
|
.text(stepSection[3])
|
|
.text('')
|
|
.text('');
|
|
|
|
// Print step ID as barcode
|
|
await this.printer
|
|
.barcode(step.id.toString(), BARCODE_CONFIG.TYPE, BARCODE_CONFIG.DIMENSIONS)
|
|
.cut(true, PAPER_CONFIG.CUT_LINES);
|
|
|
|
await this.closePrinter();
|
|
|
|
logger.info(`Printed step ${step.id}`);
|
|
|
|
await this.printHistoryRepo.create({
|
|
user_id: PRINTER_CONFIG.DEFAULT_USER_ID,
|
|
step_id: step.id,
|
|
printed_at: new Date(),
|
|
});
|
|
} catch (error) {
|
|
logger.error('Failed to print step:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
}
|