fix: ajuste no endpoint de impressão de pedidos.

This commit is contained in:
joelson brito
2025-11-05 15:40:32 -03:00
parent 3849fa1c4e
commit e448a44144
13 changed files with 847 additions and 165 deletions

View File

@@ -7,6 +7,7 @@ import { StoreDto } from './dto/store.dto';
import { SellerDto } from './dto/seller.dto';
import { BillingDto } from './dto/billing.dto';
import { CustomerDto } from './dto/customer.dto';
import { RegionDto } from './dto/region.dto';
import { CarrierDto, FindCarriersDto } from './dto/carrier.dto';
@ApiTags('DataConsult')
@@ -103,4 +104,13 @@ export class DataConsultController {
return this.dataConsultService.getOrderCarriers(orderId);
}
@Get('regions')
//@UseGuards(JwtAuthGuard)
//@ApiBearerAuth()
@ApiOperation({ summary: 'Lista todas as regiões cadastradas' })
@ApiResponse({ status: 200, description: 'Lista de regiões retornada com sucesso', type: [RegionDto] })
async getRegions(): Promise<RegionDto[]> {
return this.dataConsultService.getRegions();
}
}

View File

@@ -6,6 +6,7 @@ import { SellerDto } from './dto/seller.dto';
import { BillingDto } from './dto/billing.dto';
import { CustomerDto } from './dto/customer.dto';
import { ProductDto } from './dto/product.dto';
import { RegionDto } from './dto/region.dto';
import { ConfigService } from '@nestjs/config';
import { DATA_SOURCE } from '../core/constants';
@@ -217,4 +218,19 @@ async findSellers(): Promise<SellerDto[]> {
`;
return await this.executeQuery<any[]>(sql, [orderId]);
}
/**
* Busca todas as regiões cadastradas
*/
async findRegions(): Promise<RegionDto[]> {
const sql = `
SELECT
PCREGIAO.NUMREGIAO as "numregiao",
PCREGIAO.REGIAO as "regiao"
FROM PCREGIAO
ORDER BY PCREGIAO.NUMREGIAO
`;
const results = await this.executeQuery<RegionDto[]>(sql);
return results.map(result => new RegionDto(result));
}
}

View File

@@ -5,6 +5,7 @@ import { SellerDto } from './dto/seller.dto';
import { BillingDto } from './dto/billing.dto';
import { CustomerDto } from './dto/customer.dto';
import { ProductDto } from './dto/product.dto';
import { RegionDto } from './dto/region.dto';
import { CarrierDto, FindCarriersDto } from './dto/carrier.dto';
import { ILogger } from '../Log/ILogger';
import { RedisClientToken } from '../core/configs/cache/redis-client.adapter.provider';
@@ -24,6 +25,8 @@ export class DataConsultService {
private readonly CUSTOMERS_TTL = 3600;
private readonly CARRIERS_CACHE_KEY = 'data-consult:carriers:all';
private readonly CARRIERS_TTL = 3600;
private readonly REGIONS_CACHE_KEY = 'data-consult:regions';
private readonly REGIONS_TTL = 7200;
constructor(
private readonly repository: DataConsultRepository,
@@ -205,4 +208,26 @@ export class DataConsultService {
throw new HttpException('Erro ao buscar transportadoras do pedido', HttpStatus.INTERNAL_SERVER_ERROR);
}
}
/**
* Obter todas as regiões cadastradas
* @returns Array de RegionDto
*/
async getRegions(): Promise<RegionDto[]> {
this.logger.log('Buscando todas as regiões');
try {
return getOrSetCache<RegionDto[]>(
this.redisClient,
this.REGIONS_CACHE_KEY,
this.REGIONS_TTL,
async () => {
const regions = await this.repository.findRegions();
return regions;
}
);
} catch (error) {
this.logger.error('Erro ao buscar regiões', error);
throw new HttpException('Erro ao buscar regiões', HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}

View File

@@ -0,0 +1,23 @@
import { ApiProperty } from '@nestjs/swagger';
/**
* DTO para dados de região
*/
export class RegionDto {
@ApiProperty({
description: 'Código da região',
example: 1,
})
numregiao: number;
@ApiProperty({
description: 'Nome/descrição da região',
example: 'REGIÃO SUL',
})
regiao: string;
constructor(partial: Partial<RegionDto>) {
Object.assign(this, partial);
}
}

84
src/orders/dto/DebDto.ts Normal file
View File

@@ -0,0 +1,84 @@
import { ApiProperty } from '@nestjs/swagger';
/**
* DTO para dados de débitos/prestações de clientes
*/
export class DebDto {
@ApiProperty({
description: 'Data de emissão da prestação',
example: '2024-01-15',
type: Date,
})
dtemissao: Date;
@ApiProperty({
description: 'Código da filial',
example: '1',
})
codfilial: string;
@ApiProperty({
description: 'Número da duplicata',
example: '12345',
})
duplic: string;
@ApiProperty({
description: 'Número da prestação',
example: '1',
})
prest: string;
@ApiProperty({
description: 'Código do cliente',
example: 1000,
})
codcli: number;
@ApiProperty({
description: 'Nome do cliente',
example: 'JOÃO DA SILVA',
})
cliente: string;
@ApiProperty({
description: 'Código de cobrança',
example: 'BL',
})
codcob: string;
@ApiProperty({
description: 'Descrição da forma de cobrança',
example: 'BOLETO',
})
cobranca: string;
@ApiProperty({
description: 'Data de vencimento',
example: '2024-02-15',
type: Date,
})
dtvenc: Date;
@ApiProperty({
description: 'Data de pagamento',
example: '2024-02-10',
type: Date,
nullable: true,
})
dtpag: Date | null;
@ApiProperty({
description: 'Valor da prestação',
example: 150.50,
})
valor: number;
@ApiProperty({
description: 'Situação da prestação',
example: 'PAGO',
enum: ['PAGO', 'EM ATRASO', 'A VENCER', 'NENHUM'],
})
situacao: string;
}

View File

@@ -0,0 +1,33 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsArray, IsNotEmpty, IsNumber, IsString } from 'class-validator';
/**
* DTO para requisição de detalhes de produtos
*/
export class ProductDetailQueryDto {
@ApiProperty({
description: 'Código da região para buscar o preço',
example: 1,
})
@IsNumber()
@IsNotEmpty()
numregiao: number;
@ApiProperty({
description: 'Array de códigos de produtos',
example: [1, 2, 3],
type: [Number],
})
@IsArray()
@IsNotEmpty()
codprod: number[];
@ApiProperty({
description: 'Código da filial',
example: '1',
})
@IsString()
@IsNotEmpty()
codfilial: string;
}

View File

@@ -0,0 +1,55 @@
import { ApiProperty } from '@nestjs/swagger';
/**
* DTO para resposta de detalhes de produtos
*/
export class ProductDetailResponseDto {
@ApiProperty({
description: 'Código do produto',
example: 12345,
})
codprod: number;
@ApiProperty({
description: 'Descrição completa do produto (com marca)',
example: 'PRODUTO EXEMPLO - MARCA EXEMPLO',
})
descricao: string;
@ApiProperty({
description: 'Tipo de embalagem',
example: 'UN',
})
embalagem: string;
@ApiProperty({
description: 'Código auxiliar (código de barras)',
example: '7891234567890',
})
codauxiliar: string;
@ApiProperty({
description: 'Nome da marca',
example: 'MARCA EXEMPLO',
})
marca: string;
@ApiProperty({
description: 'Preço de venda do produto',
example: 99.90,
})
preco: number;
@ApiProperty({
description: 'Nome da filial',
example: 'FILIAL MATRIZ',
})
filial: string;
@ApiProperty({
description: 'Nome da região',
example: 'REGIÃO SUL',
})
regiao: string;
}

View File

@@ -1,59 +1,78 @@
/* eslint-disable prettier/prettier */
/* eslint-disable @typescript-eslint/no-unused-vars */
import { Body, Controller, Get, Param, Post,UseGuards } from '@nestjs/common';
import { ProductsService } from './products.service';
import { ExposedProduct } from 'src/core/models/exposed-product.model';
import { JwtAuthGuard } from 'src/auth/guards/jwt-auth.guard';
import { ExposedProductDto } from './dto/exposed-product.dto';
import { ProductValidationDto } from './dto/ProductValidationDto';
import { ProductEcommerceDto } from './dto/product-ecommerce.dto';
import { ApiTags, ApiOperation, ApiParam, ApiBody, ApiResponse, ApiBearerAuth } from '@nestjs/swagger';
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@ApiTags('Produtos')
@Controller('api/v1/products')
export class ProductsController {
constructor(private readonly productsService: ProductsService) {}
///enpoit produtos-ecommecer
@Get('products-ecommerce')
@ApiOperation({ summary: 'Lista produtos para o e-commerce' })
@ApiResponse({
status: 200,
description: 'Lista de produtos retornada com sucesso.',
type: ProductEcommerceDto,
isArray: true
})
///ENDPOIT DE VALIDAR PRODUTO POR FILTRO
@Get('product-validation/:storeId/:filtro')
@ApiOperation({ summary: 'Valida produto pelo filtro (código, EAN ou descrição)' })
@ApiParam({ name: 'storeId', type: String, description: 'ID da loja' })
@ApiParam({ name: 'filtro', type: String, description: 'Filtro de busca (código, EAN ou descrição)' })
@ApiResponse({
status: 200,
description: 'Produto encontrado com sucesso.',
type: ProductValidationDto
})
@ApiResponse({ status: 404, description: 'Produto não localizado.' })
async productValidation(
@Param('storeId') storeId: string,
@Param('filtro') filtro: string,
): Promise<ProductValidationDto> {
return this.productsService.productsValidation(storeId, filtro);
}
/// ENDPOIT PRODUTOS EXPOSTOS
@Post('exposed-product')
@ApiOperation({ summary: 'Registra produto em exposição' })
@ApiBody({ type: ExposedProductDto })
@ApiResponse({ status: 201, description: 'Produto exposto registrado com sucesso.' })
async exposedProduct(@Body() exposedProduct: ExposedProduct) {
return this.productsService.exposedProduct(exposedProduct);
}
}
/* eslint-disable prettier/prettier */
/* eslint-disable @typescript-eslint/no-unused-vars */
import { Body, Controller, Get, Param, Post,UseGuards } from '@nestjs/common';
import { ProductsService } from './products.service';
import { ExposedProduct } from 'src/core/models/exposed-product.model';
import { JwtAuthGuard } from 'src/auth/guards/jwt-auth.guard';
import { ExposedProductDto } from './dto/exposed-product.dto';
import { ProductValidationDto } from './dto/ProductValidationDto';
import { ProductEcommerceDto } from './dto/product-ecommerce.dto';
import { ApiTags, ApiOperation, ApiParam, ApiBody, ApiResponse, ApiBearerAuth } from '@nestjs/swagger';
import { ProductDetailQueryDto } from './dto/product-detail-query.dto';
import { ProductDetailResponseDto } from './dto/product-detail-response.dto';
//@ApiBearerAuth()
//@UseGuards(JwtAuthGuard)
@ApiTags('Produtos')
@Controller('api/v1/products')
export class ProductsController {
constructor(private readonly productsService: ProductsService) {}
///enpoit produtos-ecommecer
@Get('products-ecommerce')
@ApiOperation({ summary: 'Lista produtos para o e-commerce' })
@ApiResponse({
status: 200,
description: 'Lista de produtos retornada com sucesso.',
type: ProductEcommerceDto,
isArray: true
})
///ENDPOIT DE VALIDAR PRODUTO POR FILTRO
@Get('product-validation/:storeId/:filtro')
@ApiOperation({ summary: 'Valida produto pelo filtro (código, EAN ou descrição)' })
@ApiParam({ name: 'storeId', type: String, description: 'ID da loja' })
@ApiParam({ name: 'filtro', type: String, description: 'Filtro de busca (código, EAN ou descrição)' })
@ApiResponse({
status: 200,
description: 'Produto encontrado com sucesso.',
type: ProductValidationDto
})
@ApiResponse({ status: 404, description: 'Produto não localizado.' })
async productValidation(
@Param('storeId') storeId: string,
@Param('filtro') filtro: string,
): Promise<ProductValidationDto> {
return this.productsService.productsValidation(storeId, filtro);
}
/// ENDPOIT PRODUTOS EXPOSTOS
@Post('exposed-product')
@ApiOperation({ summary: 'Registra produto em exposição' })
@ApiBody({ type: ExposedProductDto })
@ApiResponse({ status: 201, description: 'Produto exposto registrado com sucesso.' })
async exposedProduct(@Body() exposedProduct: ExposedProduct) {
return this.productsService.exposedProduct(exposedProduct);
}
/**
* Endpoint para buscar detalhes de produtos com preço e estoque
*/
@Post('product-details')
@ApiOperation({ summary: 'Busca detalhes de produtos com preço e estoque' })
@ApiBody({ type: ProductDetailQueryDto })
@ApiResponse({
status: 200,
description: 'Lista de produtos com detalhes retornada com sucesso.',
type: ProductDetailResponseDto,
isArray: true
})
@ApiResponse({ status: 400, description: 'Parâmetros inválidos.' })
async getProductDetails(@Body() query: ProductDetailQueryDto): Promise<ProductDetailResponseDto[]> {
return this.productsService.getProductDetails(query);
}
}

View File

@@ -1,105 +1,146 @@
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { InjectDataSource } from '@nestjs/typeorm';
import { ExposedProduct } from 'src/core/models/exposed-product.model';
import { DataSource } from 'typeorm';
import { ProductValidationDto } from './dto/ProductValidationDto';
import { ProductEcommerceDto } from './dto/product-ecommerce.dto';
import { ResultModel } from 'src/shared/ResultModel';
@Injectable()
export class ProductsService {
constructor(
@InjectDataSource("oracle") private readonly dataSource: DataSource
) {}
async productsValidation(storeId: string, filtro: string): Promise<ProductValidationDto> {
const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
try {
const sql = `SELECT PCPRODUT.DESCRICAO as "descricao"
,PCPRODUT.CODPROD as "codigoProduto"
,PCPRODUT.CODAUXILIAR as "codigoAuxiliar"
,PCMARCA.MARCA as "marca"
,REPLACE(NVL(URLIMAGEM, 'http://10.1.1.191/si.png'), 'http://167.249.211.178:8001/', 'http://10.1.1.191/') as "images"
,CASE WHEN PCPRODUT.TIPOPRODUTO = 'A' THEN 'AUTOSSERVICO'
WHEN PCPRODUT.TIPOPRODUTO = 'S' THEN 'SHOWROOM'
WHEN PCPRODUT.TIPOPRODUTO = 'E' THEN 'ELETROMOVEIS'
ELSE 'OUTROS' END as "tipoProduto"
,PCTABPR.PVENDA1 as "precoVenda"
,( PCEST.QTESTGER - PCEST.QTRESERV - PCEST.QTBLOQUEADA ) as "qtdeEstoqueLoja"
,( SELECT ( ESTOQUE_CD.QTESTGER - ESTOQUE_CD.QTRESERV - ESTOQUE_CD.QTBLOQUEADA )
FROM PCEST ESTOQUE_CD
WHERE ESTOQUE_CD.CODPROD = PCPRODUT.CODPROD
AND ESTOQUE_CD.CODFILIAL = 6 ) as "qtdeEstoqueCD"
FROM PCPRODUT, PCEST, PCTABPR, PCMARCA
WHERE PCPRODUT.CODPROD = PCEST.CODPROD
AND PCPRODUT.CODPROD = PCTABPR.CODPROD
AND PCPRODUT.CODMARCA = PCMARCA.CODMARCA
AND PCTABPR.NUMREGIAO = 1
AND PCEST.CODFILIAL = '${storeId}'
AND ( PCPRODUT.CODAUXILIAR = REGEXP_REPLACE('${filtro}', '[^0-9]', '') OR
PCPRODUT.CODPROD = REGEXP_REPLACE('${filtro}', '[^0-9]', '') OR
PCPRODUT.DESCRICAO LIKE '%'||'${filtro}'||'%' )`;
const products = await queryRunner.manager.query(sql);
if (products.length === 0) {
throw new HttpException('Produto não localizado!', HttpStatus.NOT_FOUND);
}
const product = products[0];
if (!product.images) {
product.images = [];
} else {
product.images = product.images.includes(';')
? product.images.split(';')
: [product.images];
}
return product;
} finally {
await queryRunner.release();
}
}
async exposedProduct(product: ExposedProduct) {
const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try {
const sqlInsert = `INSERT INTO ESTPRODUTOEXPOSICAO ( CODFILIAL, DATA, CODAUXILIAR, CODFUNC )
VALUES ( '${product.storeId}', TRUNC(SYSDATE), ${product.ean}, ${product.userId})`;
await queryRunner.query(sqlInsert);
await queryRunner.commitTransaction();
return { message: 'Registro incluído com sucesso!' };
} catch (err) {
await queryRunner.rollbackTransaction();
throw err;
} finally {
await queryRunner.release();
}
}
async getProductsEcommerce(): Promise<ProductEcommerceDto[]> {
const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
try {
const sql = `SELECT P.CODPROD as "productIdErp"
,P.VTEXSKUID as "productId"
,ROUND(P.PVENDA,2) as "price"
,ROUND(P.PRECOKIT,2) as "priceKit"
FROM ESVPRODUTOSECOMMERCE P
WHERE P.VTEXSKUID > 0
AND P.CODPROD IN (52057, 33702, 46410, 24518, 25816)`;
const products = await queryRunner.query(sql);
return products;
} finally {
await queryRunner.release();
}
}
}
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { InjectDataSource } from '@nestjs/typeorm';
import { ExposedProduct } from 'src/core/models/exposed-product.model';
import { DataSource } from 'typeorm';
import { ProductValidationDto } from './dto/ProductValidationDto';
import { ProductEcommerceDto } from './dto/product-ecommerce.dto';
import { ProductDetailQueryDto } from './dto/product-detail-query.dto';
import { ProductDetailResponseDto } from './dto/product-detail-response.dto';
@Injectable()
export class ProductsService {
constructor(
@InjectDataSource("oracle") private readonly dataSource: DataSource
) {}
/**
* Valida e busca informações de um produto por código ou descrição
* @param storeId - Código da filial
* @param filtro - Filtro de busca (código auxiliar, código produto ou descrição)
* @returns Dados do produto encontrado com estoque e preço
* @throws HttpException quando produto não é encontrado
*/
async productsValidation(storeId: string, filtro: string): Promise<ProductValidationDto> {
const sql = `SELECT PCPRODUT.DESCRICAO as "descricao"
,PCPRODUT.CODPROD as "codigoProduto"
,PCPRODUT.CODAUXILIAR as "codigoAuxiliar"
,PCMARCA.MARCA as "marca"
,REPLACE(NVL(URLIMAGEM, 'http://10.1.1.191/si.png'), 'http://167.249.211.178:8001/', 'http://10.1.1.191/') as "images"
,CASE WHEN PCPRODUT.TIPOPRODUTO = 'A' THEN 'AUTOSSERVICO'
WHEN PCPRODUT.TIPOPRODUTO = 'S' THEN 'SHOWROOM'
WHEN PCPRODUT.TIPOPRODUTO = 'E' THEN 'ELETROMOVEIS'
ELSE 'OUTROS' END as "tipoProduto"
,PCTABPR.PVENDA1 as "precoVenda"
,( PCEST.QTESTGER - PCEST.QTRESERV - PCEST.QTBLOQUEADA ) as "qtdeEstoqueLoja"
,( SELECT ( ESTOQUE_CD.QTESTGER - ESTOQUE_CD.QTRESERV - ESTOQUE_CD.QTBLOQUEADA )
FROM PCEST ESTOQUE_CD
WHERE ESTOQUE_CD.CODPROD = PCPRODUT.CODPROD
AND ESTOQUE_CD.CODFILIAL = 6 ) as "qtdeEstoqueCD"
FROM PCPRODUT, PCEST, PCTABPR, PCMARCA
WHERE PCPRODUT.CODPROD = PCEST.CODPROD
AND PCPRODUT.CODPROD = PCTABPR.CODPROD
AND PCPRODUT.CODMARCA = PCMARCA.CODMARCA
AND PCTABPR.NUMREGIAO = 1
AND PCEST.CODFILIAL = :storeId
AND ( PCPRODUT.CODAUXILIAR = REGEXP_REPLACE(:filtro1, '[^0-9]', '') OR
PCPRODUT.CODPROD = REGEXP_REPLACE(:filtro2, '[^0-9]', '') OR
PCPRODUT.DESCRICAO LIKE '%'||:filtro3||'%' )`;
const products = await this.dataSource.query(sql, [storeId, filtro, filtro, filtro]);
if (products.length === 0) {
throw new HttpException('Produto não localizado!', HttpStatus.NOT_FOUND);
}
const product = products[0];
if (!product.images) {
product.images = [];
} else {
product.images = product.images.includes(';')
? product.images.split(';')
: [product.images];
}
return product;
}
/**
* Registra produto exposto em showroom
* @param product - Dados do produto exposto (storeId, ean, userId)
* @returns Mensagem de sucesso
* @throws Lança exceção em caso de erro na transação
*/
async exposedProduct(product: ExposedProduct) {
const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try {
const sqlInsert = `INSERT INTO ESTPRODUTOEXPOSICAO ( CODFILIAL, DATA, CODAUXILIAR, CODFUNC )
VALUES ( :storeId, TRUNC(SYSDATE), :ean, :userId )`;
await queryRunner.query(sqlInsert, [product.storeId, product.ean, product.userId]);
await queryRunner.commitTransaction();
return { message: 'Registro incluído com sucesso!' };
} catch (err) {
await queryRunner.rollbackTransaction();
throw err;
} finally {
await queryRunner.release();
}
}
/**
* Busca produtos disponíveis para e-commerce
* @returns Lista de produtos com preços para e-commerce
*/
async getProductsEcommerce(): Promise<ProductEcommerceDto[]> {
const sql = `SELECT P.CODPROD as "productIdErp"
,P.VTEXSKUID as "productId"
,ROUND(P.PVENDA,2) as "price"
,ROUND(P.PRECOKIT,2) as "priceKit"
FROM ESVPRODUTOSECOMMERCE P
WHERE P.VTEXSKUID > 0
AND P.CODPROD IN (52057, 33702, 46410, 24518, 25816)`;
const products = await this.dataSource.query(sql);
return products;
}
/**
* Busca detalhes de produtos com preço integrado com região
* @param query - Parâmetros de busca (codprod, numregiao, codfilial)
* @returns Lista de produtos com detalhes
*/
async getProductDetails(query: ProductDetailQueryDto): Promise<ProductDetailResponseDto[]> {
const { numregiao, codprod, codfilial } = query;
const placeholders = codprod.map((_, index) => `:codprod${index}`).join(',');
const sql = `
SELECT
PCPRODUT.CODPROD AS "codprod",
PCPRODUT.DESCRICAO || ' - ' || PCMARCA.MARCA AS "descricao",
PCPRODUT.EMBALAGEM AS "embalagem",
PCPRODUT.CODAUXILIAR AS "codauxiliar",
PCMARCA.MARCA AS "marca",
(SELECT PCTABPR.PVENDA1
FROM PCTABPR
WHERE PCTABPR.CODPROD = PCPRODUT.CODPROD
AND PCTABPR.NUMREGIAO = :numregiao1) AS "preco",
(SELECT FANTASIA
FROM PCFILIAL F
WHERE CODIGO = :codfilial) AS "filial",
(SELECT REGIAO
FROM PCREGIAO
WHERE NUMREGIAO = :numregiao2) AS "regiao"
FROM PCPRODUT
LEFT JOIN PCMARCA ON PCPRODUT.CODMARCA = PCMARCA.CODMARCA
WHERE PCPRODUT.CODPROD IN (${placeholders})
`;
const params = [numregiao, codfilial, numregiao, ...codprod];
const products = await this.dataSource.query(sql, params);
return products;
}
}