feat: adiciona endpoint oferta-8026 para buscar ofertas promocionais
This commit is contained in:
36
src/products/dto/oferta-8026-query.dto.ts
Normal file
36
src/products/dto/oferta-8026-query.dto.ts
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
76
src/products/dto/oferta-8026-response.dto.ts
Normal file
76
src/products/dto/oferta-8026-response.dto.ts
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -27,6 +27,8 @@ import { ProductDetailResponseDto } from './dto/product-detail-response.dto';
|
|||||||
import { RotinaA4QueryDto } from './dto/rotina-a4-query.dto';
|
import { RotinaA4QueryDto } from './dto/rotina-a4-query.dto';
|
||||||
import { RotinaA4ResponseDto } from './dto/rotina-a4-response.dto';
|
import { RotinaA4ResponseDto } from './dto/rotina-a4-response.dto';
|
||||||
import { UnifiedProductSearchDto } from './dto/unified-product-search.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()
|
//@ApiBearerAuth()
|
||||||
//@UseGuards(JwtAuthGuard)
|
//@UseGuards(JwtAuthGuard)
|
||||||
@@ -151,4 +153,23 @@ export class ProductsController {
|
|||||||
): Promise<ProductDetailResponseDto[]> {
|
): Promise<ProductDetailResponseDto[]> {
|
||||||
return this.productsService.unifiedProductSearch(query);
|
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<Oferta8026ResponseDto[]> {
|
||||||
|
return this.productsService.getOferta8026(query);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import { ProductDetailResponseDto } from './dto/product-detail-response.dto';
|
|||||||
import { RotinaA4QueryDto } from './dto/rotina-a4-query.dto';
|
import { RotinaA4QueryDto } from './dto/rotina-a4-query.dto';
|
||||||
import { RotinaA4ResponseDto } from './dto/rotina-a4-response.dto';
|
import { RotinaA4ResponseDto } from './dto/rotina-a4-response.dto';
|
||||||
import { UnifiedProductSearchDto } from './dto/unified-product-search.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()
|
@Injectable()
|
||||||
export class ProductsService {
|
export class ProductsService {
|
||||||
@@ -322,9 +324,6 @@ export class ProductsService {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Busca unificada de produtos por nome, código de barras ou codprod
|
|
||||||
*/
|
|
||||||
async unifiedProductSearch(
|
async unifiedProductSearch(
|
||||||
query: UnifiedProductSearchDto,
|
query: UnifiedProductSearchDto,
|
||||||
): Promise<ProductDetailResponseDto[]> {
|
): Promise<ProductDetailResponseDto[]> {
|
||||||
@@ -548,4 +547,85 @@ export class ProductsService {
|
|||||||
|
|
||||||
return produto;
|
return produto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getOferta8026(
|
||||||
|
query: Oferta8026QueryDto,
|
||||||
|
): Promise<Oferta8026ResponseDto[]> {
|
||||||
|
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,
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user