diff --git a/src/products/dto/oferta-8026-query.dto.ts b/src/products/dto/oferta-8026-query.dto.ts new file mode 100644 index 0000000..b7aaa5a --- /dev/null +++ b/src/products/dto/oferta-8026-query.dto.ts @@ -0,0 +1,36 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsArray, IsNotEmpty, IsNumber, IsString, Matches } from 'class-validator'; + +/** + * DTO para requisição de ofertas 8026 + */ +export class Oferta8026QueryDto { + @ApiProperty({ + description: 'Data de início da vigência da promoção (formato DD/MM/YYYY)', + example: '19/11/2025', + }) + @IsString() + @Matches(/^\d{2}\/\d{2}\/\d{4}$/, { + message: 'Data deve estar no formato DD/MM/YYYY', + }) + @IsNotEmpty() + data: string; + + @ApiProperty({ + description: 'Array de códigos de produtos', + example: [62602], + type: [Number], + }) + @IsArray() + @IsNotEmpty() + codprod: number[]; + + @ApiProperty({ + description: 'Número da região', + example: 1, + }) + @IsNumber() + @IsNotEmpty() + numregiao: number; +} + diff --git a/src/products/dto/oferta-8026-response.dto.ts b/src/products/dto/oferta-8026-response.dto.ts new file mode 100644 index 0000000..f8577a5 --- /dev/null +++ b/src/products/dto/oferta-8026-response.dto.ts @@ -0,0 +1,76 @@ +import { ApiProperty } from '@nestjs/swagger'; + +/** + * DTO para resposta de ofertas 8026 + */ +export class Oferta8026ResponseDto { + @ApiProperty({ + description: 'Código do produto', + example: 12345, + }) + codprod: number; + + @ApiProperty({ + description: 'Descrição do produto', + example: 'PRODUTO EXEMPLO', + }) + descricao: string; + + @ApiProperty({ + description: 'Marca do produto', + example: 'MARCA EXEMPLO', + }) + marca: string; + + @ApiProperty({ + description: 'Unidade do produto', + example: 'UN', + }) + unidade: string; + + @ApiProperty({ + description: 'Preço de venda 1', + example: 99.9, + }) + pvenda1: number; + + @ApiProperty({ + description: 'Preço fixo promocional', + example: 79.9, + }) + precofixo: number; + + @ApiProperty({ + description: 'Percentual de desconto', + example: 20, + }) + percdesconto: number; + + @ApiProperty({ + description: 'Data de fim da vigência', + example: '2024-12-31', + }) + dtfimvigencia: Date; + + @ApiProperty({ + description: 'Mensagem para débito', + example: 'DEBITO', + required: false, + }) + mensagem2: string | null; + + @ApiProperty({ + description: 'Mensagem para à vista', + example: 'À VISTA', + required: false, + }) + mensagem3: string | null; + + @ApiProperty({ + description: 'Mensagem para 10x', + example: '10X', + required: false, + }) + mensagem4: string | null; +} + diff --git a/src/products/products.controller.ts b/src/products/products.controller.ts index 315013d..962fa41 100644 --- a/src/products/products.controller.ts +++ b/src/products/products.controller.ts @@ -27,6 +27,8 @@ import { ProductDetailResponseDto } from './dto/product-detail-response.dto'; import { RotinaA4QueryDto } from './dto/rotina-a4-query.dto'; import { RotinaA4ResponseDto } from './dto/rotina-a4-response.dto'; import { UnifiedProductSearchDto } from './dto/unified-product-search.dto'; +import { Oferta8026QueryDto } from './dto/oferta-8026-query.dto'; +import { Oferta8026ResponseDto } from './dto/oferta-8026-response.dto'; //@ApiBearerAuth() //@UseGuards(JwtAuthGuard) @@ -151,4 +153,23 @@ export class ProductsController { ): Promise { return this.productsService.unifiedProductSearch(query); } + + /** + * Endpoint para buscar ofertas 8026 + */ + @Post('oferta-8026') + @ApiOperation({ summary: 'Busca ofertas 8026 conforme parâmetros específicos' }) + @ApiBody({ type: Oferta8026QueryDto }) + @ApiResponse({ + status: 200, + description: 'Lista de ofertas retornada com sucesso.', + type: Oferta8026ResponseDto, + isArray: true, + }) + @ApiResponse({ status: 400, description: 'Parâmetros inválidos.' }) + async getOferta8026( + @Body() query: Oferta8026QueryDto, + ): Promise { + return this.productsService.getOferta8026(query); + } } diff --git a/src/products/products.service.ts b/src/products/products.service.ts index 14e320c..7fc0329 100644 --- a/src/products/products.service.ts +++ b/src/products/products.service.ts @@ -9,6 +9,8 @@ import { ProductDetailResponseDto } from './dto/product-detail-response.dto'; import { RotinaA4QueryDto } from './dto/rotina-a4-query.dto'; import { RotinaA4ResponseDto } from './dto/rotina-a4-response.dto'; import { UnifiedProductSearchDto } from './dto/unified-product-search.dto'; +import { Oferta8026QueryDto } from './dto/oferta-8026-query.dto'; +import { Oferta8026ResponseDto } from './dto/oferta-8026-response.dto'; @Injectable() export class ProductsService { @@ -322,9 +324,6 @@ export class ProductsService { })); } - /** - * Busca unificada de produtos por nome, código de barras ou codprod - */ async unifiedProductSearch( query: UnifiedProductSearchDto, ): Promise { @@ -548,4 +547,85 @@ export class ProductsService { return produto; } + + async getOferta8026( + query: Oferta8026QueryDto, + ): Promise { + const { data, codprod, numregiao } = query; + + if (!codprod || codprod.length === 0) { + throw new HttpException( + 'É necessário informar pelo menos um código de produto.', + HttpStatus.BAD_REQUEST, + ); + } + + let dataFormatada = data; + const dateMatch = data.match(/^(\d{2})\/(\d{2})\/(\d{4})$/); + if (dateMatch) { + const [, part1, part2, year] = dateMatch; + const num2 = parseInt(part2, 10); + + dataFormatada = num2 > 12 + ? `${part2}/${part1}/${year}` + : `${part1}/${part2}/${year}`; + } + + const codprodPlaceholders: string[] = []; + const params: any[] = []; + let paramIndex = 0; + + params.push(dataFormatada); + paramIndex++; + + codprod.forEach((cod) => { + codprodPlaceholders.push(`:${paramIndex}`); + params.push(cod); + paramIndex++; + }); + + params.push(numregiao); + + const sql = ` + SELECT + pctabpr.codprod, + pcprodut.descricao, + pcmarca.marca, + pcprodut.unidade, + pctabpr.pvenda1, + pcprecoprom.precofixo, + TRUNC(((pctabpr.pvenda1 - pcprecoprom.precofixo) / pctabpr.pvenda1) * 100, 0) percdesconto, + pcprecoprom.dtfimvigencia, + CASE WHEN pcprecoprom.codplpagmax = 2 THEN 'DEBITO' ELSE NULL END mensagem2, + CASE WHEN pcprecoprom.codplpagmax = 10 THEN 'À VISTA' ELSE NULL END mensagem3, + CASE WHEN pcprecoprom.codplpagmax = 42 OR pcprecoprom.codplpagmax = 46 THEN '10X' ELSE NULL END mensagem4 + FROM pctabpr, pcprecoprom, pcplpag, pcprodut, pcmarca + WHERE pctabpr.codprod = pcprecoprom.codprod + AND pctabpr.numregiao = pcprecoprom.numregiao + AND pctabpr.codprod = pcprodut.codprod + AND pcprodut.codmarca = pcmarca.codmarca (+) + AND pcprecoprom.codplpagmax = pcplpag.codplpag (+) + AND TRUNC(pcprecoprom.dtiniciovigencia) = TRUNC(TO_DATE(:0, 'DD/MM/YYYY')) + AND pcprecoprom.codprod IN (${codprodPlaceholders.join(',')}) + AND PCPRECOPROM.DTFIMVIGENCIA >= TRUNC(SYSDATE) + AND pcprecoprom.codplpagmax IN (42, 46) + AND pctabpr.numregiao = :${paramIndex} + `; + + const result = await this.dataSource.query(sql, params); + + return result.map((row) => ({ + codprod: row.CODPROD, + descricao: row.DESCRICAO, + marca: row.MARCA, + unidade: row.UNIDADE, + pvenda1: row.PVENDA1, + precofixo: row.PRECOFIXO, + percdesconto: row.PERCDESCONTO, + dtfimvigencia: row.DTFIMVIGENCIA, + mensagem2: row.MENSAGEM2, + mensagem3: row.MENSAGEM3, + mensagem4: row.MENSAGEM4, + })); + } }