task_receipts/server/src/printer/command-executor.ts

268 lines
8.8 KiB
TypeScript

import { Printer as EscposPrinter } from '@node-escpos/core';
import { formatUtils } from './format-utils';
import { Command, CommandTuple, CommandArray } from './printer-commands';
import { FONT_SIZES, ALIGNMENT, FONT, STYLE, BARCODE_CONFIG } from './printer-constants';
import logger from '../logger';
export interface CommandExecutor {
executeCommands(commands: CommandArray): Promise<void>;
}
export class SerialCommandExecutor implements CommandExecutor {
constructor(private printer: EscposPrinter<[]>) {}
async executeCommands(commands: CommandArray): Promise<void> {
for (const command of commands) {
await this.executeCommand(command);
}
}
private async executeCommand(commandTuple: CommandTuple): Promise<void> {
const [command, ...params] = commandTuple;
switch (command) {
case Command.TEXT:
await this.printer.text(params[0] as string);
break;
case Command.HEADER:
await this.printer
.font(FONT.DEFAULT)
.align(ALIGNMENT.CENTER)
.style(STYLE.BOLD)
.size(FONT_SIZES.LARGE.width, FONT_SIZES.LARGE.height)
.text(formatUtils.getCheckboxText(formatUtils.checkbox(params[0] as string)));
break;
case Command.BANNER:
const char = params[0] as string;
const length = params[1] as number;
await this.printer.text(formatUtils.bannerString(char, length));
break;
case Command.NEWLINE:
await this.printer.text('');
break;
case Command.BARCODE:
const barcodeData = params[0] as string;
logger.info(`Printing barcode: ${barcodeData} with type: ${BARCODE_CONFIG.TYPE}`);
// Try multiple barcode formats until one works
const barcodeTypes = [BARCODE_CONFIG.TYPE, ...BARCODE_CONFIG.ALTERNATIVE_TYPES];
let barcodePrinted = false;
for (const barcodeType of barcodeTypes) {
try {
logger.info(`Trying barcode type: ${barcodeType}`);
await this.printer.barcode(
barcodeData,
barcodeType,
BARCODE_CONFIG.DIMENSIONS
);
logger.info(`Successfully printed barcode with type: ${barcodeType}`);
barcodePrinted = true;
break;
} catch (error) {
logger.warn(`Barcode type ${barcodeType} failed: ${error}`);
continue;
}
}
if (!barcodePrinted) {
logger.error(`All barcode types failed for data: ${barcodeData}`);
// As a last resort, just print the data as text
await this.printer.text(`[BARCODE: ${barcodeData}]`);
}
break;
case Command.FONT_SIZE:
const size = params[0] as typeof FONT_SIZES[keyof typeof FONT_SIZES];
await this.printer.size(size.width, size.height);
break;
case Command.ALIGN:
await this.printer.align(params[0] as any);
break;
case Command.FONT_FAMILY:
await this.printer.font(params[0] as any);
break;
case Command.STYLE:
await this.printer.style(params[0] as any);
break;
case Command.CUT:
const partial = params[0] as boolean;
const lines = params[1] as number;
await this.printer.cut(partial, lines);
break;
case Command.SECTION:
const header = params[0] as string;
const content = params[1] as string;
const bannerChar = params[2] as string;
const trailingNewline = params[3] as boolean;
const section = formatUtils.section(header, content, bannerChar, trailingNewline);
for (const cmd of section) {
if (cmd[0] === Command.CHECKBOX || cmd[0] === Command.TEXT) {
await this.printer.text(cmd[1] as string);
} else if (cmd[0] === Command.BANNER) {
await this.printer.text(formatUtils.bannerString(cmd[1] as string, cmd[2] as number));
} else if (cmd[0] === Command.NEWLINE) {
await this.printer.text('');
}
}
break;
case Command.STEP_HEADER:
const stepName = params[0] as string;
const stepNumber = params[1] as number;
const taskName = params[2] as string;
const isTaskView = params[3] as boolean;
const stepHeader = formatUtils.stepHeader(stepName, stepNumber, taskName, isTaskView);
await this.printer.text(formatUtils.getCheckboxText(stepHeader));
break;
case Command.CHECKBOX:
const text = params[0] as string;
const checkbox = `[ ] ${text}`;
await this.printer.text(checkbox);
break;
case Command.LIST:
const items = params[0] as string[];
const startIndex = params[1] as number;
const prefix = params[2] as string;
const listCmds = formatUtils.list(items, startIndex, prefix);
for (const cmd of listCmds) {
if (cmd[0] === Command.LIST) {
// Render as text for test printer, or as needed for real printer
for (let i = 0; i < items.length; i++) {
const num = startIndex > 0 ? `${i + startIndex}: ` : '';
const body = formatUtils.getCheckboxText(formatUtils.checkbox(`${num}${items[i]}`));
await this.printer.text(`${prefix}${body}`);
}
}
}
break;
default:
throw new Error(`Unknown command: ${command}`);
}
}
}
export class TestCommandExecutor implements CommandExecutor {
private output: string[] = [];
async executeCommands(commands: CommandArray): Promise<void> {
this.output = [];
for (const command of commands) {
await this.executeCommand(command);
}
}
private async executeCommand(commandTuple: CommandTuple): Promise<void> {
const [command, ...params] = commandTuple;
switch (command) {
case Command.TEXT:
this.output.push(params[0] as string);
break;
case Command.HEADER:
this.output.push(formatUtils.getCheckboxText(formatUtils.checkbox(params[0] as string)));
break;
case Command.BANNER:
const char = params[0] as string;
const length = params[1] as number;
this.output.push(formatUtils.banner(char, length)[1] as string);
break;
case Command.NEWLINE:
this.output.push('');
break;
case Command.BARCODE:
this.output.push(`[BARCODE: ${params[0] as string}]`);
break;
case Command.FONT_SIZE:
// Test printer ignores font size changes
break;
case Command.ALIGN:
// Test printer ignores alignment changes
break;
case Command.FONT_FAMILY:
// Test printer ignores font changes
break;
case Command.STYLE:
// Test printer ignores style changes
break;
case Command.CUT:
this.output.push('--- CUT ---');
break;
case Command.SECTION:
const header = params[0] as string;
const content = params[1] as string;
const bannerChar = params[2] as string;
const trailingNewline = params[3] as boolean;
const section = formatUtils.section(header, content, bannerChar, trailingNewline);
for (const cmd of section) {
if (cmd[0] === Command.CHECKBOX || cmd[0] === Command.TEXT) {
this.output.push(cmd[1] as string);
} else if (cmd[0] === Command.BANNER) {
this.output.push(formatUtils.bannerString(cmd[1] as string, cmd[2] as number));
} else if (cmd[0] === Command.NEWLINE) {
this.output.push('');
}
}
break;
case Command.STEP_HEADER:
const stepName = params[0] as string;
const stepNumber = params[1] as number;
const taskName = params[2] as string;
const isTaskView = params[3] as boolean;
const stepHeader = formatUtils.stepHeader(stepName, stepNumber, taskName, isTaskView);
this.output.push(formatUtils.getCheckboxText(stepHeader));
break;
case Command.CHECKBOX:
this.output.push(formatUtils.getCheckboxText(formatUtils.checkbox(params[0] as string)));
break;
case Command.LIST:
const items = params[0] as string[];
const startIndex = params[1] as number;
const prefix = params[2] as string;
const listCmds = formatUtils.list(items, startIndex, prefix);
for (const cmd of listCmds) {
if (cmd[0] === Command.LIST) {
for (let i = 0; i < items.length; i++) {
const num = startIndex > 0 ? `${i + startIndex}: ` : '';
const body = formatUtils.getCheckboxText(formatUtils.checkbox(`${num}${items[i]}`));
this.output.push(`${prefix}${body}`);
}
}
}
break;
default:
throw new Error(`Unknown command: ${command}`);
}
}
getOutput(): string {
return this.output.join('\n') + '\n';
}
}