implementação do logger

This commit is contained in:
unknown
2025-03-28 14:42:53 -03:00
parent 9c77880d89
commit 234704c9ba
23 changed files with 7736 additions and 4005 deletions

View File

@@ -1,44 +1,43 @@
/* eslint-disable prettier/prettier */
/* eslint-disable @typescript-eslint/no-unused-vars */
/*
https://docs.nestjs.com/controllers#controllers
*/
import { Controller, Get, Param } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiParam } from '@nestjs/swagger';
import { DataConsultService } from './data-consult.service';
@ApiTags('DataConsult')
@Controller('api/v1/data-consult')
export class DataConsultController {
export class DataConsultController {
constructor(private readonly dataConsultService: DataConsultService) {}
constructor(private readonly dataConsultService: DataConsultService) {}
@Get('stores')
async stores() {
return this.dataConsultService.stores();
}
@Get('stores')
@ApiOperation({ summary: 'Lista todas as lojas' })
async stores() {
return this.dataConsultService.stores();
}
@Get('sellers')
async sellers() {
return this.dataConsultService.sellers();
}
@Get('sellers')
@ApiOperation({ summary: 'Lista todos os vendedores' })
async sellers() {
return this.dataConsultService.sellers();
}
@Get('billings')
async billings() {
return this.dataConsultService.billings();
}
@Get('billings')
@ApiOperation({ summary: 'Retorna informações de faturamento' })
async billings() {
return this.dataConsultService.billings();
}
@Get('customers/:filter')
async customer(@Param('filter') filter: string) {
return this.dataConsultService.customers(filter);
}
@Get('products/:filter')
async products(@Param('filter') filter: string) {
return this.dataConsultService.products(filter);
}
@Get('customers/:filter')
@ApiOperation({ summary: 'Filtra clientes pelo parâmetro fornecido' })
@ApiParam({ name: 'filter', description: 'Filtro de busca para clientes' })
async customer(@Param('filter') filter: string) {
return this.dataConsultService.customers(filter);
}
@Get('products/:filter')
@ApiOperation({ summary: 'Filtra produtos pelo parâmetro fornecido' })
@ApiParam({ name: 'filter', description: 'Filtro de busca para produtos' })
async products(@Param('filter') filter: string) {
return this.dataConsultService.products(filter);
}
}

View File

@@ -1,19 +1,16 @@
/* eslint-disable prettier/prettier */
/* eslint-disable @typescript-eslint/no-unused-vars */
import { Module } from '@nestjs/common';
import { DataConsultService } from './data-consult.service';
import { DataConsultController } from './data-consult.controller';
/*
https://docs.nestjs.com/modules
*/
import { Module } from '@nestjs/common';
import { DataConsultRepository } from './data-consult.repository';
import { CacheModule } from '../core/configs/cache/redis.module';
import { LoggerModule } from 'src/Log/logger.module';
@Module({
imports: [],
controllers: [
DataConsultController,],
providers: [
DataConsultService,],
imports: [CacheModule,LoggerModule],
controllers: [DataConsultController],
providers: [
DataConsultService,
DataConsultRepository,
],
})
export class DataConsultModule { }
export class DataConsultModule {}

View File

@@ -0,0 +1,158 @@
import { Injectable } from '@nestjs/common';
import { DataSource } from 'typeorm';
import { typeOrmConfig } from '../core/configs/typeorm.config';
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 { ProductDto } from './dto/product.dto';
@Injectable()
export class DataConsultRepository {
private readonly dataSource: DataSource;
constructor() {
this.dataSource = new DataSource(typeOrmConfig);
this.dataSource.initialize();
}
private async executeQuery<T>(sql: string, params: any[] = []): Promise<T> {
const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
try {
const result = await queryRunner.query(sql, params);
return result as T;
} finally {
await queryRunner.release();
}
}
async findStores(): Promise<StoreDto[]> {
const sql = `
SELECT PCFILIAL.CODIGO as "id",
PCFILIAL.RAZAOSOCIAL as "name",
PCFILIAL.CODIGO || ' - ' || PCFILIAL.FANTASIA as "store"
FROM PCFILIAL
WHERE PCFILIAL.CODIGO NOT IN ('99', '69')
ORDER BY TO_NUMBER(PCFILIAL.CODIGO)
`;
return this.executeQuery<StoreDto[]>(sql);
}
async findSellers(): Promise<SellerDto[]> {
const sql = `
SELECT PCUSUARI.CODUSUR as "id",
PCUSUARI.NOME as "name"
FROM PCUSUARI
WHERE PCUSUARI.DTTERMINO IS NULL
AND PCUSUARI.TIPOVEND NOT IN ('P')
ORDER BY PCUSUARI.NOME
`;
return this.executeQuery<SellerDto[]>(sql);
}
async findBillings(): Promise<BillingDto[]> {
const sql = `
SELECT PCCOB.CODCOB as "id",
PCCOB.CODCOB || ' - ' || PCCOB.COBRANCA as "description"
FROM PCCOB
WHERE PCCOB.CODCOB NOT IN ('DEVP', 'DEVT', 'DESD')
ORDER BY PCCOB.COBRANCA
`;
return this.executeQuery<BillingDto[]>(sql);
}
async findCustomers(filter: string): Promise<CustomerDto[]> {
const queries = [
{
sql: `
SELECT PCCLIENT.CODCLI as "id",
PCCLIENT.CODCLI || ' - ' || PCCLIENT.CLIENTE ||
' ( ' || REGEXP_REPLACE(PCCLIENT.CGCENT, '[^0-9]', '') || ' )' as "name"
FROM PCCLIENT
WHERE PCCLIENT.CODCLI = REGEXP_REPLACE(?, '[^0-9]', '')
ORDER BY PCCLIENT.CLIENTE
`,
params: [filter],
},
{
sql: `
SELECT PCCLIENT.CODCLI as "id",
PCCLIENT.CODCLI || ' - ' || PCCLIENT.CLIENTE ||
' ( ' || REGEXP_REPLACE(PCCLIENT.CGCENT, '[^0-9]', '') || ' )' as "name"
FROM PCCLIENT
WHERE REGEXP_REPLACE(PCCLIENT.CGCENT, '[^0-9]', '') = REGEXP_REPLACE(?, '[^0-9]', '')
ORDER BY PCCLIENT.CLIENTE
`,
params: [filter],
},
{
sql: `
SELECT PCCLIENT.CODCLI as "id",
PCCLIENT.CODCLI || ' - ' || PCCLIENT.CLIENTE ||
' ( ' || REGEXP_REPLACE(PCCLIENT.CGCENT, '[^0-9]', '') || ' )' as "name"
FROM PCCLIENT
WHERE PCCLIENT.CLIENTE LIKE ?
ORDER BY PCCLIENT.CLIENTE
`,
params: [filter.toUpperCase().replace('@', '%') + '%'],
},
];
for (const { sql, params } of queries) {
const result = await this.executeQuery<CustomerDto[]>(sql, params);
if (result.length > 0) {
return result;
}
}
return [];
}
async findProducts(filter: string): Promise<ProductDto[]> {
const queries = [
{
sql: `
SELECT PCPRODUT.CODPROD as "id",
PCPRODUT.CODPROD || ' - ' || PCPRODUT.DESCRICAO ||
' ( ' || REGEXP_REPLACE(PCPRODUT.CODAUXILIAR, '[^0-9]', '') || ' )' as "description"
FROM PCPRODUT
WHERE PCPRODUT.CODPROD = REGEXP_REPLACE(?, '[^0-9]', '')
ORDER BY PCPRODUT.DESCRICAO
`,
params: [filter],
},
{
sql: `
SELECT PCPRODUT.CODPROD as "id",
PCPRODUT.CODPROD || ' - ' || PCPRODUT.DESCRICAO ||
' ( ' || REGEXP_REPLACE(PCPRODUT.CODAUXILIAR, '[^0-9]', '') || ' )' as "description"
FROM PCPRODUT
WHERE PCPRODUT.CODAUXILIAR = REGEXP_REPLACE(?, '[^0-9]', '')
ORDER BY PCPRODUT.DESCRICAO
`,
params: [filter],
},
{
sql: `
SELECT PCPRODUT.CODPROD as "id",
PCPRODUT.CODPROD || ' - ' || PCPRODUT.DESCRICAO ||
' ( ' || REGEXP_REPLACE(PCPRODUT.CODAUXILIAR, '[^0-9]', '') || ' )' as "description"
FROM PCPRODUT
WHERE PCPRODUT.DESCRICAO LIKE ?
ORDER BY PCPRODUT.DESCRICAO
`,
params: [filter + '%'],
},
];
for (const { sql, params } of queries) {
const result = await this.executeQuery<ProductDto[]>(sql, params);
if (result.length > 0) {
return result;
}
}
return [];
}
}

View File

@@ -1,170 +1,127 @@
/* eslint-disable prettier/prettier */
/* eslint-disable @typescript-eslint/no-unused-vars */
/*
import { Inject, Injectable } from '@nestjs/common';
import { IRedisClient } from '../core/configs/cache/IRedisClient';
import { RedisClientToken } from '../core/configs/cache/redis-client.adapter.provider';
import { DataConsultRepository } from './data-consult.repository';
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 { ProductDto } from './dto/product.dto';
import { ILogger } from '../Log/ILogger';
https://docs.nestjs.com/providers#services
*/
import { Injectable } from '@nestjs/common';
import { typeOrmConfig } from '../core/configs/typeorm.config';
import { DataSource } from 'typeorm';
const DEFAULT_CACHE_TTL = 100;
@Injectable()
export class DataConsultService {
constructor(
private readonly repository: DataConsultRepository,
@Inject(RedisClientToken)
private readonly redisClient: IRedisClient,
@Inject('LoggerService')
private readonly logger: ILogger
) {}
/**
* Método genérico para lidar com lógica de cache
* @param cacheKey - A chave a ser usada para cache
* @param fetchFn - Função para buscar dados se não estiverem no cache
* @param ttl - Tempo de vida em segundos para o cache
* @returns Os dados em cache ou recentemente buscados
*/
private async getCachedData<T>(
cacheKey: string,
fetchFn: () => Promise<T>,
ttl: number = DEFAULT_CACHE_TTL
): Promise<T> {
try {
this.logger.log(`Tentando obter dados em cache para a chave: ${cacheKey}`);
const cached = await this.redisClient.get<T>(cacheKey);
async stores() {
const dataSource = new DataSource(typeOrmConfig);
await dataSource.initialize();
const queryRunner = dataSource.createQueryRunner();
await queryRunner.connect();
try {
const sql = `SELECT PCFILIAL.CODIGO as "id"
,PCFILIAL.RAZAOSOCIAL as "name"
,PCFILIAL.CODIGO || ' - ' || PCFILIAL.FANTASIA as "store"
FROM PCFILIAL
WHERE PCFILIAL.CODIGO NOT IN ('99', '69')
ORDER BY TO_NUMBER(PCFILIAL.CODIGO)`;
if (cached) {
this.logger.log(`Cache encontrado para a chave: ${cacheKey}`);
return cached;
}
this.logger.log(`Cache não encontrado para a chave: ${cacheKey}, buscando na origem`);
const result = await fetchFn();
const stores = await queryRunner.manager.query(sql);
try {
await this.redisClient.set<T>(cacheKey, result, ttl);
this.logger.log(`Dados armazenados em cache com sucesso para a chave: ${cacheKey}`);
} catch (cacheError) {
this.logger.warn(`Falha ao armazenar dados em cache para a chave: ${cacheKey}`);
this.logger.error('Detalhes do erro de cache:', cacheError instanceof Error ? cacheError.stack : '');
}
return stores;
} finally {
await queryRunner.release();
await dataSource.destroy();
}
return result;
} catch (error) {
this.logger.error(
`Erro no método getCachedData para a chave ${cacheKey}:`,
error instanceof Error ? error.stack : ''
);
return fetchFn();
}
}
/**
* Obter todas as lojas com cache
* @returns Array de StoreDto
*/
async stores(): Promise<StoreDto[]> {
this.logger.log('Buscando todas as lojas');
return this.getCachedData<StoreDto[]>(
'data-consult:stores',
() => this.repository.findStores()
);
}
async sellers() {
const dataSource = new DataSource(typeOrmConfig);
await dataSource.initialize();
const queryRunner = dataSource.createQueryRunner();
await queryRunner.connect();
try {
const sql = `SELECT PCUSUARI.CODUSUR as "id"
,PCUSUARI.NOME as "name"
FROM PCUSUARI
WHERE PCUSUARI.DTTERMINO IS NULL
AND PCUSUARI.TIPOVEND NOT IN ('P')
ORDER BY PCUSUARI.NOME`;
/**
* Obter todos os vendedores com cache
* @returns Array de SellerDto
*/
async sellers(): Promise<SellerDto[]> {
this.logger.log('Buscando todos os vendedores');
return this.getCachedData<SellerDto[]>(
'data-consult:sellers',
() => this.repository.findSellers()
);
}
/**
* Obter todos os faturamentos com cache
* @returns Array de BillingDto
*/
async billings(): Promise<BillingDto[]> {
this.logger.log('Buscando todos os faturamentos');
return this.getCachedData<BillingDto[]>(
'data-consult:billings',
() => this.repository.findBillings()
);
}
const sellers = await queryRunner.manager.query(sql);
return sellers;
} finally {
await queryRunner.release();
await dataSource.destroy();
}
}
async billings() {
const dataSource = new DataSource(typeOrmConfig);
await dataSource.initialize();
const queryRunner = dataSource.createQueryRunner();
await queryRunner.connect();
try {
const sql = `SELECT PCCOB.CODCOB as "id"
,PCCOB.CODCOB||' - '||PCCOB.COBRANCA as "description"
FROM PCCOB
WHERE PCCOB.CODCOB NOT IN ('DEVP', 'DEVT', 'DESD')
ORDER BY PCCOB.COBRANCA`;
const sellers = await queryRunner.manager.query(sql);
return sellers;
} finally {
await queryRunner.release();
await dataSource.destroy();
}
}
async customers(filter: string) {
const dataSource = new DataSource(typeOrmConfig);
await dataSource.initialize();
const queryRunner = dataSource.createQueryRunner();
await queryRunner.connect();
try {
let sql = `SELECT PCCLIENT.CODCLI as "id"
,PCCLIENT.CODCLI || ' - '|| PCCLIENT.CLIENTE||
' ( '||REGEXP_REPLACE(PCCLIENT.CGCENT, '[^0-9]', '')||' )' as "name"
FROM PCCLIENT
WHERE PCCLIENT.CODCLI = REGEXP_REPLACE('${filter}', '[^0-9]', '')
ORDER BY PCCLIENT.CLIENTE`;
let customers = await queryRunner.manager.query(sql);
if (customers.length == 0) {
sql = `SELECT PCCLIENT.CODCLI as "id"
,PCCLIENT.CODCLI || ' - '|| PCCLIENT.CLIENTE||
' ( '||REGEXP_REPLACE(PCCLIENT.CGCENT, '[^0-9]', '')||' )' as "name"
FROM PCCLIENT
WHERE REGEXP_REPLACE(PCCLIENT.CGCENT, '[^0-9]', '') = REGEXP_REPLACE('${filter}', '[^0-9]', '')
ORDER BY PCCLIENT.CLIENTE`;
customers = await queryRunner.manager.query(sql);
}
if (customers.length == 0) {
sql = `SELECT PCCLIENT.CODCLI as "id"
,PCCLIENT.CODCLI || ' - '|| PCCLIENT.CLIENTE||
' ( '||REGEXP_REPLACE(PCCLIENT.CGCENT, '[^0-9]', '')||' )' as "name"
FROM PCCLIENT
WHERE PCCLIENT.CLIENTE LIKE '${filter.toUpperCase().replace('@', '%')}%'
ORDER BY PCCLIENT.CLIENTE`;
customers = await queryRunner.manager.query(sql);
}
return customers;
} finally {
await queryRunner.release();
await dataSource.destroy();
}
}
async products(filter: string) {
const dataSource = new DataSource(typeOrmConfig);
await dataSource.initialize();
const queryRunner = dataSource.createQueryRunner();
await queryRunner.connect();
try {
let sql = `SELECT PCPRODUT.CODPROD as "id"
,PCPRODUT.CODPROD || ' - '|| PCPRODUT.DESCRICAO||
' ( '||REGEXP_REPLACE(PCPRODUT.CODAUXILIAR, '[^0-9]', '')||' )' as "description"
FROM PCPRODUT
WHERE PCPRODUT.CODPROD = REGEXP_REPLACE('${filter}', '[^0-9]', '')
ORDER BY PCPRODUT.DESCRICAO`;
let products = await queryRunner.manager.query(sql);
if (products.length == 0) {
sql = `SELECT PCPRODUT.CODPROD as "id"
,PCPRODUT.CODPROD || ' - '|| PCPRODUT.DESCRICAO||
' ( '||REGEXP_REPLACE(PCPRODUT.CODAUXILIAR, '[^0-9]', '')||' )' as "description"
FROM PCPRODUT
WHERE PCPRODUT.CODAUXILIAR = REGEXP_REPLACE('${filter}', '[^0-9]', '')
ORDER BY PCPRODUT.DESCRICAO`;
products = await queryRunner.manager.query(sql);
}
if (products.length == 0) {
sql = `SELECT PCPRODUT.CODPROD as "id"
,PCPRODUT.CODPROD || ' - '|| PCPRODUT.DESCRICAO||
' ( '||REGEXP_REPLACE(PCPRODUT.CODAUXILIAR, '[^0-9]', '')||' )' as "description"
FROM PCPRODUT
WHERE PCPRODUT.DESCRICAO LIKE '${filter}%'
ORDER BY PCPRODUT.DESCRICAO`;
products = await queryRunner.manager.query(sql);
}
return products;
} finally {
await queryRunner.release();
await dataSource.destroy();
}
}
/**
* Obter clientes filtrados por termo de pesquisa com cache
* @param filter - Termo de pesquisa para filtrar clientes
* @returns Array de CustomerDto
*/
async customers(filter: string): Promise<CustomerDto[]> {
this.logger.log(`Buscando clientes com filtro: ${filter}`);
return this.getCachedData<CustomerDto[]>(
`data-consult:customers:${filter}`,
() => this.repository.findCustomers(filter)
);
}
/**
* Obter produtos filtrados por termo de pesquisa com cache
* @param filter - Termo de pesquisa para filtrar produtos
* @returns Array de ProductDto
*/
async products(filter: string): Promise<ProductDto[]> {
this.logger.log(`Buscando produtos com filtro: ${filter}`);
return this.getCachedData<ProductDto[]>(
`data-consult:products:${filter}`,
() => this.repository.findProducts(filter)
);
}
}

View File

@@ -0,0 +1,90 @@
import { Test, TestingModule } from '@nestjs/testing';
import { DataConsultService } from './data-consult.service';
import { DataConsultRepository } from './data-consult.repository';
describe('DataConsultService', () => {
let service: DataConsultService;
let repository: DataConsultRepository;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
DataConsultService,
{
provide: DataConsultRepository,
useValue: {
findStores: jest.fn(),
findSellers: jest.fn(),
findBillings: jest.fn(),
findCustomers: jest.fn(),
findProducts: jest.fn(),
},
},
],
}).compile();
service = module.get<DataConsultService>(DataConsultService);
repository = module.get<DataConsultRepository>(DataConsultRepository);
});
it('deve estar definido', () => {
expect(service).toBeDefined();
});
describe('stores', () => {
it('deve retornar uma lista de lojas', async () => {
const mockStores = [{ id: '1', name: 'Loja 1', store: '1 - Loja 1' }];
jest.spyOn(repository, 'findStores').mockResolvedValue(mockStores);
const result = await service.stores();
expect(result).toEqual(mockStores);
expect(repository.findStores).toHaveBeenCalled();
});
});
describe('sellers', () => {
it('deve retornar uma lista de vendedores', async () => {
const mockSellers = [{ id: '1', name: 'Vendedor 1' }];
jest.spyOn(repository, 'findSellers').mockResolvedValue(mockSellers);
const result = await service.sellers();
expect(result).toEqual(mockSellers);
expect(repository.findSellers).toHaveBeenCalled();
});
});
describe('billings', () => {
it('deve retornar informações de faturamento', async () => {
const mockBillings = [{ id: '1', description: 'Faturamento 1' }];
jest.spyOn(repository, 'findBillings').mockResolvedValue(mockBillings);
const result = await service.billings();
expect(result).toEqual(mockBillings);
expect(repository.findBillings).toHaveBeenCalled();
});
});
describe('customers', () => {
it('deve retornar clientes de acordo com o filtro fornecido', async () => {
const filter = '123';
const mockCustomers = [{ id: '123', name: 'Cliente 123' }];
jest.spyOn(repository, 'findCustomers').mockResolvedValue(mockCustomers);
const result = await service.customers(filter);
expect(result).toEqual(mockCustomers);
expect(repository.findCustomers).toHaveBeenCalledWith(filter);
});
});
describe('products', () => {
it('deve retornar produtos de acordo com o filtro fornecido', async () => {
const filter = '456';
const mockProducts = [{ id: '456', description: 'Produto 456' }];
jest.spyOn(repository, 'findProducts').mockResolvedValue(mockProducts);
const result = await service.products(filter);
expect(result).toEqual(mockProducts);
expect(repository.findProducts).toHaveBeenCalledWith(filter);
});
});
});

View File

@@ -0,0 +1,9 @@
import { ApiProperty } from '@nestjs/swagger';
export class BillingDto {
@ApiProperty({ description: 'Identificador do faturamento' })
id: string;
@ApiProperty({ description: 'Descrição do faturamento' })
description: string;
}

View File

@@ -0,0 +1,9 @@
import { ApiProperty } from '@nestjs/swagger';
export class CustomerDto {
@ApiProperty({ description: 'Identificador do cliente' })
id: string;
@ApiProperty({ description: 'Nome e identificação do cliente' })
name: string;
}

View File

@@ -0,0 +1,9 @@
import { ApiProperty } from '@nestjs/swagger';
export class ProductDto {
@ApiProperty({ description: 'Identificador do produto' })
id: string;
@ApiProperty({ description: 'Descrição do produto' })
description: string;
}

View File

@@ -0,0 +1,9 @@
import { ApiProperty } from '@nestjs/swagger';
export class SellerDto {
@ApiProperty({ description: 'Identificador do vendedor' })
id: string;
@ApiProperty({ description: 'Nome do vendedor' })
name: string;
}

View File

@@ -0,0 +1,12 @@
import { ApiProperty } from '@nestjs/swagger';
export class StoreDto {
@ApiProperty({ description: 'Identificador da loja' })
id: string;
@ApiProperty({ description: 'Nome da loja' })
name: string;
@ApiProperty({ description: 'Representação da loja (código e fantasia)' })
store: string;
}