2026-01-22 15:15:23 +00:00
|
|
|
import { Injectable } from '@nestjs/common';
|
|
|
|
|
import { PrintDataDto } from '../dto/print-data.dto';
|
|
|
|
|
import { PrintHtmlDto } from '../dto/print-html.dto';
|
|
|
|
|
const { ThermalPrinter, PrinterTypes } = require('node-thermal-printer');
|
|
|
|
|
|
|
|
|
|
@Injectable()
|
|
|
|
|
export class ListPrinterService {
|
|
|
|
|
private readonly printerType = PrinterTypes.EPSON;
|
|
|
|
|
|
|
|
|
|
async printReceipt(data: PrintDataDto): Promise<{ success: boolean; message: string }> {
|
|
|
|
|
if (!data.lines?.length) {
|
|
|
|
|
return { success: false, message: 'No lines provided' };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
let printerInterface = data.printerInterface;
|
|
|
|
|
|
|
|
|
|
if (printerInterface && !printerInterface.includes('://') && !printerInterface.startsWith('printer:')) {
|
|
|
|
|
printerInterface = `printer:${printerInterface}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const printer = new ThermalPrinter({
|
|
|
|
|
type: this.printerType,
|
|
|
|
|
interface: printerInterface,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this.applyAlignment(printer, data.alignment);
|
|
|
|
|
|
|
|
|
|
for (const line of data.lines) {
|
|
|
|
|
if (data.upsideDown) {
|
|
|
|
|
printer.upsideDown(true);
|
|
|
|
|
}
|
|
|
|
|
printer.println(line);
|
|
|
|
|
if (data.upsideDown) {
|
|
|
|
|
printer.upsideDown(false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
printer.drawLine();
|
|
|
|
|
printer.beep();
|
|
|
|
|
|
|
|
|
|
await printer.execute();
|
|
|
|
|
|
|
|
|
|
return { success: true, message: 'Print successful!' };
|
|
|
|
|
} catch (error) {
|
|
|
|
|
return {
|
|
|
|
|
success: false,
|
|
|
|
|
message: `Print failed: ${error.message || error}`
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private applyAlignment(printer: any, alignment?: string): void {
|
|
|
|
|
if (alignment === 'center') {
|
|
|
|
|
return printer.alignCenter();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (alignment === 'right') {
|
|
|
|
|
return printer.alignRight();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return printer.alignLeft();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async printHtml(data: PrintHtmlDto): Promise<{ success: boolean; message: string }> {
|
|
|
|
|
const puppeteer = require('puppeteer');
|
|
|
|
|
const pdfPrinter = require('pdf-to-printer');
|
|
|
|
|
const path = require('path');
|
|
|
|
|
const fs = require('fs');
|
|
|
|
|
const os = require('os');
|
|
|
|
|
|
|
|
|
|
const tempFilePath = path.join(os.tmpdir(), `print-${Date.now()}.pdf`);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const browser = await puppeteer.launch({ headless: true });
|
|
|
|
|
const page = await browser.newPage();
|
|
|
|
|
|
|
|
|
|
const width = data.width || '80mm';
|
|
|
|
|
const height = data.height;
|
|
|
|
|
|
|
|
|
|
const fullHtml = `
|
|
|
|
|
<!DOCTYPE html>
|
|
|
|
|
<html>
|
|
|
|
|
<head>
|
|
|
|
|
<meta charset="UTF-8">
|
|
|
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
|
|
|
<style>
|
2026-01-22 18:57:45 +00:00
|
|
|
html, body { margin: 0; padding: 0; }
|
|
|
|
|
body { zoom: 1.4; transform-origin: top left; }
|
2026-01-22 15:15:23 +00:00
|
|
|
@page { size: ${width} ${height || 'auto'}; margin: 0; }
|
|
|
|
|
</style>
|
|
|
|
|
</head>
|
|
|
|
|
<body>
|
|
|
|
|
${data.html}
|
|
|
|
|
</body>
|
|
|
|
|
</html>
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
await page.setContent(fullHtml, { waitUntil: 'networkidle0' });
|
|
|
|
|
|
|
|
|
|
const pdfOptions: any = {
|
|
|
|
|
path: tempFilePath,
|
|
|
|
|
printBackground: true,
|
|
|
|
|
width: width,
|
|
|
|
|
margin: { top: '0px', right: '0px', bottom: '0px', left: '0px' }
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (height) {
|
|
|
|
|
pdfOptions.height = height;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await page.pdf(pdfOptions);
|
|
|
|
|
|
|
|
|
|
await browser.close();
|
|
|
|
|
|
2026-01-22 18:57:45 +00:00
|
|
|
await pdfPrinter.print(tempFilePath, {
|
|
|
|
|
printer: data.printerName,
|
|
|
|
|
win32: ['-nointterrupt'] // Tenta evitar comandos extras, mas o pdf-to-printer tem opções limitadas de corte.
|
|
|
|
|
// Na verdade, o pdf-to-printer usa SumatraPDF, que não tem flag explícita de "no-cut".
|
|
|
|
|
// O corte geralmente é configuração do driver ou do tamanho do papel.
|
|
|
|
|
// Mas vamos garantir que não estamos enviando nada extra.
|
|
|
|
|
});
|
2026-01-22 15:15:23 +00:00
|
|
|
|
|
|
|
|
return { success: true, message: 'HTML convertido (com Tailwind) e enviado para impressão!' };
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Erro na impressão HTML:', error);
|
|
|
|
|
return { success: false, message: `Erro ao imprimir HTML: ${error.message}` };
|
|
|
|
|
} finally {
|
|
|
|
|
try {
|
|
|
|
|
if (fs.existsSync(tempFilePath)) {
|
|
|
|
|
fs.unlinkSync(tempFilePath);
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.warn('Não foi possível deletar arquivo temp:', e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|