refactor: remove comments and improve printer service
This commit is contained in:
parent
853aeddb75
commit
267b09946e
|
|
@ -0,0 +1,109 @@
|
|||
# Documentação da API de Impressão
|
||||
|
||||
Esta API permite listar impressoras disponíveis no servidor (Windows) e enviar comandos de impressão de texto simples ou layouts HTML (etiquetas).
|
||||
|
||||
URL Base: `http://localhost:3000`
|
||||
Swagger UI: `http://localhost:3000/api`
|
||||
|
||||
## Endpoints
|
||||
|
||||
### 1. Listar Impressoras
|
||||
|
||||
Retorna a lista de impressoras instaladas no servidor onde a API está rodando.
|
||||
|
||||
- **Método:** `GET`
|
||||
- **Rota:** `/printer/list`
|
||||
- **Exemplo de Resposta:**
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"name": "POS-80",
|
||||
"portName": "USB001",
|
||||
"driverName": "Generic / Text Only",
|
||||
"printerStatus": 0,
|
||||
"deviceId": "POS-80"
|
||||
},
|
||||
{
|
||||
"name": "Microsoft Print to PDF",
|
||||
"portName": "PORTPROMPT:",
|
||||
"driverName": "Microsoft Print To PDF",
|
||||
"printerStatus": 0
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. Imprimir HTML (Etiquetas/Layouts)
|
||||
|
||||
Converte um código HTML (com suporte a Tailwind CSS) para PDF e imprime na impressora selecionada. Ideal para etiquetas.
|
||||
|
||||
- **Método:** `POST`
|
||||
- **Rota:** `/printer/print-html`
|
||||
- **Corpo da Requisição (JSON):**
|
||||
|
||||
```json
|
||||
{
|
||||
"html": "<div class='text-xl font-bold'>Minha Etiqueta</div>...",
|
||||
"printerName": "POS-80", // Nome exato da impressora (conforme retornado na listagem)
|
||||
"width": "60mm", // Opcional. Largura do papel/etiqueta. Default: 80mm
|
||||
"height": "40mm" // Opcional. Altura da etiqueta. Default: auto
|
||||
}
|
||||
```
|
||||
|
||||
- **Detalhes:**
|
||||
- O backend injeta automaticamente o script do Tailwind CSS.
|
||||
- O HTML é renderizado via Puppeteer (Chrome headless).
|
||||
|
||||
---
|
||||
|
||||
### 3. Imprimir Texto Simples (ESC/POS)
|
||||
|
||||
Envia comandos de texto diretamente para a impressora. Ideal para cupons simples e rápidos, sem formatação complexa.
|
||||
|
||||
- **Método:** `POST`
|
||||
- **Rota:** `/printer/print`
|
||||
- **Corpo da Requisição (JSON):**
|
||||
|
||||
```json
|
||||
{
|
||||
"lines": [
|
||||
"--------------------------------",
|
||||
" CUPOM FISCAL ",
|
||||
"--------------------------------",
|
||||
"Item 1 ................. R$ 10,00"
|
||||
],
|
||||
"alignment": "center", // "left", "center", "right". Default: left
|
||||
"upsideDown": false, // Se true, imprime de cabeça para baixo
|
||||
"printerInterface": "POS-80" // Pode ser nome da impressora (USB) ou "tcp://192.168.1.100" (Rede)
|
||||
}
|
||||
```
|
||||
|
||||
## Tipos (TypeScript Interfaces)
|
||||
|
||||
Se estiver usando TypeScript no Frontend, utilize estas interfaces:
|
||||
|
||||
```typescript
|
||||
export interface Printer {
|
||||
name: string;
|
||||
portName?: string;
|
||||
driverName?: string;
|
||||
printerStatus?: number;
|
||||
deviceId?: string;
|
||||
}
|
||||
|
||||
export interface PrintHtmlPayload {
|
||||
html: string;
|
||||
printerName: string;
|
||||
width?: string;
|
||||
height?: string;
|
||||
}
|
||||
|
||||
export interface PrintTextPayload {
|
||||
lines: string[];
|
||||
alignment?: 'left' | 'center' | 'right';
|
||||
upsideDown?: boolean;
|
||||
printerInterface?: string;
|
||||
}
|
||||
```
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
"collection": "@nestjs/schematics",
|
||||
"sourceRoot": "src",
|
||||
"compilerOptions": {
|
||||
"deleteOutDir": true
|
||||
"deleteOutDir": true,
|
||||
"plugins": ["@nestjs/swagger"]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -26,17 +26,20 @@
|
|||
"@nestjs/common": "^11.0.17",
|
||||
"@nestjs/core": "^11.0.1",
|
||||
"@nestjs/platform-express": "^11.0.1",
|
||||
"@nestjs/swagger": "^11.2.5",
|
||||
"@nestjs/typeorm": "^11.0.0",
|
||||
"@types/multer": "^2.0.0",
|
||||
"multer": "^2.0.2",
|
||||
"node-printer": "^1.0.4",
|
||||
"node-thermal-printer": "^4.5.0",
|
||||
"oracledb": "^6.10.0",
|
||||
"pdf-to-printer": "^5.6.1",
|
||||
"printer": "^0.4.0",
|
||||
"puppeteer": "^24.35.0",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"rxjs": "^7.8.1",
|
||||
"typeorm": "^0.3.28"
|
||||
"swagger-ui-express": "^5.0.1",
|
||||
"typeorm": "^0.3.28",
|
||||
"unix-print": "^1.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3.2.0",
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { AppController } from './app.controller';
|
||||
import { AppService } from './app.service';
|
||||
import { ListPrinterController } from './controller/list-printer.controller';
|
||||
import { ListPrinterService } from './services/list-printer';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { typeOrmConfig } from './config/typeorm.config';
|
||||
import { ListPrinterController } from './controller/list-printer.controller';
|
||||
import { ListPrinterService } from './services/create-printer';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forRoot(typeOrmConfig)],
|
||||
|
|
|
|||
|
|
@ -1,17 +1,62 @@
|
|||
import { Controller, Get, Post, Body } from '@nestjs/common';
|
||||
import { ListPrinterService } from '../services/list-printer';
|
||||
import { Controller, Post, Body, Get, HttpCode, HttpStatus, Param } from '@nestjs/common';
|
||||
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
|
||||
import { ListPrinterService } from '../services/create-printer';
|
||||
import { PrintDataDto } from '../dto/print-data.dto';
|
||||
import { PrintHtmlDto } from '../dto/print-html.dto';
|
||||
import { PrinterDto } from '../dto/printer.dto';
|
||||
import { getPrinters } from '../services/get-printer';
|
||||
|
||||
@Controller('printers')
|
||||
@ApiTags('Printer')
|
||||
@Controller('printer')
|
||||
export class ListPrinterController {
|
||||
constructor(private readonly listPrinterService: ListPrinterService) {}
|
||||
constructor(private readonly printerService: ListPrinterService) {}
|
||||
|
||||
@Get()
|
||||
async getPrinters() {
|
||||
return this.listPrinterService.listPrinters();
|
||||
@Post(':printerName/print')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@ApiOperation({ summary: 'Envia um comando de impressão de texto simples (ESC/POS)' })
|
||||
@ApiResponse({ status: 200, description: 'Impressão enviada com sucesso' })
|
||||
async print(
|
||||
@Param('printerName') printerName: string,
|
||||
@Body() printData: PrintDataDto
|
||||
) {
|
||||
printData.printerInterface = printerName;
|
||||
return this.printerService.printReceipt(printData);
|
||||
}
|
||||
|
||||
@Post('print')
|
||||
async print(@Body() body: { printerName: string; filePath: string }) {
|
||||
return this.listPrinterService.printDocument(body.printerName, body.filePath);
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@ApiOperation({ summary: 'Envia um comando de impressão (Compatibilidade)' })
|
||||
async printLegacy(@Body() printData: PrintDataDto) {
|
||||
return this.printerService.printReceipt(printData);
|
||||
}
|
||||
|
||||
@Post(':printerName/print-html')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@ApiOperation({ summary: 'Converte HTML para PDF e imprime na impressora especificada na URL' })
|
||||
@ApiResponse({ status: 200, description: 'HTML enviado para impressão' })
|
||||
async printHtml(
|
||||
@Param('printerName') printerName: string,
|
||||
@Body() printHtmlDto: PrintHtmlDto
|
||||
) {
|
||||
printHtmlDto.printerName = printerName;
|
||||
return this.printerService.printHtml(printHtmlDto);
|
||||
}
|
||||
|
||||
@Post('print-html')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@ApiOperation({ summary: 'Converte HTML para PDF (Compatibilidade)' })
|
||||
async printHtmlLegacy(@Body() printHtmlDto: PrintHtmlDto) {
|
||||
return this.printerService.printHtml(printHtmlDto);
|
||||
}
|
||||
|
||||
@Get('list')
|
||||
@ApiOperation({ summary: 'Lista impressoras disponíveis no sistema' })
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: 'Lista de impressoras encontradas',
|
||||
type: [PrinterDto]
|
||||
})
|
||||
async listPrinters() {
|
||||
return getPrinters();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class PrintDataDto {
|
||||
@ApiProperty({
|
||||
example: ['--------------------------------', ' TESTE DE IMPRESSAO ', '--------------------------------'],
|
||||
description: 'Lista de linhas de texto para imprimir'
|
||||
})
|
||||
lines: string[];
|
||||
|
||||
@ApiProperty({
|
||||
required: false,
|
||||
enum: ['left', 'center', 'right'],
|
||||
example: 'center',
|
||||
description: 'Alinhamento do texto'
|
||||
})
|
||||
alignment?: 'left' | 'center' | 'right';
|
||||
|
||||
@ApiProperty({
|
||||
required: false,
|
||||
example: false,
|
||||
description: 'Se verdadeiro, imprime de cabeça para baixo (se suportado)'
|
||||
})
|
||||
upsideDown?: boolean;
|
||||
|
||||
@ApiProperty({
|
||||
required: false,
|
||||
example: 'tcp://10.1.119.13',
|
||||
description: 'Interface da impressora. Ex: tcp://ip:porta ou printer:NomeDaImpressora'
|
||||
})
|
||||
printerInterface?: string;
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class PrintHtmlDto {
|
||||
@ApiProperty({
|
||||
example: '<h1>Titulo</h1><p>Conteudo do pedido...</p>',
|
||||
description: 'Conteúdo HTML para impressão'
|
||||
})
|
||||
html: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: 'POS-80',
|
||||
description: 'Nome da impressora onde será impresso o PDF gerado'
|
||||
})
|
||||
printerName: string;
|
||||
|
||||
@ApiProperty({ example: '60mm', required: false, description: 'Largura da etiqueta (default: 80mm)' })
|
||||
width?: string;
|
||||
|
||||
@ApiProperty({ example: '40mm', required: false, description: 'Altura da etiqueta (default: auto)' })
|
||||
height?: string;
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class PrinterDto {
|
||||
@ApiProperty({ example: 'POS-80', description: 'Nome da impressora' })
|
||||
name: string;
|
||||
|
||||
@ApiProperty({ example: 'USB001', description: 'Porta da impressora (Win32)' })
|
||||
portName?: string;
|
||||
|
||||
@ApiProperty({ example: 'Generic / Text Only', description: 'Nome do driver' })
|
||||
driverName?: string;
|
||||
|
||||
@ApiProperty({ example: 0, description: 'Status da impressora (0 = Idle)' })
|
||||
printerStatus?: number;
|
||||
|
||||
@ApiProperty({ example: 'POS-80', description: 'ID do dispositivo (usado para impressão)' })
|
||||
deviceId?: string;
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
export interface PrintData {
|
||||
lines: string[];
|
||||
alignment?: 'left' | 'center' | 'right';
|
||||
upsideDown?: boolean;
|
||||
}
|
||||
10
src/main.ts
10
src/main.ts
|
|
@ -1,10 +1,20 @@
|
|||
import { NestFactory } from '@nestjs/core';
|
||||
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
|
||||
import { AppModule } from './app.module';
|
||||
import { initializeOracleClient } from './helpers/oracle.helper';
|
||||
|
||||
async function bootstrap() {
|
||||
initializeOracleClient();
|
||||
const app = await NestFactory.create(AppModule);
|
||||
|
||||
const config = new DocumentBuilder()
|
||||
.setTitle('API Documentation')
|
||||
.setDescription('The API description')
|
||||
.setVersion('1.0')
|
||||
.build();
|
||||
const document = SwaggerModule.createDocument(app, config);
|
||||
SwaggerModule.setup('api', app, document);
|
||||
|
||||
await app.listen(3000);
|
||||
}
|
||||
bootstrap();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,143 @@
|
|||
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.cut();
|
||||
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>
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-webkit-print-color-adjust: exact;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
transform-origin: top left;
|
||||
zoom: 1.25;
|
||||
}
|
||||
@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();
|
||||
|
||||
await pdfPrinter.print(tempFilePath, { printer: data.printerName });
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
import { exec } from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
export const getPrinters = async () => {
|
||||
try {
|
||||
const command = `powershell "Get-Printer | Select-Object Name, DriverName, PrinterStatus | ConvertTo-Json"`;
|
||||
const { stdout } = await execAsync(command);
|
||||
if (!stdout) return [];
|
||||
const data = JSON.parse(stdout);
|
||||
return Array.isArray(data) ? data : [data];
|
||||
} catch (error) {
|
||||
console.error("Erro ao listar impressoras via PowerShell:", error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { exec } from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
import { print } from 'pdf-to-printer';
|
||||
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
@Injectable()
|
||||
export class ListPrinterService {
|
||||
async listPrinters(): Promise<string[]> {
|
||||
try {
|
||||
const { stdout } = await execAsync(
|
||||
'powershell -Command "Get-Printer | Select-Object -ExpandProperty Name"',
|
||||
{ encoding: 'utf-8' }
|
||||
);
|
||||
|
||||
const printers = stdout
|
||||
.split('\r\n')
|
||||
.map(line => line.trim())
|
||||
.filter(line => line && line !== 'Name');
|
||||
|
||||
return printers;
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to list printers: ${(error as Error).message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async printDocument(printerName: string, filePath: string): Promise<{ success: boolean; message: string }> {
|
||||
try {
|
||||
await print(filePath, { printer: printerName });
|
||||
return { success: true, message: `Document sent to printer: ${printerName}` };
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to print document: ${(error as Error).message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue