Compare commits

..

2 Commits

Author SHA1 Message Date
Joelbrit0 fc2045da89 chore: organize project and update service 2026-01-29 11:42:23 -03:00
Joelbrit0 66adb39b9b docs(swagger): clarify API contracts and response schemas 2026-01-26 18:48:06 -03:00
14 changed files with 507 additions and 73 deletions

View File

@ -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

View File

@ -28,13 +28,18 @@ 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 |
| :------------------- | :------------------------------- | :------------------------ |
| :------------------- | :-------------------------------------------- | :------------------------ |
| `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=...)` |
| `ORACLE_CLIENT_PATH` | Caminho do Oracle Instant Client | `C:\oracle\instantclient` |
| `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`
---

View File

@ -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 {}

View File

@ -8,11 +8,11 @@ export function createTypeOrmOptions(
): TypeOrmModuleOptions {
const baseOptions: DataSourceOptions = {
type: 'oracle',
username: configService.get<string>('DB_USERNAME') || 'teste',
password: configService.get<string>('DB_PASSWORD') || 'teste',
username: configService.get<string>('DB_USERNAME') || 'SEVEN',
password: configService.get<string>('DB_PASSWORD') || 'USR54SEV',
connectString:
configService.get<string>('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}'],

View File

@ -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);
}
}

View File

@ -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],
})

View File

@ -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;
}

View File

@ -2,8 +2,12 @@ import { ApiProperty } from '@nestjs/swagger';
export class PrintDataDto {
@ApiProperty({
example: ['--------------------------------', ' TESTE DE IMPRESSAO ', '--------------------------------'],
description: 'Lista de linhas de texto para imprimir'
example: [
'--------------------------------',
' TESTE DE IMPRESSAO ',
'--------------------------------',
],
description: 'Lista de linhas de texto para imprimir',
})
lines: string[];
@ -11,21 +15,22 @@ export class PrintDataDto {
required: false,
enum: ['left', 'center', 'right'],
example: 'center',
description: 'Alinhamento do texto'
description: 'Alinhamento do texto',
})
alignment?: 'left' | 'center' | 'right';
@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;
}

View File

@ -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;
}

View File

@ -3,26 +3,36 @@ 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'
description: 'Conteúdo HTML para impressão',
})
html: string;
@ApiProperty({
example: 'POS-80',
description: 'Nome da impressora onde será impresso o PDF gerado'
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)'
description: 'ID único do job para idempotência (evita duplicados)',
})
jobId?: string;
}

View File

@ -0,0 +1,9 @@
import { ApiProperty } from '@nestjs/swagger';
export class PrintResultDto {
@ApiProperty({ example: true })
success: boolean;
@ApiProperty({ example: 'Print successful!' })
message: string;
}

View File

@ -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;
}

View File

@ -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);

View File

@ -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<string, unknown>, 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<string, unknown>): 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<PedidoCondVenda7RowDto[]> {
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<string, unknown>);
}
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)}`,
);
}
}
}