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 { if (!this.device) { throw new Error('Printer device not initialized'); } try { await new Promise((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 { 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 { return await this.stepRepository.findByTaskId(task.id); } async printTask(task: Task, db: Knex): Promise { 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 { 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; } } }