From 66adb39b9b72c87c841840926309cbfa2ded99b6 Mon Sep 17 00:00:00 2001 From: Joelbrit0 Date: Mon, 26 Jan 2026 18:48:06 -0300 Subject: [PATCH 1/3] docs(swagger): clarify API contracts and response schemas --- API_CONTRACTS.md | 57 ++++++++++++++++++++-------- README.md | 26 ++++++++----- src/controller/printer.controller.ts | 51 ++++++++++++++++++++++--- src/dto/print-data.dto.ts | 25 +++++++----- src/dto/print-html-result.dto.ts | 11 ++++++ src/dto/print-html.dto.ts | 36 +++++++++++------- src/dto/print-result.dto.ts | 9 +++++ src/dto/printer.dto.ts | 30 +++++++++------ src/main.ts | 8 ++-- 9 files changed, 185 insertions(+), 68 deletions(-) create mode 100644 src/dto/print-html-result.dto.ts create mode 100644 src/dto/print-result.dto.ts diff --git a/API_CONTRACTS.md b/API_CONTRACTS.md index 8211e10..874abc0 100644 --- a/API_CONTRACTS.md +++ b/API_CONTRACTS.md @@ -18,21 +18,20 @@ Retorna a lista de impressoras instaladas no servidor onde a API está rodando. ```json [ { - "name": "POS-80", - "portName": "USB001", - "driverName": "Generic / Text Only", - "printerStatus": 0, - "deviceId": "POS-80" + "Name": "POS-80", + "DriverName": "Generic / Text Only", + "PrinterStatus": 0 }, { - "name": "Microsoft Print to PDF", - "portName": "PORTPROMPT:", - "driverName": "Microsoft Print To PDF", - "printerStatus": 0 + "Name": "Microsoft Print to PDF", + "DriverName": "Microsoft Print To PDF", + "PrinterStatus": 0 } ] ``` +> Nota: atualmente esse endpoint repassa o resultado do PowerShell `Get-Printer` (Windows) com chaves em PascalCase. + --- ### 2. Imprimir HTML (Etiquetas/Layouts) @@ -53,6 +52,16 @@ Converte um código HTML (com suporte a Tailwind CSS) para PDF e imprime na impr } ``` +- **Exemplo de Resposta:** + +```json +{ + "success": true, + "message": "Tarefa enviada para a fila de impressão", + "jobId": "123" +} +``` + - **Detalhes:** - O backend injeta automaticamente o script do Tailwind CSS. - O HTML é renderizado via Puppeteer (Chrome headless). @@ -82,22 +91,29 @@ Envia comandos de texto diretamente para a impressora. Ideal para cupons simples } ``` +- **Exemplo de Resposta:** + +```json +{ + "success": true, + "message": "Print successful!" +} +``` + ## 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; + Name: string; + DriverName?: string; + PrinterStatus?: number; } export interface PrintHtmlPayload { html: string; - printerName: string; + printerName?: string; width?: string; height?: string; jobId?: string; @@ -109,6 +125,17 @@ export interface PrintTextPayload { upsideDown?: boolean; printerInterface?: string; } + +export interface PrintHtmlResponse { + success: boolean; + message: string; + jobId?: string; +} + +export interface PrintTextResponse { + success: boolean; + message: string; +} ``` ## Swagger diff --git a/README.md b/README.md index 95222bf..92f8035 100644 --- a/README.md +++ b/README.md @@ -27,14 +27,19 @@ A solução utiliza uma arquitetura baseada em filas para assegurar a resiliênc O serviço utiliza as seguintes definições no arquivo `.env`: -| Variável | Descrição | Exemplo | -| :------------------- | :------------------------------- | :------------------------ | -| `REDIS_HOST` | Endereço do servidor Redis | `localhost` | -| `REDIS_PORT` | Porta do servidor Redis | `6379` | -| `DB_USERNAME` | Usuário do Banco de Dados Oracle | `admin` | -| `DB_PASSWORD` | Senha do Banco de Dados Oracle | `1234` | -| `DB_CONNECT_STRING` | String de conexão Oracle | `(DESCRIPTION=...)` | -| `ORACLE_CLIENT_PATH` | Caminho do Oracle Instant Client | `C:\oracle\instantclient` | +| Variável | Descrição | Exemplo | +| :------------------- | :-------------------------------------------- | :------------------------ | +| `BODY_LIMIT` | Limite do body (JSON/urlencoded) | `2mb` | +| `REDIS_HOST` | Endereço do servidor Redis | `localhost` | +| `REDIS_PORT` | Porta do servidor Redis | `6379` | +| `REDIS_PASSWORD` | Senha do Redis (opcional) | `minha-senha` | +| `DB_ENABLED` | Habilita inicialização do Oracle/TypeORM | `true` | +| `DB_USERNAME` | Usuário do Banco de Dados Oracle | `admin` | +| `DB_PASSWORD` | Senha do Banco de Dados Oracle | `1234` | +| `DB_CONNECT_STRING` | String de conexão Oracle | `(DESCRIPTION=...)` | +| `DB_RETRY_ATTEMPTS` | Tentativas de conexão ao iniciar | `5` | +| `DB_RETRY_DELAY_MS` | Delay base (ms) entre tentativas | `2000` | +| `ORACLE_CLIENT_PATH` | Caminho do Oracle Instant Client (thick mode) | `C:\oracle\instantclient` | ### Pré-requisitos @@ -102,9 +107,10 @@ A interface de monitoramento em tempo real das filas está disponível no endpoi Permite a visualização de tarefas ativas, concluídas e falhas, além da reinjeção manual de jobs. -### Telemetria +### Swagger -O serviço está instrumentado com **OpenTelemetry**, exportando traces via protocolo OTLP para análise de performance e rastreabilidade distribuída. +A documentação interativa (OpenAPI) está disponível em: +`http://localhost:3000/api` --- diff --git a/src/controller/printer.controller.ts b/src/controller/printer.controller.ts index 3ba5e95..845ed64 100644 --- a/src/controller/printer.controller.ts +++ b/src/controller/printer.controller.ts @@ -7,12 +7,20 @@ import { HttpStatus, Param, } from '@nestjs/common'; -import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger'; +import { + ApiBody, + ApiOkResponse, + ApiOperation, + ApiParam, + ApiTags, +} from '@nestjs/swagger'; import { PrinterService } from '../services/printer.service'; 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'; +import { PrintResultDto } from '../dto/print-result.dto'; +import { PrintHtmlResultDto } from '../dto/print-html-result.dto'; @ApiTags('Printer') @Controller('printer') @@ -24,7 +32,15 @@ export class PrinterController { @ApiOperation({ summary: 'Envia um comando de impressão de texto simples (ESC/POS)', }) - @ApiResponse({ status: 200, description: 'Impressão enviada com sucesso' }) + @ApiParam({ + name: 'printerName', + example: 'POS-80', + description: 'Nome da impressora (será usado como printerInterface)', + }) + @ApiOkResponse({ + description: 'Impressão enviada com sucesso', + type: PrintResultDto, + }) async print( @Param('printerName') printerName: string, @Body() printData: PrintDataDto, @@ -36,6 +52,15 @@ export class PrinterController { @Post('print') @HttpCode(HttpStatus.OK) @ApiOperation({ summary: 'Envia um comando de impressão (Compatibilidade)' }) + @ApiBody({ + type: PrintDataDto, + description: + 'Compatibilidade: informe printerInterface no body ou use /printer/:printerName/print.', + }) + @ApiOkResponse({ + description: 'Impressão enviada com sucesso', + type: PrintResultDto, + }) async printLegacy(@Body() printData: PrintDataDto) { return this.printerService.printReceipt(printData); } @@ -46,7 +71,15 @@ export class PrinterController { summary: 'Converte HTML para PDF e imprime na impressora especificada na URL', }) - @ApiResponse({ status: 200, description: 'HTML enviado para impressão' }) + @ApiParam({ + name: 'printerName', + example: 'POS-80', + description: 'Nome da impressora para o spooler (pdf-to-printer)', + }) + @ApiOkResponse({ + description: 'HTML enviado para impressão (job enfileirado)', + type: PrintHtmlResultDto, + }) async printHtml( @Param('printerName') printerName: string, @Body() printHtmlDto: PrintHtmlDto, @@ -58,14 +91,22 @@ export class PrinterController { @Post('print-html') @HttpCode(HttpStatus.OK) @ApiOperation({ summary: 'Converte HTML para PDF (Compatibilidade)' }) + @ApiBody({ + type: PrintHtmlDto, + description: + 'Compatibilidade: informe printerName no body ou use /printer/:printerName/print-html.', + }) + @ApiOkResponse({ + description: 'HTML enviado para impressão (job enfileirado)', + type: PrintHtmlResultDto, + }) async printHtmlLegacy(@Body() printHtmlDto: PrintHtmlDto) { return this.printerService.printHtml(printHtmlDto); } @Get('list') @ApiOperation({ summary: 'Lista impressoras disponíveis no sistema' }) - @ApiResponse({ - status: 200, + @ApiOkResponse({ description: 'Lista de impressoras encontradas', type: [PrinterDto], }) diff --git a/src/dto/print-data.dto.ts b/src/dto/print-data.dto.ts index 1dc267b..659d3ab 100644 --- a/src/dto/print-data.dto.ts +++ b/src/dto/print-data.dto.ts @@ -1,31 +1,36 @@ import { ApiProperty } from '@nestjs/swagger'; export class PrintDataDto { - @ApiProperty({ - example: ['--------------------------------', ' TESTE DE IMPRESSAO ', '--------------------------------'], - description: 'Lista de linhas de texto para imprimir' + @ApiProperty({ + example: [ + '--------------------------------', + ' TESTE DE IMPRESSAO ', + '--------------------------------', + ], + description: 'Lista de linhas de texto para imprimir', }) lines: string[]; - @ApiProperty({ - required: false, + @ApiProperty({ + required: false, enum: ['left', 'center', 'right'], example: 'center', - description: 'Alinhamento do texto' + description: 'Alinhamento do texto', }) alignment?: 'left' | 'center' | 'right'; - @ApiProperty({ - required: false, + @ApiProperty({ + required: false, example: false, - description: 'Se verdadeiro, imprime de cabeça para baixo (se suportado)' + 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' + description: + 'Interface da impressora. Obrigatorio ao usar /printer/print; ignorado ao usar /printer/:printerName/print. Ex: tcp://ip:porta ou printer:NomeDaImpressora', }) printerInterface?: string; } diff --git a/src/dto/print-html-result.dto.ts b/src/dto/print-html-result.dto.ts new file mode 100644 index 0000000..fa898f5 --- /dev/null +++ b/src/dto/print-html-result.dto.ts @@ -0,0 +1,11 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { PrintResultDto } from './print-result.dto'; + +export class PrintHtmlResultDto extends PrintResultDto { + @ApiProperty({ + required: false, + example: '123', + description: 'ID do job enfileirado no BullMQ', + }) + jobId?: string; +} diff --git a/src/dto/print-html.dto.ts b/src/dto/print-html.dto.ts index eba317d..019b9b0 100644 --- a/src/dto/print-html.dto.ts +++ b/src/dto/print-html.dto.ts @@ -1,28 +1,38 @@ import { ApiProperty } from '@nestjs/swagger'; export class PrintHtmlDto { - @ApiProperty({ - example: '

Titulo

Conteudo do pedido...

', - description: 'Conteúdo HTML para impressão' + @ApiProperty({ + example: '

Titulo

Conteudo do pedido...

', + description: 'Conteúdo HTML para impressão', }) html: string; - @ApiProperty({ - example: 'POS-80', - description: 'Nome da impressora onde será impresso o PDF gerado' + @ApiProperty({ + example: 'POS-80', + required: false, + description: + 'Nome da impressora. Obrigatorio ao usar /printer/print-html; ignorado ao usar /printer/:printerName/print-html.', }) - printerName: string; + printerName?: string; - @ApiProperty({ example: '60mm', required: false, description: 'Largura da etiqueta (default: 80mm)' }) + @ApiProperty({ + example: '60mm', + required: false, + description: 'Largura da etiqueta (default: 80mm)', + }) width?: string; - @ApiProperty({ example: '40mm', required: false, description: 'Altura da etiqueta (default: auto)' }) + @ApiProperty({ + example: '40mm', + required: false, + description: 'Altura da etiqueta (default: auto)', + }) height?: string; - @ApiProperty({ - example: 'impresso-12345', - required: false, - description: 'ID único do job para idempotência (evita duplicados)' + @ApiProperty({ + example: 'impresso-12345', + required: false, + description: 'ID único do job para idempotência (evita duplicados)', }) jobId?: string; } diff --git a/src/dto/print-result.dto.ts b/src/dto/print-result.dto.ts new file mode 100644 index 0000000..c86aa91 --- /dev/null +++ b/src/dto/print-result.dto.ts @@ -0,0 +1,9 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class PrintResultDto { + @ApiProperty({ example: true }) + success: boolean; + + @ApiProperty({ example: 'Print successful!' }) + message: string; +} diff --git a/src/dto/printer.dto.ts b/src/dto/printer.dto.ts index fee49c4..7be3bc3 100644 --- a/src/dto/printer.dto.ts +++ b/src/dto/printer.dto.ts @@ -1,18 +1,24 @@ import { ApiProperty } from '@nestjs/swagger'; export class PrinterDto { - @ApiProperty({ example: 'POS-80', description: 'Nome da impressora' }) - name: string; + @ApiProperty({ + example: 'POS-80', + description: 'Nome da impressora (retornado pelo PowerShell: Get-Printer)', + }) + Name: string; - @ApiProperty({ example: 'USB001', description: 'Porta da impressora (Win32)' }) - portName?: string; + @ApiProperty({ + example: 'Generic / Text Only', + required: false, + description: 'Nome do driver (retornado pelo PowerShell: Get-Printer)', + }) + DriverName?: 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; + @ApiProperty({ + example: 0, + required: false, + description: + 'Status da impressora (retornado pelo PowerShell: Get-Printer)', + }) + PrinterStatus?: number; } diff --git a/src/main.ts b/src/main.ts index cdbf065..9d8846f 100644 --- a/src/main.ts +++ b/src/main.ts @@ -18,9 +18,11 @@ async function bootstrap() { app.use(urlencoded({ extended: true, limit: bodyLimit })); const config = new DocumentBuilder() - .setTitle('API Documentation') - .setDescription('The API description') - .setVersion('1.0') + .setTitle('Servico de Impressao') + .setDescription( + 'Microservico NestJS para impressao local (ESC/POS e HTML->PDF), com processamento assincrono via BullMQ/Redis e opcional integracao Oracle via TypeORM.', + ) + .setVersion('1.0.0') .build(); const document = SwaggerModule.createDocument(app, config); SwaggerModule.setup('api', app, document); From fc2045da89b5c51f8f443b0de6bb935ace7d6fde Mon Sep 17 00:00:00 2001 From: Joelbrit0 Date: Thu, 29 Jan 2026 11:42:23 -0300 Subject: [PATCH 2/3] chore: organize project and update service --- src/app.module.ts | 6 +- src/config/typeorm.config.ts | 6 +- src/controller/pedido-seven.controller.ts | 40 +++++ src/dto/pedido-condvenda7-row.dto.ts | 74 ++++++++ src/services/pedido-seven.service.ts | 201 ++++++++++++++++++++++ 5 files changed, 322 insertions(+), 5 deletions(-) create mode 100644 src/controller/pedido-seven.controller.ts create mode 100644 src/dto/pedido-condvenda7-row.dto.ts create mode 100644 src/services/pedido-seven.service.ts diff --git a/src/app.module.ts b/src/app.module.ts index df88a81..e2ad669 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -2,13 +2,15 @@ import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { PrinterController } from './controller/printer.controller'; +import { PedidoSevenController } from './controller/pedido-seven.controller'; import { PrinterService } from './services/printer.service'; import { PrinterProcessor } from './services/printer.processor'; import { InfrastructureModule } from './infrastructure/infrastructure.module'; +import { PedidoSevenService } from './services/pedido-seven.service'; @Module({ imports: [InfrastructureModule], - controllers: [AppController, PrinterController], - providers: [AppService, PrinterService, PrinterProcessor], + controllers: [AppController, PrinterController, PedidoSevenController], + providers: [AppService, PrinterService, PrinterProcessor, PedidoSevenService], }) export class AppModule {} diff --git a/src/config/typeorm.config.ts b/src/config/typeorm.config.ts index 0d918ca..6d597d0 100644 --- a/src/config/typeorm.config.ts +++ b/src/config/typeorm.config.ts @@ -8,11 +8,11 @@ export function createTypeOrmOptions( ): TypeOrmModuleOptions { const baseOptions: DataSourceOptions = { type: 'oracle', - username: configService.get('DB_USERNAME') || 'teste', - password: configService.get('DB_PASSWORD') || 'teste', + username: configService.get('DB_USERNAME') || 'SEVEN', + password: configService.get('DB_PASSWORD') || 'USR54SEV', connectString: configService.get('DB_CONNECT_STRING') || - '(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=10.1.1.241)(PORT=1521))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=BDTESTE)))', + '(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=10.1.1.241)(PORT=1521))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=WINT)))', synchronize: false, logging: true, entities: [__dirname + '/../**/*.{entity,view}.{js,ts}'], diff --git a/src/controller/pedido-seven.controller.ts b/src/controller/pedido-seven.controller.ts new file mode 100644 index 0000000..9942a78 --- /dev/null +++ b/src/controller/pedido-seven.controller.ts @@ -0,0 +1,40 @@ +import { Controller, Get, Param, ParseIntPipe } from '@nestjs/common'; +import { + ApiNotFoundResponse, + ApiOkResponse, + ApiOperation, + ApiParam, + ApiServiceUnavailableResponse, + ApiTags, +} from '@nestjs/swagger'; +import { PedidoSevenService } from '../services/pedido-seven.service'; +import { PedidoCondVenda7RowDto } from '../dto/pedido-condvenda7-row.dto'; + +@ApiTags('Pedidos') +@Controller('pedidos') +export class PedidoSevenController { + constructor(private readonly pedidoSevenService: PedidoSevenService) {} + + @Get('condvenda-7/:numped7') + @ApiOperation({ + summary: 'Consulta pedido (CONDVENDA=7) por NUMPEDENTFUT', + }) + @ApiParam({ + name: 'numped7', + example: 123456, + description: 'Valor para PCPEDC.NUMPEDENTFUT', + }) + @ApiOkResponse({ + description: 'Linhas retornadas pela consulta (pedido x itens)', + type: [PedidoCondVenda7RowDto], + }) + @ApiNotFoundResponse({ + description: 'Pedido nao encontrado para o NUMPEDENTFUT informado', + }) + @ApiServiceUnavailableResponse({ + description: 'Banco de dados indisponivel (DataSource nao inicializado)', + }) + async consultar(@Param('numped7', ParseIntPipe) numped7: number) { + return this.pedidoSevenService.consultarPorNumped7(numped7); + } +} diff --git a/src/dto/pedido-condvenda7-row.dto.ts b/src/dto/pedido-condvenda7-row.dto.ts new file mode 100644 index 0000000..ecca5a9 --- /dev/null +++ b/src/dto/pedido-condvenda7-row.dto.ts @@ -0,0 +1,74 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class PedidoCondVenda7RowDto { + @ApiProperty({ example: 123456 }) + numped!: number; + + @ApiProperty({ example: '12345678000199' }) + cgc!: string; + + @ApiProperty({ example: 'MINHA EMPRESA LTDA' }) + razaosocial!: string; + + @ApiProperty({ description: 'SYSDATE', type: String, format: 'date-time' }) + dtemissao!: Date; + + @ApiProperty({ example: 1 }) + codfilial!: number; + + @ApiProperty({ example: 'FILIAL CENTRO' }) + filialvenda!: string; + + @ApiProperty({ example: 2 }) + codfilialretira!: number; + + @ApiProperty({ example: 'FILIAL RETIRA' }) + filialretira!: string; + + @ApiProperty({ type: String, format: 'date-time' }) + data!: Date; + + @ApiProperty({ example: 999 }) + numcar!: number; + + @ApiProperty({ example: 123 }) + codcli!: number; + + @ApiProperty({ example: 'CLIENTE TESTE' }) + cliente!: string; + + @ApiProperty({ example: 10 }) + codusur!: number; + + @ApiProperty({ example: 'VENDEDOR' }) + vendedor!: string; + + @ApiProperty({ example: 25.5 }) + vlfrete!: number; + + @ApiProperty({ example: 199.9 }) + valor!: number; + + @ApiProperty({ example: 'ENTREGA' }) + tipoentrega!: string; + + @ApiProperty({ + type: String, + format: 'date-time', + required: false, + nullable: true, + }) + dtentrega!: Date | null; + + @ApiProperty({ example: 98765 }) + codprod!: number; + + @ApiProperty({ example: 'PRODUTO X' }) + descricao!: string; + + @ApiProperty({ example: 2 }) + qt!: number; + + @ApiProperty({ example: 'e10adc3949ba59abbe56e057f20f883e' }) + hash!: string; +} diff --git a/src/services/pedido-seven.service.ts b/src/services/pedido-seven.service.ts new file mode 100644 index 0000000..e1b4ed6 --- /dev/null +++ b/src/services/pedido-seven.service.ts @@ -0,0 +1,201 @@ +import { + Injectable, + InternalServerErrorException, + NotFoundException, + Optional, + ServiceUnavailableException, +} from '@nestjs/common'; +import { DataSource } from 'typeorm'; +import { PedidoCondVenda7RowDto } from '../dto/pedido-condvenda7-row.dto'; + +@Injectable() +export class PedidoSevenService { + constructor(@Optional() private readonly dataSource?: DataSource) {} + + private static toNumber(value: unknown, field: string): number { + if (typeof value === 'number' && Number.isFinite(value)) return value; + if (typeof value === 'string' && value.trim() !== '') { + const n = Number(value); + if (Number.isFinite(n)) return n; + } + throw new InternalServerErrorException(`Campo ${field} invalido`); + } + + private static asString(value: unknown, field: string): string { + if (typeof value === 'string') return value; + if (value == null) return ''; + if (typeof value === 'number' || typeof value === 'boolean') { + return String(value); + } + throw new InternalServerErrorException(`Campo ${field} invalido`); + } + + private static toDate(value: unknown, field: string): Date { + if (value instanceof Date && !Number.isNaN(value.getTime())) return value; + if (typeof value === 'string' || typeof value === 'number') { + const d = new Date(value); + if (!Number.isNaN(d.getTime())) return d; + } + throw new InternalServerErrorException(`Campo ${field} invalido`); + } + + private static toNullableDate(value: unknown): Date | null { + if (value == null) return null; + if (value instanceof Date && !Number.isNaN(value.getTime())) return value; + if (typeof value === 'string' || typeof value === 'number') { + const d = new Date(value); + if (!Number.isNaN(d.getTime())) return d; + } + return null; + } + + private static pick(row: Record, key: string): unknown { + return ( + row[key] ?? + row[key.toUpperCase()] ?? + row[key.toLowerCase()] ?? + row[key.replace(/_/g, '')] ?? + row[key.toUpperCase().replace(/_/g, '')] + ); + } + + private static mapRow(row: Record): PedidoCondVenda7RowDto { + const dto = new PedidoCondVenda7RowDto(); + + dto.numped = this.toNumber(this.pick(row, 'NUMPED'), 'NUMPED'); + dto.cgc = this.asString(this.pick(row, 'CGC'), 'CGC'); + dto.razaosocial = this.asString( + this.pick(row, 'RAZAOSOCIAL'), + 'RAZAOSOCIAL', + ); + dto.dtemissao = this.toDate(this.pick(row, 'DTEMISSAO'), 'DTEMISSAO'); + + dto.codfilial = this.toNumber(this.pick(row, 'CODFILIAL'), 'CODFILIAL'); + dto.filialvenda = this.asString( + this.pick(row, 'FILIALVENDA'), + 'FILIALVENDA', + ); + + dto.codfilialretira = this.toNumber( + this.pick(row, 'CODFILIALRETIRA'), + 'CODFILIALRETIRA', + ); + dto.filialretira = this.asString( + this.pick(row, 'FILIALRETIRA'), + 'FILIALRETIRA', + ); + + dto.data = this.toDate(this.pick(row, 'DATA'), 'DATA'); + dto.numcar = this.toNumber(this.pick(row, 'NUMCAR'), 'NUMCAR'); + dto.codcli = this.toNumber(this.pick(row, 'CODCLI'), 'CODCLI'); + dto.cliente = this.asString(this.pick(row, 'CLIENTE'), 'CLIENTE'); + dto.codusur = this.toNumber(this.pick(row, 'CODUSUR'), 'CODUSUR'); + dto.vendedor = this.asString(this.pick(row, 'VENDEDOR'), 'VENDEDOR'); + dto.vlfrete = this.toNumber(this.pick(row, 'VLFRETE'), 'VLFRETE'); + dto.valor = this.toNumber(this.pick(row, 'VALOR'), 'VALOR'); + dto.tipoentrega = this.asString( + this.pick(row, 'TIPOENTREGA'), + 'TIPOENTREGA', + ); + dto.dtentrega = this.toNullableDate(this.pick(row, 'DTENTREGA')); + dto.codprod = this.toNumber(this.pick(row, 'CODPROD'), 'CODPROD'); + dto.descricao = this.asString(this.pick(row, 'DESCRICAO'), 'DESCRICAO'); + dto.qt = this.toNumber(this.pick(row, 'QT'), 'QT'); + dto.hash = this.asString(this.pick(row, 'HASH'), 'HASH'); + + return dto; + } + + async consultarPorNumped7( + numped7: number, + ): Promise { + if (!this.dataSource) { + throw new ServiceUnavailableException('DataSource nao disponivel'); + } + + if (!this.dataSource.isInitialized) { + throw new ServiceUnavailableException( + 'Banco de dados indisponivel (DataSource nao inicializado)', + ); + } + + const sql = ` +SELECT + PCPEDC.NUMPED, + PCFILIAL.CGC, + PCFILIAL.RAZAOSOCIAL, + SYSDATE DTEMISSAO, + PCPEDC.CODFILIAL, + PCFILIAL.FANTASIA FILIALVENDA, + PCPEDI.CODFILIALRETIRA, + RETIRA.FANTASIA FILIALRETIRA, + PCPEDC.DATA, + PCPEDC.NUMCAR, + PCPEDC.CODCLI, + PCCLIENT.CLIENTE CLIENTE, + PCNFSAID.CODUSUR, + PCUSUARI.NOME VENDEDOR, + PCPEDC.VLFRETE, + DECODE(PCNFSAID.VLTOTGER, 0, PCNFSAID.VLTOTAL, PCNFSAID.VLTOTAL) VALOR, + DECODE( + PCPEDI.TIPOENTREGA, + 'EN', 'ENTREGA', + 'EF', 'ENCOMENDA', + 'RP', 'RETIRA POSTERIOR', + 'RI', 'RETIRA IMEDIATA', + 'RETIRA IMEDIATA' + ) TIPOENTREGA, + PCPEDC.DTENTREGA, + PCPEDI.CODPROD, + PCPRODUT.DESCRICAO, + PCPEDI.QT, + MD5(PCNFSAID.NUMPED || '@Juru2025$') HASH +FROM + PCPEDC, + PCPEDI, + PCNFSAID, + PCCLIENT, + PCUSUARI, + PCPRODUT, + PCFILIAL, + PCFILIAL RETIRA +WHERE + PCNFSAID.NUMPED = PCPEDC.NUMPEDENTFUT + AND PCPEDC.CODFILIAL = PCFILIAL.CODIGO + AND PCPEDI.CODFILIALRETIRA = RETIRA.CODIGO + AND PCPEDI.CODPROD = PCPRODUT.CODPROD + AND PCPEDC.CODCLI = PCCLIENT.CODCLI + AND PCNFSAID.CODUSUR = PCUSUARI.CODUSUR + AND PCPEDC.NUMPED = PCPEDI.NUMPED + AND PCNFSAID.DTCANCEL IS NULL + AND PCNFSAID.CONDVENDA = 7 + AND PCPEDC.NUMPEDENTFUT = :1 +`; + + try { + const rows = (await this.dataSource.query(sql, [numped7])) as unknown; + if (!Array.isArray(rows)) { + throw new InternalServerErrorException('Resposta invalida do banco'); + } + + if (rows.length === 0) { + throw new NotFoundException( + `Pedido nao encontrado para NUMPEDENTFUT=${numped7}`, + ); + } + + return rows.map((r) => { + if (r && typeof r === 'object' && !Array.isArray(r)) { + return PedidoSevenService.mapRow(r as Record); + } + throw new InternalServerErrorException( + 'Linha invalida retornada pelo banco', + ); + }); + } catch (err) { + throw new InternalServerErrorException( + `Erro ao executar a consulta: ${err instanceof Error ? err.message : String(err)}`, + ); + } + } +} From a5d15a3f09b0d017040413c04905394b7bac4bce Mon Sep 17 00:00:00 2001 From: Felipe Batista Date: Tue, 17 Mar 2026 16:29:46 -0300 Subject: [PATCH 3/3] teste --- ecosystem.config.js | 24 ++++++++ package-lock.json | 86 +++++++++++----------------- src/controller/printer.controller.ts | 20 +++++++ src/dto/print-job-status.dto.ts | 33 +++++++++++ src/services/pedido-seven.service.ts | 27 ++++++++- src/services/printer.service.ts | 25 +++++++- 6 files changed, 161 insertions(+), 54 deletions(-) create mode 100644 ecosystem.config.js create mode 100644 src/dto/print-job-status.dto.ts diff --git a/ecosystem.config.js b/ecosystem.config.js new file mode 100644 index 0000000..d7a72ef --- /dev/null +++ b/ecosystem.config.js @@ -0,0 +1,24 @@ +module.exports = { + apps: [{ + name: 'nest-api', + script: 'dist/main.js', + instances: 1, + exec_mode: 'fork', + watch: false, + env: { NODE_ENV: 'development' }, + env_production: { NODE_ENV: 'production' } + }], + + deploy : { + production : { + user : 'SSH_USERNAME', + host : 'SSH_HOSTMACHINE', + ref : 'origin/master', + repo : 'GIT_REPOSITORY', + path : 'DESTINATION_PATH', + 'pre-deploy-local': '', + 'post-deploy' : 'npm install && pm2 reload ecosystem.config.js --env production', + 'pre-setup': '' + } + } +}; diff --git a/package-lock.json b/package-lock.json index 5bdc4ee..577cd23 100644 --- a/package-lock.json +++ b/package-lock.json @@ -327,7 +327,6 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.0.tgz", "integrity": "sha512-97z/ju/Jy1rZmDxybphrBuI+jtJjFVoz7Mr9yUQVVVi+DNZE333uFQeMOqcCIy1x3WYBIbWftUSLmbNXNT7qFQ==", "dev": true, - "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.22.13", @@ -875,7 +874,6 @@ "resolved": "https://registry.npmjs.org/@bull-board/api/-/api-6.16.4.tgz", "integrity": "sha512-fn4O+QbA3mRj0rEE41mvwbvtiiv0UYgnxQ9ErWb9n74EwIC/yZbiyxQ+Gh/ehU9u7B0PuaNyR0IOG/h3DGo1Mg==", "license": "MIT", - "peer": true, "dependencies": { "redis-info": "^3.1.0" }, @@ -1092,7 +1090,6 @@ "resolved": "https://registry.npmjs.org/@bull-board/ui/-/ui-6.16.4.tgz", "integrity": "sha512-5Yv+4g0rDvBBq2RxaUewSEwD8ywvqCX6lKlzPM5Aaf0+4cxGoENQRZNcBaAIKX4+fAzAbdVB4VGP4NUgtx5LVg==", "license": "MIT", - "peer": true, "dependencies": { "@bull-board/api": "6.16.4" } @@ -1111,7 +1108,7 @@ "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "devOptional": true, + "dev": true, "dependencies": { "@jridgewell/trace-mapping": "0.3.9" }, @@ -1123,7 +1120,7 @@ "version": "0.3.9", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "devOptional": true, + "dev": true, "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" @@ -2076,7 +2073,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", - "devOptional": true, + "dev": true, "engines": { "node": ">=6.0.0" } @@ -2104,7 +2101,7 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { @@ -2214,7 +2211,6 @@ "resolved": "https://registry.npmjs.org/@nestjs/bull-shared/-/bull-shared-11.0.4.tgz", "integrity": "sha512-VBJcDHSAzxQnpcDfA0kt9MTGUD1XZzfByV70su0W0eDCQ9aqIEBlzWRW21tv9FG9dIut22ysgDidshdjlnczLw==", "license": "MIT", - "peer": true, "dependencies": { "tslib": "2.8.1" }, @@ -2339,7 +2335,6 @@ "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.0.17.tgz", "integrity": "sha512-FwKylI/hVxaNvzBJdWMMG1LH0cLKz4Oh4jKOHet2JUVMM9j6CuodRbrSnL++KL6PJY/b2E6AY58UDPLNeCqJWw==", "license": "MIT", - "peer": true, "dependencies": { "iterare": "1.2.1", "tslib": "2.8.1", @@ -2400,7 +2395,6 @@ "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.0.1.tgz", "integrity": "sha512-Yn7X2aInjmX7yxpH8TjJmgC0JPvs+tcreETkquSRmKbuK5J28dZDi8loiaw3eRTLLvzzUovv5mlqFxmVhDESOw==", "hasInstallScript": true, - "peer": true, "dependencies": { "@nuxt/opencollective": "0.4.1", "fast-safe-stringify": "2.1.1", @@ -2460,7 +2454,6 @@ "version": "11.0.1", "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.0.1.tgz", "integrity": "sha512-vkAcm4Lm/aAvvpYtRcRKuwzsliy4SqoSp0saHOIx6VdphIb1k7ziRkjDbLFDczDZmkiyX1pJ9kI5SHjoQzVDPw==", - "peer": true, "dependencies": { "body-parser": "1.20.3", "cors": "2.8.5", @@ -2900,7 +2893,6 @@ "resolved": "https://registry.npmjs.org/@swc/cli/-/cli-0.6.0.tgz", "integrity": "sha512-Q5FsI3Cw0fGMXhmsg7c08i4EmXCrcl+WnAxb6LYOLHw4JFFC3yzmx9LaXZ7QMbA+JZXbigU2TirI7RAfO0Qlnw==", "dev": true, - "peer": true, "dependencies": { "@swc/counter": "^0.1.3", "@xhmikosr/bin-wrapper": "^13.0.5", @@ -2970,7 +2962,6 @@ "dev": true, "hasInstallScript": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@swc/counter": "^0.1.3", "@swc/types": "^0.1.17" @@ -3216,25 +3207,25 @@ "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", - "devOptional": true + "dev": true }, "node_modules/@tsconfig/node12": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "devOptional": true + "dev": true }, "node_modules/@tsconfig/node14": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "devOptional": true + "dev": true }, "node_modules/@tsconfig/node16": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "devOptional": true + "dev": true }, "node_modules/@types/babel__core": { "version": "7.20.2", @@ -3305,7 +3296,6 @@ "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.2.tgz", "integrity": "sha512-uQDwm1wFHmbBbCZCqAlq6Do9LYwByNZHWzXppSnay9SuwJ+VRbjkbLABer54kcPnMSlG6Fdiy2yaFXm/z9Z5gw==", "dev": true, - "peer": true, "dependencies": { "@types/estree": "*", "@types/json-schema": "*" @@ -3433,7 +3423,6 @@ "version": "22.10.7", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.7.tgz", "integrity": "sha512-V09KvXxFiutGp6B7XkpaDXlNadZxrzajcY50EuoLIpQ6WWYCSvf19lVIazzfIzQvhUN2HjX12spLojTnhuKlGg==", - "peer": true, "dependencies": { "undici-types": "~6.20.0" } @@ -3553,7 +3542,6 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.20.0.tgz", "integrity": "sha512-gKXG7A5HMyjDIedBi6bUrDcun8GIjnI8qOwVLiY3rx6T/sHP/19XLJOnIq/FgQvWLHja5JN/LSE7eklNBr612g==", "dev": true, - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.20.0", "@typescript-eslint/types": "8.20.0", @@ -4548,8 +4536,7 @@ "version": "8.14.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", - "devOptional": true, - "peer": true, + "dev": true, "bin": { "acorn": "bin/acorn" }, @@ -4570,7 +4557,7 @@ "version": "8.2.0", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "devOptional": true, + "dev": true, "engines": { "node": ">=0.4.0" } @@ -4589,7 +4576,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -4769,7 +4755,7 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "devOptional": true + "dev": true }, "node_modules/argparse": { "version": "2.0.1", @@ -4953,6 +4939,21 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "node_modules/bare-events": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", + "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", + "license": "Apache-2.0", + "optional": true, + "peerDependencies": { + "bare-abort-controller": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + } + } + }, "node_modules/bare-fs": { "version": "4.5.2", "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.5.2.tgz", @@ -5207,7 +5208,6 @@ "url": "https://github.com/sponsors/ai" } ], - "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001688", "electron-to-chromium": "^1.5.73", @@ -5284,7 +5284,6 @@ "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.66.6.tgz", "integrity": "sha512-4EAwZQqbTugEAfI1OI8EhaicSFDNBoDrUlrigPO/KH+4+1UeOWQ2h2OQx+92js957q2L/bLhxvK7/sylzMcgPg==", "license": "MIT", - "peer": true, "dependencies": { "cron-parser": "4.9.0", "ioredis": "5.9.1", @@ -5462,7 +5461,6 @@ "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "readdirp": "^4.0.1" }, @@ -5852,7 +5850,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "devOptional": true + "dev": true }, "node_modules/cron-parser": { "version": "4.9.0", @@ -6089,8 +6087,7 @@ "version": "0.0.1534754", "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1534754.tgz", "integrity": "sha512-26T91cV5dbOYnXdJi5qQHoTtUoNEqwkHcAyu/IKtjIAxiEqPMrDiRkDOPWVsGfNZGmlQVHQbZRSjD8sxagWVsQ==", - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/dezalgo": { "version": "1.0.4", @@ -6106,7 +6103,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "devOptional": true, + "dev": true, "engines": { "node": ">=0.3.1" } @@ -6349,7 +6346,6 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.18.0.tgz", "integrity": "sha512-+waTfRWQlSbpt3KWE+CjrPPYnbq9kfZIYUqapc0uBXyjTp8aYXZDsUH16m39Ryq3NjAVP4tjuF7KaukeqoCoaA==", "dev": true, - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -6409,7 +6405,6 @@ "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.0.1.tgz", "integrity": "sha512-lZBts941cyJyeaooiKxAtzoPHTN+GbQTJFAIdQbRhA4/8whaAraEh47Whw/ZFfrjNSnlAxqfm9i0XVAEkULjCw==", "dev": true, - "peer": true, "bin": { "eslint-config-prettier": "build/bin/cli.js" }, @@ -6650,7 +6645,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/express/-/express-5.0.1.tgz", "integrity": "sha512-ORF7g6qGnD+YtUG9yx4DFoqCShNMmUKiXuT5oWMHiOvt/4WFbHC6yCwQMTSBMno7AqntNCAzzcnnjowRkTL9eQ==", - "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.0.1", @@ -8171,7 +8165,6 @@ "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, - "peer": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", @@ -9007,7 +9000,7 @@ "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "devOptional": true + "dev": true }, "node_modules/makeerror": { "version": "1.0.12", @@ -9533,7 +9526,6 @@ "integrity": "sha512-kGUumXmrEWbSpBuKJyb9Ip3rXcNgKK6grunI3/cLPzrRvboZ6ZoLi9JQ+z6M/RIG924tY8BLflihL4CKKQAYMA==", "hasInstallScript": true, "license": "(Apache-2.0 OR UPL-1.0)", - "peer": true, "engines": { "node": ">=14.17" } @@ -9899,7 +9891,6 @@ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz", "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==", "dev": true, - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -10276,8 +10267,7 @@ "node_modules/reflect-metadata": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", - "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", - "peer": true + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==" }, "node_modules/repeat-string": { "version": "1.6.1", @@ -10449,7 +10439,6 @@ "version": "7.8.1", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "peer": true, "dependencies": { "tslib": "^2.1.0" } @@ -11277,7 +11266,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -11567,8 +11555,7 @@ "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "devOptional": true, - "peer": true, + "dev": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -11733,7 +11720,6 @@ "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.28.tgz", "integrity": "sha512-6GH7wXhtfq2D33ZuRXYwIsl/qM5685WZcODZb7noOOcRMteM9KF2x2ap3H0EBjnSV0VO4gNAfJT5Ukp0PkOlvg==", "license": "MIT", - "peer": true, "dependencies": { "@sqltools/formatter": "^1.2.5", "ansis": "^4.2.0", @@ -11949,8 +11935,7 @@ "version": "5.7.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", - "devOptional": true, - "peer": true, + "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -12126,7 +12111,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "devOptional": true + "dev": true }, "node_modules/v8-to-istanbul": { "version": "9.1.0", @@ -12199,7 +12184,6 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.97.1.tgz", "integrity": "sha512-EksG6gFY3L1eFMROS/7Wzgrii5mBAFe4rIr3r2BTfo7bcc+DWwFZ4OJ/miOuHJO/A85HwyI4eQ0F6IKXesO7Fg==", "dev": true, - "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.6", @@ -12482,7 +12466,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "devOptional": true, + "dev": true, "engines": { "node": ">=6" } diff --git a/src/controller/printer.controller.ts b/src/controller/printer.controller.ts index 845ed64..5f1e333 100644 --- a/src/controller/printer.controller.ts +++ b/src/controller/printer.controller.ts @@ -9,6 +9,7 @@ import { } from '@nestjs/common'; import { ApiBody, + ApiNotFoundResponse, ApiOkResponse, ApiOperation, ApiParam, @@ -21,6 +22,7 @@ import { PrinterDto } from '../dto/printer.dto'; import { getPrinters } from '../services/get-printer'; import { PrintResultDto } from '../dto/print-result.dto'; import { PrintHtmlResultDto } from '../dto/print-html-result.dto'; +import { PrintJobStatusDto } from '../dto/print-job-status.dto'; @ApiTags('Printer') @Controller('printer') @@ -113,4 +115,22 @@ export class PrinterController { async listPrinters() { return getPrinters(); } + + @Get('jobs/:jobId') + @ApiOperation({ summary: 'Consulta o status de um job de impressão' }) + @ApiParam({ + name: 'jobId', + example: '123', + description: 'ID do job enfileirado (BullMQ job id)', + }) + @ApiOkResponse({ + description: 'Status atual do job', + type: PrintJobStatusDto, + }) + @ApiNotFoundResponse({ + description: 'Job não encontrado', + }) + async getJobStatus(@Param('jobId') jobId: string) { + return this.printerService.getPrintJobStatus(jobId); + } } diff --git a/src/dto/print-job-status.dto.ts b/src/dto/print-job-status.dto.ts new file mode 100644 index 0000000..e232511 --- /dev/null +++ b/src/dto/print-job-status.dto.ts @@ -0,0 +1,33 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class PrintJobStatusDto { + @ApiProperty({ example: '123' }) + jobId: string; + + @ApiProperty({ example: 'print-html-job' }) + name: string; + + @ApiProperty({ example: 'completed' }) + state: string; + + @ApiProperty({ required: false, example: 'POS-80' }) + printerName?: string; + + @ApiProperty({ required: false, example: 0 }) + attemptsMade?: number; + + @ApiProperty({ required: false, example: 1738200000000 }) + timestamp?: number; + + @ApiProperty({ required: false, example: 1738200000100 }) + processedOn?: number; + + @ApiProperty({ required: false, example: 1738200000500 }) + finishedOn?: number; + + @ApiProperty({ required: false, example: 'Some error message' }) + failedReason?: string; + + @ApiProperty({ required: false }) + result?: unknown; +} diff --git a/src/services/pedido-seven.service.ts b/src/services/pedido-seven.service.ts index e1b4ed6..2d83e54 100644 --- a/src/services/pedido-seven.service.ts +++ b/src/services/pedido-seven.service.ts @@ -39,6 +39,16 @@ export class PedidoSevenService { throw new InternalServerErrorException(`Campo ${field} invalido`); } + private static toNullableNumber(value: unknown): number { + if (value == null) return 0; + if (typeof value === 'number' && Number.isFinite(value)) return value; + if (typeof value === 'string' && value.trim() !== '') { + const n = Number(value); + if (Number.isFinite(n)) return n; + } + return 0; + } + private static toNullableDate(value: unknown): Date | null { if (value == null) return null; if (value instanceof Date && !Number.isNaN(value.getTime())) return value; @@ -92,7 +102,7 @@ export class PedidoSevenService { dto.codusur = this.toNumber(this.pick(row, 'CODUSUR'), 'CODUSUR'); dto.vendedor = this.asString(this.pick(row, 'VENDEDOR'), 'VENDEDOR'); dto.vlfrete = this.toNumber(this.pick(row, 'VLFRETE'), 'VLFRETE'); - dto.valor = this.toNumber(this.pick(row, 'VALOR'), 'VALOR'); + dto.valor = this.toNullableNumber(this.pick(row, 'VALOR')); dto.tipoentrega = this.asString( this.pick(row, 'TIPOENTREGA'), 'TIPOENTREGA', @@ -136,7 +146,17 @@ SELECT PCNFSAID.CODUSUR, PCUSUARI.NOME VENDEDOR, PCPEDC.VLFRETE, - DECODE(PCNFSAID.VLTOTGER, 0, PCNFSAID.VLTOTAL, PCNFSAID.VLTOTAL) VALOR, + CASE + WHEN ROW_NUMBER() OVER ( + PARTITION BY PCPEDC.NUMPEDENTFUT + ORDER BY PCPEDC.NUMPED, PCPEDI.ROWID + ) = 1 + THEN CASE + WHEN NVL(PCNFSAID.VLTOTGER, 0) = 0 THEN PCNFSAID.VLTOTAL + ELSE PCNFSAID.VLTOTGER + END + ELSE NULL + END VALOR, DECODE( PCPEDI.TIPOENTREGA, 'EN', 'ENTREGA', @@ -170,6 +190,9 @@ WHERE AND PCNFSAID.DTCANCEL IS NULL AND PCNFSAID.CONDVENDA = 7 AND PCPEDC.NUMPEDENTFUT = :1 +ORDER BY + PCPEDC.NUMPED, + PCPEDI.CODPROD `; try { diff --git a/src/services/printer.service.ts b/src/services/printer.service.ts index 8e4681c..4089c92 100644 --- a/src/services/printer.service.ts +++ b/src/services/printer.service.ts @@ -1,9 +1,10 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable, NotFoundException } from '@nestjs/common'; import { InjectQueue } from '@nestjs/bullmq'; import { Queue } from 'bullmq'; import { PrintDataDto } from '../dto/print-data.dto'; import { PrintHtmlDto } from '../dto/print-html.dto'; import { ThermalPrinter, PrinterTypes } from 'node-thermal-printer'; +import { PrintJobStatusDto } from '../dto/print-job-status.dto'; @Injectable() export class PrinterService { @@ -92,4 +93,26 @@ export class PrinterService { jobId: job.id, }; } + + async getPrintJobStatus(jobId: string): Promise { + const job = await this.printerQueue.getJob(jobId); + if (!job) { + throw new NotFoundException(`Job not found: ${jobId}`); + } + + const state = await job.getState(); + + return { + jobId: String(job.id), + name: job.name, + state, + printerName: (job.data as any)?.printerName, + attemptsMade: job.attemptsMade, + timestamp: job.timestamp, + processedOn: job.processedOn, + finishedOn: job.finishedOn, + failedReason: job.failedReason || undefined, + result: job.returnvalue ?? undefined, + }; + } }