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)}`, + ); + } + } +}