From aba5dfaffc216868dc703384666bf0b4a158d410 Mon Sep 17 00:00:00 2001 From: Raphael Rivas Date: Tue, 20 Aug 2024 13:30:09 -0300 Subject: [PATCH 01/11] remove unused --- src/sftp/sftp.service.ts | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/sftp/sftp.service.ts b/src/sftp/sftp.service.ts index 4f8fbf6d..e4ed2985 100644 --- a/src/sftp/sftp.service.ts +++ b/src/sftp/sftp.service.ts @@ -153,20 +153,10 @@ export class SftpService implements OnModuleInit, OnModuleLoad { cnabString: string | null; }> { await this.connectClient(); - - // const listFiles = await this.sftpClient.list( - // this.dir(this.FOLDERS.RETORNO)); - - // for(const file of listFiles) { - // await this.moveToBackup(file.name,SftpBackupFolder.Ajuste); - // } - const firstFile = (await this.sftpClient.list(this.dir(folder), this.REGEX.RETORNO)).pop(); - if (!firstFile) { return { cnabName: null, cnabString: null }; } - const cnabPath = this.dir(`${folder}/${firstFile.name}`); const cnabString = await this.downloadToString(cnabPath); return { cnabName: firstFile.name, cnabString }; From 22886f199b3bc090a37ec8da944755da3a0c2d00 Mon Sep 17 00:00:00 2001 From: williamfl2007 Date: Tue, 20 Aug 2024 14:15:26 -0300 Subject: [PATCH 02/11] =?UTF-8?q?Corre=C3=A7=C3=A3o=20filtro=20valor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/relatorio/relatorio.repository.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/relatorio/relatorio.repository.ts b/src/relatorio/relatorio.repository.ts index b02e33e5..fab81065 100644 --- a/src/relatorio/relatorio.repository.ts +++ b/src/relatorio/relatorio.repository.ts @@ -44,7 +44,7 @@ export class RelatorioRepository { if(valorMin!==undefined) query = query +` and da."valorLancamento">=${valorMin}`; - if(valorMin!==undefined) + if(valorMax!==undefined) query = query + ` and da."valorLancamento"<=${valorMax}`; query = query + `) as cs @@ -87,7 +87,7 @@ export class RelatorioRepository { if(valorMin!==undefined) query = query +` and da."valorLancamento">=${valorMin}`; - if(valorMin!==undefined) + if(valorMax!==undefined) query = query +` and da."valorLancamento"<=${valorMax}`; query = query +`) as cs @@ -124,7 +124,7 @@ export class RelatorioRepository { if(valorMin!==undefined) query = query +` and da."valorLancamento">=${valorMin}`; - if(valorMin!==undefined) + if(valorMax!==undefined) query = query + ` and da."valorLancamento"<=${valorMax}`; if(favorecidoNome!==undefined) From de4f854eb498d098d40cbb1caa124e9e7cc36eb2 Mon Sep 17 00:00:00 2001 From: williamfl2007 Date: Tue, 20 Aug 2024 16:19:57 -0300 Subject: [PATCH 03/11] Ajustes consorcio --- .../dtos/relatorio-analitico-result.dto.ts | 16 +++++++++ src/relatorio/dtos/relatorio-analitico.dto.ts | 17 +++++++++ src/relatorio/relatorio.controller.ts | 35 +++++++++++++++++++ src/relatorio/relatorio.repository.ts | 18 +++++----- src/relatorio/relatorio.service.ts | 25 +++++++++++++ 5 files changed, 101 insertions(+), 10 deletions(-) create mode 100644 src/relatorio/dtos/relatorio-analitico-result.dto.ts create mode 100644 src/relatorio/dtos/relatorio-analitico.dto.ts diff --git a/src/relatorio/dtos/relatorio-analitico-result.dto.ts b/src/relatorio/dtos/relatorio-analitico-result.dto.ts new file mode 100644 index 00000000..b23e22fc --- /dev/null +++ b/src/relatorio/dtos/relatorio-analitico-result.dto.ts @@ -0,0 +1,16 @@ +import { DeepPartial } from 'typeorm'; +import { RelatorioAnaliticoDto } from './relatorio-analitico.dto'; + +export class RelatorioAnaliticoResultDto { + [x: string]: any; + constructor(analitico?: DeepPartial) { + if (analitico !== undefined) { + Object.assign(this, analitico); + } + } + + count: number; + valor: number; + data: RelatorioAnaliticoDto[]; + status: string; +} diff --git a/src/relatorio/dtos/relatorio-analitico.dto.ts b/src/relatorio/dtos/relatorio-analitico.dto.ts new file mode 100644 index 00000000..3b3fe1a0 --- /dev/null +++ b/src/relatorio/dtos/relatorio-analitico.dto.ts @@ -0,0 +1,17 @@ +import { DeepPartial } from 'typeorm'; + +export class RelatorioAnaliticoDto { + constructor(analitico?: DeepPartial) { + if (analitico !== undefined) { + Object.assign(this, analitico); + } + } + + dataEfetivacao: Date; + dataVencimento: Date; + favorecido: string; + consorcio: string; + valorTransacao: number = 0; + status: string; + ocorrencia: string; +} \ No newline at end of file diff --git a/src/relatorio/relatorio.controller.ts b/src/relatorio/relatorio.controller.ts index 1cc131e7..63a1615a 100644 --- a/src/relatorio/relatorio.controller.ts +++ b/src/relatorio/relatorio.controller.ts @@ -47,4 +47,39 @@ export class RelatorioController { dataInicio,dataFim, favorecidoNome, consorcioNome, valorMin, valorMax, pago, aPagar }); } + + + @ApiQuery({ name: 'dataInicio', description: 'Data da Ordem de Pagamento Inicial', required: true, type: String, example: '2024-07-15' }) + @ApiQuery({ name: 'dataFim', description: 'Data da Ordem de Pagamento Final', required: true, type: String, example: '2024-07-16' }) + @ApiQuery({ name: 'favorecidoNome', description: 'Pesquisa o nome parcial dos favorecidos, sem distinção de acento ou maiúsculas.', required: false, type: String, example: 'internorte,intersul,jose carlos' }) + @ApiQuery({ name: 'consorcioNome', description: ApiDescription({ _: 'Pesquisa o nome parcial dos consórcios, sem distinção de acento ou maiúsculas.', 'STPC/STPL': 'Agrupa todos os vanzeiros sob o consórcio' }), required: false, type: String, example: 'Santa Cruz,STPL,Internorte,STPC,MobiRio,Transcarioca,Intersul,VLT' }) + @ApiQuery({ name: 'valorMin', description: 'Somatório do valor bruto.', required: false, type: Number, example: 12.0 }) + @ApiQuery({ name: 'valorMax', description: 'Somatório do valor bruto.', required: false, type: Number, example: 12.99 }) + @ApiQuery({ name: 'pago', required: false, type: Boolean, description: ApiDescription({ _: 'Se o pagamento foi pago com sucesso.', default: false }) }) + @ApiQuery({ name: 'aPagar', required: false, type: Boolean, description: ApiDescription({ _: 'Se o status for a pagar', default: false }) }) + @HttpCode(HttpStatus.OK) + @ApiBearerAuth() + @UseGuards(AuthGuard('jwt')) + @Get('analitico') + async getAnalitico( + @Query('dataInicio', new ParseDatePipe({ dateOnly: true })) + dataInicio: Date, + @Query('dataFim', new ParseDatePipe({ dateOnly: true })) + dataFim: Date, + @Query('favorecidoNome', new ParseArrayPipe({ items: String, separator: ',', optional: true })) + favorecidoNome: string[], + @Query('consorcioNome', new ParseArrayPipe({ items: String, separator: ',', optional: true })) + consorcioNome: string[], + @Query('valorMin', new ParseNumberPipe({ optional: true })) + valorMin: number | undefined, + @Query('valorMax', new ParseNumberPipe({ optional: true })) + valorMax: number | undefined, + @Query('pago',new ParseBooleanPipe({ optional: true })) pago: boolean | undefined, + @Query('aPagar',new ParseBooleanPipe({ optional: true })) aPagar: boolean | undefined + ) { + return await this.relatorioService.findAnalitico({ + dataInicio,dataFim, favorecidoNome, consorcioNome, valorMin, valorMax, pago, aPagar + }); + } + } \ No newline at end of file diff --git a/src/relatorio/relatorio.repository.ts b/src/relatorio/relatorio.repository.ts index fab81065..5a80031e 100644 --- a/src/relatorio/relatorio.repository.ts +++ b/src/relatorio/relatorio.repository.ts @@ -8,7 +8,7 @@ import { IFindPublicacaoRelatorio } from './interfaces/find-publicacao-relatorio export class RelatorioRepository { constructor(@InjectDataSource() private readonly dataSource: DataSource) {} - + private getQueryConsorcio(dataInicio:string,dataFim:string,pago?:boolean, valorMin?:number,valorMax?:number,nomeConsorcio?:string[],aPagar?:boolean){ let query = @@ -24,16 +24,14 @@ export class RelatorioRepository { inner join item_transacao it on ita.id = it."itemTransacaoAgrupadoId" inner join arquivo_publicacao ap on ap."itemTransacaoId"=it.id inner join cliente_favorecido cf on cf.id=it."clienteFavorecidoId" - WHERE `; - - if((nomeConsorcio!==undefined) && !(['Todos'].some(i=>nomeConsorcio?.includes(i)))) - query = query +` ita."nomeConsorcio" in('${nomeConsorcio?.join("','")}')`; - else - query = query +` ita."nomeConsorcio" not in('STPC','STPL') `; + WHERE (1=1) `; if(dataInicio!==undefined && dataFim!==undefined && (dataFim === dataInicio || new Date(dataFim)>new Date(dataInicio))) query = query + ` and da."dataVencimento" between '${dataInicio}' and '${dataFim}'`; + + if((nomeConsorcio!==undefined) && !(['Todos'].some(i=>nomeConsorcio?.includes(i)))) + query = query +` and ita."nomeConsorcio" in('${nomeConsorcio?.join("','")}')`; if(pago!==undefined) query = query + ` and ap."isPago"=${pago}`; @@ -48,7 +46,7 @@ export class RelatorioRepository { query = query + ` and da."valorLancamento"<=${valorMax}`; query = query + `) as cs - group by cs."consorcio"` + group by cs."consorcio"` return query; } @@ -144,13 +142,13 @@ export class RelatorioRepository { }else if(args.consorcioNome!==undefined && (['STPC','STPL'].some(i=>args.consorcioNome?.includes(i)))){ queryConsorcio = this.getQueryStpcStpl(args.dataInicio.toISOString().slice(0,10), args.dataFim.toISOString().slice(0,10),args.pago,args.valorMin,args.valorMax,args.consorcioNome,args.aPagar); - }else if(args.favorecidoNome===undefined && args.consorcioNome === undefined) { + }else if((args.favorecidoNome===undefined && args.consorcioNome === undefined)){ queryConsorcio = this.getQueryConsorcio(args.dataInicio.toISOString().slice(0,10), args.dataFim.toISOString().slice(0,10),args.pago,args.valorMin, args.valorMax,args.consorcioNome,args.aPagar) + ' union all '+ this.getQueryStpcStpl(args.dataInicio?.toISOString().slice(0,10), - args.dataFim.toISOString().slice(0,10),args.pago,args.valorMin,args.valorMax,args.consorcioNome,args.aPagar); + args.dataFim.toISOString().slice(0,10),args.pago,args.valorMin,args.valorMax,args.consorcioNome,args.aPagar); } let queryOperadores =''; diff --git a/src/relatorio/relatorio.service.ts b/src/relatorio/relatorio.service.ts index eb1e3f31..8b58818d 100644 --- a/src/relatorio/relatorio.service.ts +++ b/src/relatorio/relatorio.service.ts @@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common'; import { IFindPublicacaoRelatorio } from './interfaces/find-publicacao-relatorio.interface'; import { RelatorioRepository } from './relatorio.repository'; import { RelatorioConsolidadoResultDto } from './dtos/relatorio-consolidado-result.dto'; +import { RelatorioAnaliticoResultDto } from './dtos/relatorio-analitico-result.dto'; @Injectable() export class RelatorioService { @@ -58,4 +59,28 @@ export class RelatorioService { } return result; } + + async findAnalitico(args: IFindPublicacaoRelatorio){ + const d = 2; + let result: RelatorioAnaliticoResultDto[]=[]; + + let analitcoPagos; + if(args.pago === true && (args.aPagar === false || args.aPagar === undefined)){ + + } + + let analitcoNaoPagos; + if(args.pago === false && (args.aPagar === false || args.aPagar === undefined)){ + } + + let analitcoAPagar; + if(args.aPagar === true && (args.pago === false || args.pago === undefined)){ + + } + let analitico; + if(args.aPagar === undefined && args.pago === undefined){ + + } + return result; + } } From cc653170892c401854be22ff870633bf8bc8c3a2 Mon Sep 17 00:00:00 2001 From: williamfl2007 Date: Tue, 20 Aug 2024 16:58:48 -0300 Subject: [PATCH 04/11] Ajuste query stpc/stpl --- src/relatorio/relatorio.repository.ts | 61 ++------------------------- 1 file changed, 4 insertions(+), 57 deletions(-) diff --git a/src/relatorio/relatorio.repository.ts b/src/relatorio/relatorio.repository.ts index 5a80031e..3e2c609f 100644 --- a/src/relatorio/relatorio.repository.ts +++ b/src/relatorio/relatorio.repository.ts @@ -49,50 +49,7 @@ export class RelatorioRepository { group by cs."consorcio"` return query; } - - private getQueryStpcStpl(dataInicio:string,dataFim:string,pago?:boolean, - valorMin?:number,valorMax?:number,nomeConsorcio?:string[],aPagar?:boolean){ - let query = ` select cs."consorcio" nomeFavorecido,sum(cs."valor_agrupado")::float valor - from ( - select distinct ita.id AS id, - ita."nomeConsorcio" AS consorcio, - cf.nome AS favorecido, - cf."cpfCnpj" AS favorecido_cpfcnpj, - da."valorLancamento" AS valor_agrupado - from transacao_view tv - inner join item_transacao_agrupado ita on tv."itemTransacaoAgrupadoId"=ita.id - inner join detalhe_a da on da."itemTransacaoAgrupadoId"= ita.id - inner join item_transacao it on ita.id = it."itemTransacaoAgrupadoId" - inner join arquivo_publicacao ap on ap."itemTransacaoId"=it.id - inner join cliente_favorecido cf on cf.id=it."clienteFavorecidoId" - WHERE `; - - if((nomeConsorcio!==undefined) && !(['Todos'].some(i=>nomeConsorcio?.includes(i)))) - query = query +` ita."nomeConsorcio" in('${nomeConsorcio?.join("','")}')`; - else - query = query +` ita."nomeConsorcio" in('STPC','STPL')`; - - if(dataInicio!==undefined && dataFim!==undefined && - (dataFim === dataInicio || new Date(dataFim)>new Date(dataInicio))) - query = query + ` and da."dataVencimento" between '${dataInicio}' and '${dataFim}'`; - - if(pago!==undefined) - query = query + ` and ap."isPago"=${pago}`; - - if(aPagar === true) - query = query + ` and ap."isPago" is null `; - - if(valorMin!==undefined) - query = query +` and da."valorLancamento">=${valorMin}`; - - if(valorMax!==undefined) - query = query +` and da."valorLancamento"<=${valorMax}`; - - query = query +`) as cs - group by cs."consorcio"`; - return query; - } - + private getOperadores(dataInicio:string,dataFim:string,pago?:boolean,valorMin?:number, valorMax?:number,favorecidoNome?:string[],aPagar?:boolean){ let query = `select cs."favorecido" nomeFavorecido,sum(cs."valor_agrupado")::float valor @@ -135,22 +92,12 @@ export class RelatorioRepository { public async findConsolidado(args: IFindPublicacaoRelatorio): Promise { let queryConsorcio = '' - if(args.consorcioNome!==undefined && !(['STPC','STPL'].some(i=>args.consorcioNome?.includes(i)))){ + if(args.consorcioNome!==undefined){ queryConsorcio = this.getQueryConsorcio(args.dataInicio.toISOString().slice(0,10), args.dataFim.toISOString().slice(0,10),args.pago,args.valorMin, args.valorMax,args.consorcioNome,args.aPagar); - }else if(args.consorcioNome!==undefined && (['STPC','STPL'].some(i=>args.consorcioNome?.includes(i)))){ - queryConsorcio = this.getQueryStpcStpl(args.dataInicio.toISOString().slice(0,10), - args.dataFim.toISOString().slice(0,10),args.pago,args.valorMin,args.valorMax,args.consorcioNome,args.aPagar); - }else if((args.favorecidoNome===undefined && args.consorcioNome === undefined)){ - queryConsorcio = this.getQueryConsorcio(args.dataInicio.toISOString().slice(0,10), - args.dataFim.toISOString().slice(0,10),args.pago,args.valorMin, - args.valorMax,args.consorcioNome,args.aPagar) + - ' union all '+ - this.getQueryStpcStpl(args.dataInicio?.toISOString().slice(0,10), - args.dataFim.toISOString().slice(0,10),args.pago,args.valorMin,args.valorMax,args.consorcioNome,args.aPagar); - } - + } + let queryOperadores =''; if(args.consorcioNome==undefined){ From 1acb465a97b8ba4e9492d12554420c5c68bd584b Mon Sep 17 00:00:00 2001 From: Raphael Rivas Date: Wed, 21 Aug 2024 09:54:05 -0300 Subject: [PATCH 05/11] fix: read Retorno faster and with no read errors - chore: env to show typeorm and debug logging - perf: faster query - 5-10 min -> 0.5s - fix: read last line with Registros A,B or A,B,Z - fix: move retorno from origin folder instead of default location --- src/cnab/cnab.controller.ts | 4 +- src/cnab/cnab.service.ts | 31 +++-- src/cnab/entity/arquivo-publicacao.entity.ts | 52 ++------ src/cnab/entity/pagamento/detalhe-a.entity.ts | 53 +++----- .../entity/pagamento/header-arquivo.entity.ts | 3 + .../entity/pagamento/header-lote.entity.ts | 14 +- .../item-transacao-agrupado.entity.ts | 3 + .../entity/pagamento/item-transacao.entity.ts | 3 + .../pagamento/transacao-agrupado.entity.ts | 9 +- .../pagamento/transacao-status.entity.ts | 16 ++- .../arquivo-publicacao.repository.ts | 94 ++++++------- .../cliente-favorecido.repository.ts | 101 +++++++------- .../pagamento/detalhe-a.repository.ts | 124 +++++++++++------- .../service/arquivo-publicacao.service.ts | 6 +- .../service/cliente-favorecido.service.ts | 6 +- src/cnab/service/ocorrencia.service.ts | 4 +- .../service/pagamento/detalhe-a.service.ts | 89 ++++--------- .../pagamento/remessa-retorno.service.ts | 22 ++-- src/cnab/utils/cnab/cnab-utils.ts | 5 +- src/database/data-source.ts | 2 +- src/database/typeorm-config.service.ts | 3 +- src/sftp/sftp.service.ts | 5 +- src/utils/custom-logger.ts | 36 +---- 23 files changed, 315 insertions(+), 370 deletions(-) diff --git a/src/cnab/cnab.controller.ts b/src/cnab/cnab.controller.ts index 8c5835e9..6916b6ba 100644 --- a/src/cnab/cnab.controller.ts +++ b/src/cnab/cnab.controller.ts @@ -141,14 +141,14 @@ export class CnabController { }; } - @ApiQuery({ name: 'folder', description: ApiDescription({ _: 'Pasta para ler os retornos', default: '/retorno' }), required: false, type: String }) + @ApiQuery({ name: 'folder', description: ApiDescription({ _: 'Pasta para ler os retornos', default: '`/retorno`' }), required: false, type: String }) @HttpCode(HttpStatus.OK) @ApiBearerAuth() @UseGuards(AuthGuard('jwt'), RolesGuard) @Roles(RoleEnum.admin) @Get('updateRetorno') async getUpdateRetorno( - @Query('folder', new ParseDatePipe()) folder: string | undefined, // + @Query('folder') folder: string | undefined, // ) { return await this.cnabService.updateRetorno(folder); } diff --git a/src/cnab/cnab.service.ts b/src/cnab/cnab.service.ts index a0417ddb..e34db679 100644 --- a/src/cnab/cnab.service.ts +++ b/src/cnab/cnab.service.ts @@ -88,7 +88,7 @@ export class CnabService { /** * Obtém dados de OrdemPagamento e salva em ItemTransacao e afins, para gerar remessa. - * + * * As datas de uma semana de pagamento são de sex-qui (D+1) * * Requirement: **Salvar novas transações Jaé** - {@link https://github.com/RJ-SMTR/api-cct/issues/207#issuecomment-1984421700 #207, items 3} @@ -319,7 +319,7 @@ export class CnabService { dataPagamento: ordem.dataPagamento, pagador: pagador, idOrdemPagamento: ordem.idOrdemPagamento, - status: new TransacaoStatus(TransacaoStatusEnum.created), + status: TransacaoStatus.fromEnum(TransacaoStatusEnum.created), }); return transacao; } @@ -497,7 +497,7 @@ export class CnabService { await this.remessaRetornoService.updateHeaderArquivoDTOFrom104(headerArquivoDTO, cnabHeaderArquivo); await this.transacaoAgService.save({ id: transacaoAgId, - status: new TransacaoStatus(TransacaoStatusEnum.remessa), + status: TransacaoStatus.fromEnum(TransacaoStatusEnum.remessa), }); } @@ -526,30 +526,43 @@ export class CnabService { */ public async updateRetorno(folder?: string) { const METHOD = this.updateRetorno.name; - let { cnabString, cnabName } = await this.sftpService.getFirstCnabRetorno(folder); + let { cnabName, cnabString } = await this.sftpService.getFirstCnabRetorno(folder); + const cnabs: string[] = []; + const success: any[] = []; + const failed: any[] = []; + const startDate = new Date(); while (cnabString) { if (!cnabName || !cnabString) { this.logger.log('Retorno não encontrado, abortando tarefa.', METHOD); return; } - + this.logger.log('Leitura de retornos iniciada...', METHOD); + const startDateItem = new Date(); try { const retorno104 = parseCnab240Pagamento(cnabString); + cnabs.push(cnabName); await this.remessaRetornoService.saveRetorno(retorno104); - await this.sftpService.moveToBackup(cnabName, SftpBackupFolder.RetornoSuccess, cnabString); + await this.sftpService.moveToBackup(cnabName, SftpBackupFolder.RetornoSuccess, cnabString, folder); + const durationItem = formatDateInterval(new Date(), startDateItem); + this.logger.log(`CNAB '${cnabName}' lido com sucesso - ${durationItem}`); + success.push(cnabName); const cnab = await this.sftpService.getFirstCnabRetorno(folder); cnabString = cnab.cnabString; cnabName = cnab.cnabName; } catch (error) { - this.logger.error(`Erro ao processar CNAB retorno, movendo para backup de erros e finalizando... - ${error}`, error.stack, METHOD); + const durationItem = formatDateInterval(new Date(), startDateItem); + this.logger.error(`Erro ao processar CNAB retorno (${durationItem}), movendo para backup de erros e finalizando... - ${error}`, error.stack, METHOD); if (!cnabName || !cnabString) { this.logger.log('Retorno não encontrado, abortando tarefa.', METHOD); return; } - await this.sftpService.moveToBackup(cnabName, SftpBackupFolder.RetornoFailure, cnabString); + await this.sftpService.moveToBackup(cnabName, SftpBackupFolder.RetornoFailure, cnabString, folder); + failed.push(cnabName); } } - return; + const duration = formatDateInterval(new Date(), startDate); + this.logger.log('Leitura de retornos finalizada com sucesso.', METHOD); + return { duration, cnabs: cnabs.length, success, failed }; } /** diff --git a/src/cnab/entity/arquivo-publicacao.entity.ts b/src/cnab/entity/arquivo-publicacao.entity.ts index d766789e..6912bd09 100644 --- a/src/cnab/entity/arquivo-publicacao.entity.ts +++ b/src/cnab/entity/arquivo-publicacao.entity.ts @@ -1,18 +1,6 @@ import { EntityHelper } from 'src/utils/entity-helper'; -import { - asNullableStringOrNumber -} from 'src/utils/pipe-utils'; -import { - AfterLoad, - Column, - CreateDateColumn, - DeepPartial, - Entity, - JoinColumn, - OneToOne, - PrimaryGeneratedColumn, - UpdateDateColumn, -} from 'typeorm'; +import { asNullableStringOrNumber } from 'src/utils/pipe-utils'; +import { AfterLoad, Column, CreateDateColumn, DeepPartial, Entity, JoinColumn, OneToOne, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'; import { ItemTransacao } from './pagamento/item-transacao.entity'; import { isSameDay } from 'date-fns'; @@ -25,6 +13,9 @@ export class ArquivoPublicacao extends EntityHelper { super(); if (arquivoPublicacao !== undefined) { Object.assign(this, arquivoPublicacao); + if (arquivoPublicacao.itemTransacao) { + arquivoPublicacao.itemTransacao = new ItemTransacao(arquivoPublicacao.itemTransacao); + } } } @PrimaryGeneratedColumn({ @@ -83,38 +74,17 @@ export class ArquivoPublicacao extends EntityHelper { return !this.isPago && this.dataEfetivacao; } - public static filterUnique( - publicacoes: ArquivoPublicacao[], - compare: ArquivoPublicacao, - ) { - return publicacoes.filter( - (p) => - p.itemTransacao.idConsorcio === compare.itemTransacao.idConsorcio && - p.itemTransacao.idOperadora === compare.itemTransacao.idOperadora && - isSameDay(p.itemTransacao.dataOrdem, compare.itemTransacao.dataOrdem), - ); + public static filterUnique(publicacoes: ArquivoPublicacao[], compare: ArquivoPublicacao) { + return publicacoes.filter((p) => p.itemTransacao.idConsorcio === compare.itemTransacao.idConsorcio && p.itemTransacao.idOperadora === compare.itemTransacao.idOperadora && isSameDay(p.itemTransacao.dataOrdem, compare.itemTransacao.dataOrdem)); } public static getUniqueUpdatePublicacoes(publicacoes: ArquivoPublicacao[]) { const unique: ArquivoPublicacao[] = []; publicacoes.forEach((publicacao) => { - const existing = ArquivoPublicacao.filterUnique(unique, publicacao)[0] as - | ArquivoPublicacao - | undefined; - const ocourences = ArquivoPublicacao.filterUnique( - publicacoes, - publicacao, - ).sort( - (a, b) => - b.itemTransacao.dataOrdem.getTime() - - a.itemTransacao.dataOrdem.getTime(), - ); - const paid = ocourences.filter((i) => i.isPago)[0] as - | ArquivoPublicacao - | undefined; - const noErrors = ocourences.filter((i) => !i.getIsError())[0] as - | ArquivoPublicacao - | undefined; + const existing = ArquivoPublicacao.filterUnique(unique, publicacao)[0] as ArquivoPublicacao | undefined; + const ocourences = ArquivoPublicacao.filterUnique(publicacoes, publicacao).sort((a, b) => b.itemTransacao.dataOrdem.getTime() - a.itemTransacao.dataOrdem.getTime()); + const paid = ocourences.filter((i) => i.isPago)[0] as ArquivoPublicacao | undefined; + const noErrors = ocourences.filter((i) => !i.getIsError())[0] as ArquivoPublicacao | undefined; const recent = ocourences[0] as ArquivoPublicacao; if (!existing) { const newPublicacao = paid || noErrors || recent; diff --git a/src/cnab/entity/pagamento/detalhe-a.entity.ts b/src/cnab/entity/pagamento/detalhe-a.entity.ts index 87ceddb1..2c883ffd 100644 --- a/src/cnab/entity/pagamento/detalhe-a.entity.ts +++ b/src/cnab/entity/pagamento/detalhe-a.entity.ts @@ -1,21 +1,6 @@ import { EntityHelper } from 'src/utils/entity-helper'; -import { - asNullableStringOrNumber, - asStringOrNumber, -} from 'src/utils/pipe-utils'; -import { - AfterLoad, - Column, - CreateDateColumn, - DeepPartial, - Entity, - JoinColumn, - ManyToOne, - OneToMany, - OneToOne, - PrimaryGeneratedColumn, - UpdateDateColumn, -} from 'typeorm'; +import { asNullableStringOrNumber, asStringOrNumber } from 'src/utils/pipe-utils'; +import { AfterLoad, Column, CreateDateColumn, DeepPartial, Entity, JoinColumn, ManyToOne, OneToMany, OneToOne, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'; import { HeaderLote } from './header-lote.entity'; import { ItemTransacaoAgrupado } from './item-transacao-agrupado.entity'; @@ -26,6 +11,19 @@ import { Ocorrencia } from './ocorrencia.entity'; */ @Entity() export class DetalheA extends EntityHelper { + constructor(detalheA?: DeepPartial) { + super(); + if (detalheA) { + Object.assign(this, detalheA); + if (detalheA.itemTransacaoAgrupado) { + detalheA.itemTransacaoAgrupado = new ItemTransacaoAgrupado(detalheA.itemTransacaoAgrupado); + } + if (detalheA.headerLote) { + detalheA.headerLote = new HeaderLote(detalheA.headerLote); + } + } + } + @PrimaryGeneratedColumn({ primaryKeyConstraintName: 'PK_DetalheA_id' }) id: number; @@ -143,31 +141,18 @@ export class DetalheA extends EntityHelper { /** * ID: headerLoteUniqueId + detalheA columns */ - public static getUniqueId( - detalheA: DeepPartial, - headerLoteUniqueId?: string, - ): string { - const _headerLoteUniqueId = headerLoteUniqueId - ? `(${headerLoteUniqueId})` - : `(${HeaderLote.getUniqueId(detalheA?.headerLote)})`; + public static getUniqueId(detalheA: DeepPartial, headerLoteUniqueId?: string): string { + const _headerLoteUniqueId = headerLoteUniqueId ? `(${headerLoteUniqueId})` : `(${HeaderLote.getUniqueId(detalheA?.headerLote)})`; return `${_headerLoteUniqueId}|${detalheA.nsr}`; } public isPago() { - const errors = Ocorrencia.getErrorCodesFromString( - this.ocorrenciasCnab || '', - ); + const errors = Ocorrencia.getErrorCodesFromString(this.ocorrenciasCnab || ''); return errors.length === 0; } public static getOcorrenciaErrors(detalhes: DetalheA[]) { - return detalhes.reduce( - (l, i) => [ - ...l, - ...i.ocorrencias.filter((j) => !['00', 'BD'].includes(j.code)), - ], - [], - ); + return detalhes.reduce((l, i) => [...l, ...i.ocorrencias.filter((j) => !['00', 'BD'].includes(j.code))], []); } public static getItemTransacaoAgIds(detalhesA: DetalheA[]) { diff --git a/src/cnab/entity/pagamento/header-arquivo.entity.ts b/src/cnab/entity/pagamento/header-arquivo.entity.ts index 033c1007..436a7ae7 100644 --- a/src/cnab/entity/pagamento/header-arquivo.entity.ts +++ b/src/cnab/entity/pagamento/header-arquivo.entity.ts @@ -23,6 +23,9 @@ export class HeaderArquivo extends EntityHelper { super(); if (dto) { Object.assign(this, dto); + if (dto.dataGeracao) { + this.dataGeracao = new Date(this.dataGeracao); + } } } diff --git a/src/cnab/entity/pagamento/header-lote.entity.ts b/src/cnab/entity/pagamento/header-lote.entity.ts index 29ed6c20..dbec297d 100644 --- a/src/cnab/entity/pagamento/header-lote.entity.ts +++ b/src/cnab/entity/pagamento/header-lote.entity.ts @@ -10,8 +10,11 @@ import { Pagador } from './pagador.entity'; export class HeaderLote extends EntityHelper { constructor(dto?: DeepPartial) { super(); - if (dto) { + if (dto) { Object.assign(this, dto); + if (dto.headerArquivo) { + dto.headerArquivo = new HeaderArquivo(dto.headerArquivo); + } } } @@ -60,13 +63,8 @@ export class HeaderLote extends EntityHelper { /** * ID: headerArquivo UniqueId + headerLote columns */ - public static getUniqueId( - item?: DeepPartial, - headerArqUniqueId?: string, - ): string { - const _headerArqUniqueId = headerArqUniqueId - ? `(${headerArqUniqueId})` - : `(${HeaderArquivo.getUniqueId(item?.headerArquivo)})`; + public static getUniqueId(item?: DeepPartial, headerArqUniqueId?: string): string { + const _headerArqUniqueId = headerArqUniqueId ? `(${headerArqUniqueId})` : `(${HeaderArquivo.getUniqueId(item?.headerArquivo)})`; return `${_headerArqUniqueId}|${item?.loteServico}`; } } diff --git a/src/cnab/entity/pagamento/item-transacao-agrupado.entity.ts b/src/cnab/entity/pagamento/item-transacao-agrupado.entity.ts index ec62f140..a51bcd7b 100644 --- a/src/cnab/entity/pagamento/item-transacao-agrupado.entity.ts +++ b/src/cnab/entity/pagamento/item-transacao-agrupado.entity.ts @@ -33,6 +33,9 @@ export class ItemTransacaoAgrupado extends EntityHelper { super(); if (dto) { Object.assign(this, dto); + if (this?.transacaoAgrupado) { + this.transacaoAgrupado = new TransacaoAgrupado(dto.transacaoAgrupado); + } } } diff --git a/src/cnab/entity/pagamento/item-transacao.entity.ts b/src/cnab/entity/pagamento/item-transacao.entity.ts index 5418a975..7e98bdff 100644 --- a/src/cnab/entity/pagamento/item-transacao.entity.ts +++ b/src/cnab/entity/pagamento/item-transacao.entity.ts @@ -33,6 +33,9 @@ export class ItemTransacao extends EntityHelper { super(); if (dto) { Object.assign(this, dto); + if (dto.itemTransacaoAgrupado) { + this.itemTransacaoAgrupado = new ItemTransacaoAgrupado(dto.itemTransacaoAgrupado) + } } } diff --git a/src/cnab/entity/pagamento/transacao-agrupado.entity.ts b/src/cnab/entity/pagamento/transacao-agrupado.entity.ts index 44225bac..4b4ef738 100644 --- a/src/cnab/entity/pagamento/transacao-agrupado.entity.ts +++ b/src/cnab/entity/pagamento/transacao-agrupado.entity.ts @@ -21,10 +21,13 @@ import { Transacao } from './transacao.entity'; */ @Entity() export class TransacaoAgrupado extends EntityHelper { - constructor(transacao?: DeepPartial) { + constructor(dto?: DeepPartial) { super(); - if (transacao !== undefined) { - Object.assign(this, transacao); + if (dto !== undefined) { + Object.assign(this, dto); + if (dto?.status) { + this.status = new TransacaoStatus(dto.status); + } } } diff --git a/src/cnab/entity/pagamento/transacao-status.entity.ts b/src/cnab/entity/pagamento/transacao-status.entity.ts index 4b806e7f..9515d1e2 100644 --- a/src/cnab/entity/pagamento/transacao-status.entity.ts +++ b/src/cnab/entity/pagamento/transacao-status.entity.ts @@ -1,4 +1,4 @@ -import { Column, Entity, PrimaryColumn } from 'typeorm'; +import { Column, DeepPartial, Entity, PrimaryColumn } from 'typeorm'; import { ApiProperty } from '@nestjs/swagger'; import { Allow } from 'class-validator'; import { EntityHelper } from 'src/utils/entity-helper'; @@ -7,14 +7,22 @@ import { TransacaoStatusEnum } from '../../enums/pagamento/transacao-status.enum @Entity() export class TransacaoStatus extends EntityHelper { - constructor(role?: TransacaoStatusEnum, onlyId = true) { + constructor(dto?: DeepPartial) { super(); + if (dto !== undefined) { + Object.assign(this, dto); + } + } + + public static fromEnum(role?: TransacaoStatusEnum, onlyId = true) { + const status = new TransacaoStatus(); if (role !== undefined) { - this.id = role; + status.id = role; if (!onlyId) { - this.name = Enum.getKey(TransacaoStatusEnum, role); + status.name = Enum.getKey(TransacaoStatusEnum, role); } } + return status; } @ApiProperty({ example: 1 }) diff --git a/src/cnab/repository/arquivo-publicacao.repository.ts b/src/cnab/repository/arquivo-publicacao.repository.ts index d3671b47..8fb2d84f 100644 --- a/src/cnab/repository/arquivo-publicacao.repository.ts +++ b/src/cnab/repository/arquivo-publicacao.repository.ts @@ -1,27 +1,20 @@ import { Injectable, Logger } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { - Between, - DeepPartial, - FindManyOptions, - In, - InsertResult, - QueryRunner, - Repository, -} from 'typeorm'; +import { Between, DeepPartial, FindManyOptions, In, InsertResult, Repository } from 'typeorm'; import { ArquivoPublicacaoResultDTO } from '../dto/arquivo-publicacao-result.dto'; import { ArquivoPublicacao } from '../entity/arquivo-publicacao.entity'; -import { DetalheAService } from '../service/pagamento/detalhe-a.service'; -import { OcorrenciaService } from '../service/ocorrencia.service'; -import { ItemTransacaoAgrupadoService } from '../service/pagamento/item-transacao-agrupado.service'; import { DetalheA } from '../entity/pagamento/detalhe-a.entity'; -import { IFindPublicacaoRelatorio } from 'src/relatorio/interfaces/find-publicacao-relatorio.interface'; +import { DetalheAService } from '../service/pagamento/detalhe-a.service'; +import { CommonHttpException } from 'src/utils/http-exception/common-http-exception'; + +export interface IArquivoPublicacaoRawWhere { + id?: number[]; + itemTransacaoAgrupadoId?: number[]; +} @Injectable() export class ArquivoPublicacaoRepository { - private logger: Logger = new Logger('ArquivoPublicacaoRepository', { - timestamp: true, - }); + private logger: Logger = new Logger('ArquivoPublicacaoRepository', { timestamp: true }); constructor( @InjectRepository(ArquivoPublicacao) @@ -32,24 +25,17 @@ export class ArquivoPublicacaoRepository { /** * Bulk save */ - public async insert( - dtos: DeepPartial[], - ): Promise { + public async insert(dtos: DeepPartial[]): Promise { return this.arquivoPublicacaoRepository.insert(dtos); } - public async upsert( - items: DeepPartial[], - ): Promise { + public async upsert(items: DeepPartial[]): Promise { return await this.arquivoPublicacaoRepository.upsert(items, { conflictPaths: { id: true }, }); } - public async update( - id: number, - dto: DeepPartial, - ): Promise { + public async update(id: number, dto: DeepPartial): Promise { await this.arquivoPublicacaoRepository.update(id, dto); const updated = await this.getOne({ where: { id: id } }); return updated; @@ -63,43 +49,57 @@ export class ArquivoPublicacaoRepository { }); const detalheAList = await this.detalheAService.findMany({ itemTransacaoAgrupado: { - id: In( - publicacoes.map((i) => i.itemTransacao.itemTransacaoAgrupado.id), - ), + id: In(publicacoes.map((i) => i.itemTransacao.itemTransacaoAgrupado.id)), }, }); const publicacaoResults: ArquivoPublicacaoResultDTO[] = []; for (const publicacao of publicacoes) { /** DetalheA é undefined se rodamos `saveNewTransacoesJae()` mas ainda não rodamos `sendRemessa()` */ - const detalheA = detalheAList.filter( - (i) => - i.itemTransacaoAgrupado.id && - i.itemTransacaoAgrupado.id === - publicacao.itemTransacao.itemTransacaoAgrupado.id, - )[0] as DetalheA | undefined; - publicacaoResults.push( - new ArquivoPublicacaoResultDTO(publicacao, detalheA?.ocorrencias), - ); + const detalheA = detalheAList.filter((i) => i.itemTransacaoAgrupado.id && i.itemTransacaoAgrupado.id === publicacao.itemTransacao.itemTransacaoAgrupado.id)[0] as DetalheA | undefined; + publicacaoResults.push(new ArquivoPublicacaoResultDTO(publicacao, detalheA?.ocorrencias)); } return publicacaoResults; } - public async getOne( - options: FindManyOptions, - ): Promise { + public async getOne(options: FindManyOptions): Promise { return await this.arquivoPublicacaoRepository.findOneOrFail(options); } - public async findMany( - options: FindManyOptions, - ): Promise { + public async findMany(options: FindManyOptions): Promise { return await this.arquivoPublicacaoRepository.find(options); } - public async findOne( - options: FindManyOptions, - ): Promise { + public async findManyRaw(where: IArquivoPublicacaoRawWhere): Promise { + const qWhere: { query: string; params?: any[] } = { query: '' }; + if (where.id) { + qWhere.query = `WHERE ap.id IN(${where.id.join(',')})`; + qWhere.params = []; + } else if (where.itemTransacaoAgrupadoId) { + qWhere.query = `WHERE ita.id IN(${where.itemTransacaoAgrupadoId.join(',')})`; + qWhere.params = []; + } + const result: any[] = await this.arquivoPublicacaoRepository.query( + ` + SELECT + ap.id, ap."createdAt", ap."dataEfetivacao", ap."dataGeracaoRetorno", ap."dataVencimento", ap."dataVencimento", + ap."horaGeracaoRetorno", ap."idTransacao", ap."isPago", ap."updatedAt", ap."valorRealEfetivado", + json_build_object( + 'id', it.id, + 'itemTransacaoAgrupado', json_build_object('id', ita.id) + ) AS "itemTransacao" + FROM arquivo_publicacao ap + INNER JOIN item_transacao it ON it.id = ap."itemTransacaoId" + INNER JOIN item_transacao_agrupado ita ON ita.id = it."itemTransacaoAgrupadoId" + ${qWhere.query} + `, + qWhere.params, + ); + const itens = result.map((i) => new ArquivoPublicacao(i)); + return itens; + } + + public async findOne(options: FindManyOptions): Promise { const many = await this.arquivoPublicacaoRepository.find(options); return many.pop() || null; } diff --git a/src/cnab/repository/cliente-favorecido.repository.ts b/src/cnab/repository/cliente-favorecido.repository.ts index 33487e9d..40f3e37f 100644 --- a/src/cnab/repository/cliente-favorecido.repository.ts +++ b/src/cnab/repository/cliente-favorecido.repository.ts @@ -3,21 +3,16 @@ import { InjectRepository } from '@nestjs/typeorm'; import { CustomLogger } from 'src/utils/custom-logger'; import { CommonHttpException } from 'src/utils/http-exception/common-http-exception'; import { EntityCondition } from 'src/utils/types/entity-condition.type'; -import { - DeepPartial, - FindManyOptions, - FindOneOptions, - FindOptionsWhere, - InsertResult, - Repository, - SelectQueryBuilder, - UpdateResult, -} from 'typeorm'; +import { DeepPartial, FindManyOptions, FindOneOptions, InsertResult, Repository, UpdateResult } from 'typeorm'; import { SaveClienteFavorecidoDTO } from '../dto/cliente-favorecido.dto'; import { ClienteFavorecido } from '../entity/cliente-favorecido.entity'; import { IClienteFavorecidoFindBy } from '../interfaces/cliente-favorecido-find-by.interface'; -import { Pagination } from 'src/utils/types/pagination.type'; -import { getPagination } from 'src/utils/get-pagination'; + +export interface IClienteFavorecidoRawWhere { + id?: number[]; + nome?: string[]; + cpfCnpj?: string; +} @Injectable() export class ClienteFavorecidoRepository { @@ -35,7 +30,7 @@ export class ClienteFavorecidoRepository { async remove(favorecidos: ClienteFavorecido[]) { return await this.clienteFavorecidoRepository.remove(favorecidos); } - + async save(dto: SaveClienteFavorecidoDTO): Promise { if (dto.id === undefined) { await this.create(dto); @@ -47,10 +42,7 @@ export class ClienteFavorecidoRepository { public async findOneByNome(nome: string): Promise { const cliente = await this.clienteFavorecidoRepository .createQueryBuilder('favorecido') - .where( - 'unaccent(UPPER("favorecido"."nome")) ILIKE unaccent(UPPER(:nome))', - { nome: `%${nome}%` }, - ) + .where('unaccent(UPPER("favorecido"."nome")) ILIKE unaccent(UPPER(:nome))', { nome: `%${nome}%` }) .getOne(); return cliente; @@ -60,50 +52,32 @@ export class ClienteFavorecidoRepository { return this.clienteFavorecidoRepository.clear(); } - async create( - createProfileDto: SaveClienteFavorecidoDTO, - ): Promise { - const createdUser = await this.clienteFavorecidoRepository.save( - this.clienteFavorecidoRepository.create(createProfileDto), - ); + async create(createProfileDto: SaveClienteFavorecidoDTO): Promise { + const createdUser = await this.clienteFavorecidoRepository.save(this.clienteFavorecidoRepository.create(createProfileDto)); this.logger.log(`ClienteFavorecido criado: ${createdUser.getLogInfo()}`); return createdUser; } - async upsert( - favorecidos: DeepPartial[], - ): Promise { + async upsert(favorecidos: DeepPartial[]): Promise { const payload = await this.clienteFavorecidoRepository.upsert(favorecidos, { conflictPaths: { cpfCnpj: true }, skipUpdateIfNoValuesChanged: true, }); - this.logger.log( - `${payload.identifiers.length} ClienteFavorecidos atualizados.`, - ); + this.logger.log(`${payload.identifiers.length} ClienteFavorecidos atualizados.`); return payload; } - async update( - id: number, - updateDto: SaveClienteFavorecidoDTO, - ): Promise { - const updatePayload = await this.clienteFavorecidoRepository.update( - { id: id }, - updateDto, - ); + async update(id: number, updateDto: SaveClienteFavorecidoDTO): Promise { + const updatePayload = await this.clienteFavorecidoRepository.update({ id: id }, updateDto); const updatedItem = new ClienteFavorecido({ id: id, ...updateDto, }); - this.logger.log( - `ClienteFavorecido atualizado: ${updatedItem.getLogInfo()}`, - ); + this.logger.log(`ClienteFavorecido atualizado: ${updatedItem.getLogInfo()}`); return updatePayload; } - public async getOne( - fields: EntityCondition, - ): Promise { + public async getOne(fields: EntityCondition): Promise { const result = await this.clienteFavorecidoRepository.findOne({ where: fields, }); @@ -115,15 +89,11 @@ export class ClienteFavorecidoRepository { public async findAll(): Promise { return await this.clienteFavorecidoRepository.find(); } - public async findMany( - options: FindManyOptions, - ): Promise { + public async findMany(options: FindManyOptions): Promise { return await this.clienteFavorecidoRepository.find(options); } - public async findManyBy( - where?: IClienteFavorecidoFindBy, - ): Promise { + public async findManyBy(where?: IClienteFavorecidoFindBy): Promise { let isFirstWhere = false; let qb = this.clienteFavorecidoRepository.createQueryBuilder('favorecido'); function cmd() { @@ -152,12 +122,33 @@ export class ClienteFavorecidoRepository { return result; } - public async findOne( - options: FindOneOptions, - ): Promise { - const first = ( - await this.clienteFavorecidoRepository.find(options) - ).shift(); + public async findOne(options: FindOneOptions): Promise { + const first = (await this.clienteFavorecidoRepository.find(options)).shift(); return first || null; } + + public async findOneRaw(where: IClienteFavorecidoRawWhere): Promise { + const qWhere: { query: string; params?: any[] } = { query: '' }; + if (where.id) { + qWhere.query = 'WHERE cf.id IN ($1)'; + qWhere.params = [where.id]; + } else if (where.cpfCnpj) { + qWhere.query = `WHERE cf."cpfCnpj" = $1`; + qWhere.params = [where.cpfCnpj]; + } else if (where.nome) { + const nomes = where.nome.map(n => `'%${n}%'`) + qWhere.query = `WHERE cf.nome ILIKE ANY(ARRAY[${nomes.join(',')}])`; + qWhere.params = []; + } + const result: any[] = await this.clienteFavorecidoRepository.query( + ` + SELECT cf.* + FROM cliente_favorecido cf + ${qWhere.query} + `, + qWhere.params, + ); + const itens = result.map((i) => new ClienteFavorecido(i)); + return itens[0]; + } } diff --git a/src/cnab/repository/pagamento/detalhe-a.repository.ts b/src/cnab/repository/pagamento/detalhe-a.repository.ts index 3802f95a..7487e5b5 100644 --- a/src/cnab/repository/pagamento/detalhe-a.repository.ts +++ b/src/cnab/repository/pagamento/detalhe-a.repository.ts @@ -5,24 +5,24 @@ import { logWarn } from 'src/utils/log-utils'; import { asNumber } from 'src/utils/pipe-utils'; import { EntityCondition } from 'src/utils/types/entity-condition.type'; import { Nullable } from 'src/utils/types/nullable.type'; -import { - DeepPartial, - FindManyOptions, - FindOneOptions, - In, - InsertResult, - LessThanOrEqual, - MoreThanOrEqual, - Repository, -} from 'typeorm'; +import { DeepPartial, FindManyOptions, FindOneOptions, In, InsertResult, LessThanOrEqual, MoreThanOrEqual, Repository } from 'typeorm'; import { DetalheA } from '../../entity/pagamento/detalhe-a.entity'; +import { getDateYMDString } from 'src/utils/date-utils'; +import { CommonHttpException } from 'src/utils/http-exception/common-http-exception'; + +export interface IDetalheARawWhere { + id?: number[]; + detalheARem?: { + dataVencimento: Date; + numeroDocumentoEmpresa: number; + valorLancamento: number; + }; +} @Injectable() export class DetalheARepository { - private logger: Logger = new Logger('DetalheARepository', { - timestamp: true, - }); + private logger: Logger = new Logger('DetalheARepository', { timestamp: true }); constructor( @InjectRepository(DetalheA) @@ -35,9 +35,7 @@ export class DetalheARepository { * @param dtos DTOs that can exist or not in database * @returns Saved objects not in database. */ - public async saveManyIfNotExists( - dtos: DeepPartial[], - ): Promise { + public async saveManyIfNotExists(dtos: DeepPartial[]): Promise { // Existing const existing = await this.findMany({ where: dtos.reduce( @@ -51,34 +49,19 @@ export class DetalheARepository { [], ), }); - const existingMap: Record> = existing.reduce( - (m, i) => ({ ...m, [DetalheA.getUniqueId(i)]: i }), - {}, - ); + const existingMap: Record> = existing.reduce((m, i) => ({ ...m, [DetalheA.getUniqueId(i)]: i }), {}); // Check if (existing.length === dtos.length) { - logWarn( - this.logger, - `${existing.length}/${dtos.length} DetalhesA já existem, nada a fazer...`, - ); + logWarn(this.logger, `${existing.length}/${dtos.length} DetalhesA já existem, nada a fazer...`); } else if (existing.length) { - logWarn( - this.logger, - `${existing.length}/${dtos.length} DetalhesA já existem, ignorando...`, - ); + logWarn(this.logger, `${existing.length}/${dtos.length} DetalhesA já existem, ignorando...`); return []; } // Save new - const newDTOs = dtos.reduce( - (l, i) => [...l, ...(!existingMap[DetalheA.getUniqueId(i)] ? [i] : [])], - [], - ); + const newDTOs = dtos.reduce((l, i) => [...l, ...(!existingMap[DetalheA.getUniqueId(i)] ? [i] : [])], []); const insert = await this.insert(newDTOs); // Return saved - const insertIds = (insert.identifiers as { id: number }[]).reduce( - (l, i) => [...l, i.id], - [], - ); + const insertIds = (insert.identifiers as { id: number }[]).reduce((l, i) => [...l, i.id], []); const saved = await this.findMany({ where: { id: In(insertIds) } }); return saved; } @@ -89,9 +72,10 @@ export class DetalheARepository { public async save(dto: DeepPartial): Promise { const saved = await this.detalheARepository.save(dto); - return await this.detalheARepository.findOneOrFail({ - where: { id: saved.id }, - }); + // return await this.detalheARepository.findOneOrFail({ + // where: { id: saved.id }, + // }); + return await this.getOneRaw({ id: [saved.id] }); } public async getOne(fields: EntityCondition): Promise { @@ -100,17 +84,60 @@ export class DetalheARepository { }); } - public async findOne( - options: FindOneOptions, - ): Promise> { + public async getOneRaw(where: IDetalheARawWhere): Promise { + const qWhere: { query: string; params?: any[] } = { query: '' }; + if (where.id) { + qWhere.query = `WHERE da.id IN (${where.id.join(',')})`; + qWhere.params = []; + } else if (where.detalheARem) { + const { dataVencimento, numeroDocumentoEmpresa, valorLancamento } = where.detalheARem; + qWhere.query = `WHERE da."dataVencimento"::DATE = $1 AND da."numeroDocumentoEmpresa" = $2 AND da."valorLancamento" = $3`; + qWhere.params = [getDateYMDString(dataVencimento), numeroDocumentoEmpresa, valorLancamento]; + } + const result: any[] = await this.detalheARepository.query( + ` + SELECT + da.id, da."createdAt", da."dataEfetivacao", da."dataVencimento", da."finalidadeDOC", + da."indicadorBloqueio", da."indicadorFormaParcelamento", da."loteServico", da.nsr, + da."numeroDocumentoBanco", da."numeroDocumentoEmpresa", da."numeroParcela", + da."ocorrenciasCnab", da."periodoVencimento", da."quantidadeMoeda", + da."quantidadeParcelas", da."tipoMoeda", da."updatedAt", da."valorLancamento", + da."valorRealEfetivado", + json_build_object( + 'id', da."itemTransacaoAgrupadoId", + 'transacaoAgrupado', json_build_object( + 'id', ta.id, + 'status', json_build_object('id', ts.id, 'name', ts.name) + ) + ) AS "itemTransacaoAgrupado", + json_build_object( + 'id', hl.id, + 'headerArquivo', json_build_object('id', ha.id, 'dataGeracao', ha."dataGeracao") + ) AS "headerLote" + FROM detalhe_a da + INNER JOIN header_lote hl ON da."headerLoteId" = hl.id + INNER JOIN header_arquivo ha ON hl."headerArquivoId" = ha.id + INNER JOIN item_transacao_agrupado ita ON da."itemTransacaoAgrupadoId" = ita.id + INNER JOIN transacao_agrupado ta ON ta.id = ita."transacaoAgrupadoId" + INNER JOIN transacao_status ts ON ts.id = ta."statusId" + ${qWhere.query} + `, + qWhere.params, + ); + if (result.length == 0) { + throw CommonHttpException.details('It should return at least one DetalheA'); + } + const detalhes = result.map((i) => new DetalheA(i)); + return detalhes[0]; + } + + public async findOne(options: FindOneOptions): Promise> { return await this.detalheARepository.findOne(options); } - public async findMany( - options?: FindManyOptions, - ): Promise { + public async findMany(options?: FindManyOptions): Promise { const detalheA = await this.detalheARepository.find(options); - // await this.forceManyEager(detalheA); + // await this.forceManyEager(detalheA); return detalheA; } @@ -122,10 +149,7 @@ export class DetalheARepository { public async getNextNumeroDocumento(date: Date): Promise { return ( (await this.detalheARepository.count({ - where: [ - { createdAt: MoreThanOrEqual(startOfDay(date)) }, - { createdAt: LessThanOrEqual(endOfDay(date)) }, - ], + where: [{ createdAt: MoreThanOrEqual(startOfDay(date)) }, { createdAt: LessThanOrEqual(endOfDay(date)) }], })) + 1 ); } diff --git a/src/cnab/service/arquivo-publicacao.service.ts b/src/cnab/service/arquivo-publicacao.service.ts index b568943f..e240f0e7 100644 --- a/src/cnab/service/arquivo-publicacao.service.ts +++ b/src/cnab/service/arquivo-publicacao.service.ts @@ -5,7 +5,7 @@ import { DeepPartial, FindManyOptions, QueryRunner } from 'typeorm'; import { ArquivoPublicacao } from '../entity/arquivo-publicacao.entity'; import { DetalheA } from '../entity/pagamento/detalhe-a.entity'; import { ItemTransacao } from '../entity/pagamento/item-transacao.entity'; -import { ArquivoPublicacaoRepository } from '../repository/arquivo-publicacao.repository'; +import { ArquivoPublicacaoRepository, IArquivoPublicacaoRawWhere } from '../repository/arquivo-publicacao.repository'; import { OcorrenciaService } from './ocorrencia.service'; import { ItemTransacaoService } from './pagamento/item-transacao.service'; import { IFindPublicacaoRelatorio } from 'src/relatorio/interfaces/find-publicacao-relatorio.interface'; @@ -26,6 +26,10 @@ export class ArquivoPublicacaoService { return this.arquivoPublicacaoRepository.findMany(options); } + public findManyRaw(where: IArquivoPublicacaoRawWhere) { + return this.arquivoPublicacaoRepository.findManyRaw(where); + } + public async findManyByDate(startDate: Date, endDate: Date) { return await this.arquivoPublicacaoRepository.findManyByDate( startDate, diff --git a/src/cnab/service/cliente-favorecido.service.ts b/src/cnab/service/cliente-favorecido.service.ts index ba528032..2f2e6e26 100644 --- a/src/cnab/service/cliente-favorecido.service.ts +++ b/src/cnab/service/cliente-favorecido.service.ts @@ -11,7 +11,7 @@ import { validateDTO } from 'src/utils/validation-utils'; import { FindManyOptions, FindOneOptions, In } from 'typeorm'; import { SaveClienteFavorecidoDTO } from '../dto/cliente-favorecido.dto'; import { ClienteFavorecido } from '../entity/cliente-favorecido.entity'; -import { ClienteFavorecidoRepository } from '../repository/cliente-favorecido.repository'; +import { ClienteFavorecidoRepository, IClienteFavorecidoRawWhere } from '../repository/cliente-favorecido.repository'; import { IClienteFavorecidoFindBy } from '../interfaces/cliente-favorecido-find-by.interface'; @Injectable() @@ -205,4 +205,8 @@ export class ClienteFavorecidoService { ): Promise { return await this.clienteFavorecidoRepository.findOne(options); } + + public async findOneRaw(where: IClienteFavorecidoRawWhere): Promise { + return await this.clienteFavorecidoRepository.findOneRaw(where); + } } diff --git a/src/cnab/service/ocorrencia.service.ts b/src/cnab/service/ocorrencia.service.ts index 5c75fdc8..dae09140 100644 --- a/src/cnab/service/ocorrencia.service.ts +++ b/src/cnab/service/ocorrencia.service.ts @@ -25,9 +25,11 @@ export class OcorrenciaService { } public async saveMany(ocorrencias: DeepPartial[], queryRunner: QueryRunner) { + const saved: Ocorrencia[] = []; for (const ocorrencia of ocorrencias) { - return await queryRunner.manager.getRepository(Ocorrencia).save(ocorrencia); + saved.push(await queryRunner.manager.getRepository(Ocorrencia).save(ocorrencia)); } + return saved; } public async delete(detalheA: DeepPartial, queryRunner: QueryRunner) { diff --git a/src/cnab/service/pagamento/detalhe-a.service.ts b/src/cnab/service/pagamento/detalhe-a.service.ts index 6cf558bd..462b5621 100644 --- a/src/cnab/service/pagamento/detalhe-a.service.ts +++ b/src/cnab/service/pagamento/detalhe-a.service.ts @@ -21,12 +21,7 @@ import { TransacaoStatus } from 'src/cnab/entity/pagamento/transacao-status.enti export class DetalheAService { private logger: Logger = new Logger('DetalheAService', { timestamp: true }); - constructor( - private detalheARepository: DetalheARepository, - private clienteFavorecidoService: ClienteFavorecidoService, - private transacaoAgrupadoService: TransacaoAgrupadoService, - private pagamentosPendentesService: PagamentosPendentesService, - ) {} + constructor(private detalheARepository: DetalheARepository, private clienteFavorecidoService: ClienteFavorecidoService, private transacaoAgrupadoService: TransacaoAgrupadoService, private pagamentosPendentesService: PagamentosPendentesService) {} /** * Assumimos que todos os detalheA do retorno têm dataVencimento na mesma semana. @@ -34,8 +29,7 @@ export class DetalheAService { async updateDetalheAStatus(detalheA: DetalheA) { // Update Transacao status const transacaoAgrupada = detalheA.itemTransacaoAgrupado.transacaoAgrupado; - const status = new TransacaoStatus(); - status.id = TransacaoStatusEnum.publicado; + const status = TransacaoStatus.fromEnum(TransacaoStatusEnum.publicado); transacaoAgrupada.status = status; await this.transacaoAgrupadoService.save(transacaoAgrupada); } @@ -46,20 +40,13 @@ export class DetalheAService { * @param dtos DTOs that can exist or not in database * @returns Saved objects not in database. */ - public saveManyIfNotExists( - dtos: DeepPartial[], - ): Promise { + public saveManyIfNotExists(dtos: DeepPartial[]): Promise { return this.detalheARepository.saveManyIfNotExists(dtos); } - public async saveRetornoFrom104( - headerArq: CnabHeaderArquivo104, - headerLotePgto: CnabHeaderLote104Pgto, - r: CnabRegistros104Pgto, - dataEfetivacao: Date, - ): Promise { - const favorecido = await this.clienteFavorecidoService.findOne({ - where: { nome: ILike(`%${r.detalheA.nomeTerceiro.stringValue.trim()}%`) }, + public async saveRetornoFrom104(headerArq: CnabHeaderArquivo104, headerLotePgto: CnabHeaderLote104Pgto, r: CnabRegistros104Pgto, dataEfetivacao: Date): Promise { + const favorecido = await this.clienteFavorecidoService.findOneRaw({ + nome: [r.detalheA.nomeTerceiro.stringValue.trim()], }); if (!favorecido) { @@ -68,17 +55,16 @@ export class DetalheAService { const dataVencimento = startOfDay(r.detalheA.dataVencimento.convertedValue); let detalheARem; try { - detalheARem = await this.detalheARepository.getOne({ - dataVencimento: dataVencimento, - numeroDocumentoEmpresa: - r.detalheA.numeroDocumentoEmpresa.convertedValue, - valorLancamento: r.detalheA.valorLancamento.convertedValue, + detalheARem = await this.detalheARepository.getOneRaw({ + detalheARem: { + dataVencimento: dataVencimento, + numeroDocumentoEmpresa: r.detalheA.numeroDocumentoEmpresa.convertedValue, + valorLancamento: r.detalheA.valorLancamento.convertedValue, + }, }); - if (detalheARem.ocorrenciasCnab === undefined || - detalheARem.ocorrenciasCnab === '' || - detalheARem.ocorrenciasCnab !== r.detalheA.ocorrencias.value.trim()){ + if (detalheARem.ocorrenciasCnab === undefined || detalheARem.ocorrenciasCnab === '' || detalheARem.ocorrenciasCnab !== r.detalheA.ocorrencias.value.trim()) { const detalheA = new DetalheADTO({ - id: detalheARem.id, + id: detalheARem.id, loteServico: Number(r.detalheA.loteServico.value), finalidadeDOC: r.detalheA.finalidadeDOC.value, numeroDocumentoEmpresa: Number(r.detalheA.numeroDocumentoEmpresa.value), @@ -90,32 +76,20 @@ export class DetalheAService { numeroDocumentoBanco: String(r.detalheA.numeroDocumentoBanco.convertedValue), quantidadeParcelas: Number(r.detalheA.quantidadeParcelas.value), indicadorBloqueio: r.detalheA.indicadorBloqueio.value, - indicadorFormaParcelamento: - r.detalheA.indicadorFormaParcelamento.stringValue, + indicadorFormaParcelamento: r.detalheA.indicadorFormaParcelamento.stringValue, periodoVencimento: startOfDay(r.detalheA.dataVencimento.convertedValue), numeroParcela: r.detalheA.numeroParcela.convertedValue, valorRealEfetivado: r.detalheA.valorRealEfetivado.convertedValue, nsr: Number(r.detalheA.nsr.value), - ocorrenciasCnab: - r.detalheA.ocorrencias.value.trim() || - headerLotePgto.ocorrencias.value.trim() || - headerArq.ocorrenciaCobrancaSemPapel.value.trim(), + ocorrenciasCnab: r.detalheA.ocorrencias.value.trim() || headerLotePgto.ocorrencias.value.trim() || headerArq.ocorrenciaCobrancaSemPapel.value.trim(), }); return await this.detalheARepository.save(detalheA); } } catch (err) { this.logger.error(err); - this.logger.debug( - `Detalhe não encontrado para o favorecido: `, - favorecido?.nome, - ); + this.logger.debug(`Detalhe não encontrado para o favorecido: `, favorecido?.nome); } - if ( - r.detalheA.ocorrencias !== undefined && - r.detalheA.ocorrencias.value.trim() !== '' && - r.detalheA.ocorrencias.value.trim() !== 'BD' && - r.detalheA.ocorrencias.value.trim() !== '00' - ) { + if (r.detalheA.ocorrencias !== undefined && r.detalheA.ocorrencias.value.trim() !== '' && r.detalheA.ocorrencias.value.trim() !== 'BD' && r.detalheA.ocorrencias.value.trim() !== '00') { const pg = await this.pagamentosPendentesService.findOne({ numeroDocumento: r.detalheA.numeroDocumentoEmpresa.value.trim(), valorLancamento: r.detalheA.valorLancamento.convertedValue, @@ -124,16 +98,11 @@ export class DetalheAService { if (!pg) { const pagamentosPendentes = new PagamentosPendentes(); - pagamentosPendentes.nomeFavorecido = - r.detalheA.nomeTerceiro.stringValue.trim(); - pagamentosPendentes.dataVencimento = - r.detalheA.dataVencimento.convertedValue; - pagamentosPendentes.valorLancamento = - r.detalheA.valorLancamento.convertedValue; - pagamentosPendentes.numeroDocumento = - r.detalheA.numeroDocumentoEmpresa.value.trim(); - pagamentosPendentes.ocorrenciaErro = - r.detalheA.ocorrencias.value.trim(); + pagamentosPendentes.nomeFavorecido = r.detalheA.nomeTerceiro.stringValue.trim(); + pagamentosPendentes.dataVencimento = r.detalheA.dataVencimento.convertedValue; + pagamentosPendentes.valorLancamento = r.detalheA.valorLancamento.convertedValue; + pagamentosPendentes.numeroDocumento = r.detalheA.numeroDocumentoEmpresa.value.trim(); + pagamentosPendentes.ocorrenciaErro = r.detalheA.ocorrencias.value.trim(); await this.pagamentosPendentesService.save(pagamentosPendentes); } } @@ -149,21 +118,15 @@ export class DetalheAService { return await this.detalheARepository.getOne(fields); } - public async findOne( - options: FindOneOptions, - ): Promise> { + public async findOne(options: FindOneOptions): Promise> { return await this.detalheARepository.findOne(options); } - public async findMany( - fields: EntityCondition, - ): Promise { + public async findMany(fields: EntityCondition): Promise { return await this.detalheARepository.findMany({ where: fields }); } - public async findManyRaw( - options: FindManyOptions, - ): Promise { + public async findManyRaw(options: FindManyOptions): Promise { return await this.detalheARepository.findMany(options); } diff --git a/src/cnab/service/pagamento/remessa-retorno.service.ts b/src/cnab/service/pagamento/remessa-retorno.service.ts index cc517e9e..bb0d5e48 100644 --- a/src/cnab/service/pagamento/remessa-retorno.service.ts +++ b/src/cnab/service/pagamento/remessa-retorno.service.ts @@ -520,21 +520,15 @@ export class RemessaRetornoService { * Atualizar publicacoes de retorno */ async savePublicacaoRetorno(detalheARetorno: DetalheA, queryRunner: QueryRunner) { - const itens = await this.itemTransacaoService.findMany({ - where: { - itemTransacaoAgrupado: { - id: detalheARetorno.itemTransacaoAgrupado.id, - }, - }, + const publicacoes = await this.arquivoPublicacaoService.findManyRaw({ + itemTransacaoAgrupadoId: [detalheARetorno.itemTransacaoAgrupado.id], }); - for (const item of itens) { - const publicacao = await this.arquivoPublicacaoService.getOne({ - where: { - itemTransacao: { - id: item.id, - }, - }, - }); + // const publicacoes = await this.arquivoPublicacaoService.findMany({ + // where: { + // itemTransacao: { itemTransacaoAgrupado: { id: detalheARetorno.itemTransacaoAgrupado.id } }, + // }, + // }); + for (const publicacao of publicacoes) { publicacao.isPago = detalheARetorno.isPago(); if (publicacao.isPago) { publicacao.valorRealEfetivado = publicacao.itemTransacao.valor; diff --git a/src/cnab/utils/cnab/cnab-utils.ts b/src/cnab/utils/cnab/cnab-utils.ts index 6f6e977c..f0803128 100644 --- a/src/cnab/utils/cnab/cnab-utils.ts +++ b/src/cnab/utils/cnab/cnab-utils.ts @@ -199,7 +199,7 @@ export function parseCnabFile(cnabString: string, fileDTO: CnabFile): CnabFile { // get valid data const file = sc(fileDTO); const registrosDTO = getCnabRegistrosFromCnabFile(file); - const lines = cnabString.trim().replace(/\r\n/g, '\n').split('\n'); + const lines = cnabString.replace(/\r\n/g, '\n').replace(/\n$/g, '').split('\n'); // parse setParseCnabHeaderTrailerArquivo(lines, registrosDTO, file); @@ -211,9 +211,8 @@ export function parseCnabFile(cnabString: string, fileDTO: CnabFile): CnabFile { * Parse HeaderArquivo, TrailerArquivo directly to CnabRegistro */ function setParseCnabHeaderTrailerArquivo(lines: string[], registrosDTO: CnabRegistro[], file: CnabFile) { - const lastIndex = registrosDTO.length - 1; file.headerArquivo = parseCnabRegistro(lines[0], registrosDTO[0]); - file.trailerArquivo = parseCnabRegistro(lines[lastIndex], registrosDTO[lastIndex]); + file.trailerArquivo = parseCnabRegistro(lines.slice(-1)[0], registrosDTO.slice(-1)[0]); } function parseCnabLotes(cnabAllLines: string[], registrosDTO: CnabRegistro[]): CnabLote[] { diff --git a/src/database/data-source.ts b/src/database/data-source.ts index 81403786..99183194 100644 --- a/src/database/data-source.ts +++ b/src/database/data-source.ts @@ -18,7 +18,7 @@ export const AppDataSource = new DataSource({ resourceArn: process.env.DATABASE_RESOURCE_ARN, dropSchema: false, keepConnectionAlive: true, - logging: process.env.NODE_ENV !== 'production', + logging: process.env.NODE_ENV !== 'production' || process.env.TYPEORM_LOGGING === 'true', entities: [__dirname + '/../**/*.entity{.ts,.js}'], migrations: [__dirname + '/migrations/**/*{.ts,.js}'], cli: { diff --git a/src/database/typeorm-config.service.ts b/src/database/typeorm-config.service.ts index 7a6025da..e4b72e66 100644 --- a/src/database/typeorm-config.service.ts +++ b/src/database/typeorm-config.service.ts @@ -21,8 +21,7 @@ export class TypeOrmConfigService implements TypeOrmOptionsFactory { }), dropSchema: false, keepConnectionAlive: true, - logging: - this.configService.get('app.nodeEnv', { infer: true }) !== 'production', + logging: this.configService.get('app.nodeEnv', { infer: true }) !== 'production' || process.env.TYPEORM_LOGGING === 'true', entities: [__dirname + '/../**/*.entity{.ts,.js}'], migrations: [__dirname + '/migrations/**/*{.ts,.js}'], cli: { diff --git a/src/sftp/sftp.service.ts b/src/sftp/sftp.service.ts index 70499aac..c7c840a9 100644 --- a/src/sftp/sftp.service.ts +++ b/src/sftp/sftp.service.ts @@ -192,9 +192,10 @@ export class SftpService implements OnModuleInit, OnModuleLoad { cnabName: string, folder: SftpBackupFolder, cnabContentIfNoOrigin?: string, + originFolder = this.FOLDERS.RETORNO, ) { - const METHOD = 'moveToBackup()'; - const originPath = this.dir(`${this.FOLDERS.RETORNO}/${cnabName}`); + const METHOD = 'moveToBackup'; + const originPath = this.dir(`${originFolder}/${cnabName}`); const destPath = this.dir(`${folder}/${cnabName}`); await this.connectClient(); if (cnabContentIfNoOrigin && !(await this.sftpClient.exists(originPath))) { diff --git a/src/utils/custom-logger.ts b/src/utils/custom-logger.ts index 3079fe95..cf3f6cf8 100644 --- a/src/utils/custom-logger.ts +++ b/src/utils/custom-logger.ts @@ -19,12 +19,7 @@ enum colors { @Injectable() export class CustomLogger extends Logger { private IS_LOCAL = undefined; - private color( - type: 'error' | 'warn' | 'debug' | 'level', - message: string, - level?: 'VERBOSE' | 'DEBUG' | 'LOG' | 'WARN' | 'ERROR', - reset = true, - ): string { + private color(type: 'error' | 'warn' | 'debug' | 'level', message: string, level?: 'VERBOSE' | 'DEBUG' | 'LOG' | 'WARN' | 'ERROR', reset = true): string { let _default = 'log'; if (level === 'WARN') { _default = 'warn'; @@ -62,10 +57,7 @@ export class CustomLogger extends Logger { } private isLocal() { - return ( - ['local', 'development'].includes(process.env.NODE_ENV || '') && - this.IS_LOCAL !== false - ); + return (['local', 'development'].includes(process.env.NODE_ENV || '') || process.env.CUSTOM_LOGGER_DEBUG === 'true') && this.IS_LOCAL !== false; } private getContext(isLocal: boolean, context?: string) { @@ -132,38 +124,24 @@ export class CustomLogger extends Logger { } } - private formatMessage( - message: string, - level: 'VERBOSE' | 'DEBUG' | 'LOG' | 'WARN' | 'ERROR', - context?: string, - stack?: string, - ): string { + private formatMessage(message: string, level: 'VERBOSE' | 'DEBUG' | 'LOG' | 'WARN' | 'ERROR', context?: string, stack?: string): string { const IS_LOCAL = this.isLocal(); const nest = this.color('level', `[Nest] ${this.getProcessId()}`, level); const levelStr = this.color('level', level.padStart(7, ' '), level); const contextStr = this.getContext(IS_LOCAL, context); - const timestampStr = this.options.timestamp - ? this.color('level', ' - ', level) + this.getTimestamp() + ' ' - : ''; + const timestampStr = this.options.timestamp ? this.color('level', ' - ', level) + this.getTimestamp() + ' ' : ''; // const details = (trace as any)?.response; // const stack = (trace as any)?.stack; - let messageStr = - level === 'ERROR' ? this.formatError(message, undefined, stack) : message; + let messageStr = level === 'ERROR' ? this.formatError(message, undefined, stack) : message; messageStr = this.color('level', messageStr, level, !(level === 'ERROR')); - const formattedMessage = IS_LOCAL - ? `${nest} ${timestampStr}${levelStr} ${contextStr} ${messageStr}` - : `${contextStr} ${messageStr}`; + const formattedMessage = IS_LOCAL ? `${nest} ${timestampStr}${levelStr} ${contextStr} ${messageStr}` : `${contextStr} ${messageStr}`; return formattedMessage; } /** * Format log for error content. */ - private formatError( - firstLine: string, - message?: object | string, - traceback?: string, - ): string { + private formatError(firstLine: string, message?: object | string, traceback?: string): string { let formattedString = firstLine; if (message) { formattedString += `\n${asJSONStrOrObj(message)}`; From b11baf322c053773805a28040af509c8845d7abe Mon Sep 17 00:00:00 2001 From: Raphael Rivas Date: Wed, 21 Aug 2024 15:01:00 -0300 Subject: [PATCH 06/11] fix: If detalheA not found, move to failed - refactor: CustomLogger instead of logger - fix: log error message of nested HttpException --- src/auth-licensee/auth-licensee.service.ts | 3 +- src/auth/auth.controller.ts | 3 +- src/auth/auth.service.ts | 3 +- src/bigquery/bigquery.service.ts | 4 +- .../bigquery-ordem-pagamento.repository.ts | 5 +- .../bigquery-transacao.repository.ts | 3 +- .../bigquery-ordem-pagamento.service.ts | 15 +-- .../services/bigquery-transacao.service.ts | 30 ++--- src/cnab/cnab.service.ts | 10 +- .../arquivo-publicacao.repository.ts | 7 +- .../cliente-favorecido.repository.ts | 9 +- .../extrato/extrato-detalhe-e.repository.ts | 41 +++---- src/cnab/repository/ocorrencia.repository.ts | 15 +-- .../pagamento/detalhe-a.repository.ts | 35 ++++-- .../service/pagamento/detalhe-a.service.ts | 40 +++--- .../pagamento/remessa-retorno.service.ts | 18 +-- src/users/users.service.ts | 114 ++++-------------- src/utils/custom-logger.ts | 9 +- 18 files changed, 147 insertions(+), 217 deletions(-) diff --git a/src/auth-licensee/auth-licensee.service.ts b/src/auth-licensee/auth-licensee.service.ts index 5322e9de..ef07d934 100644 --- a/src/auth-licensee/auth-licensee.service.ts +++ b/src/auth-licensee/auth-licensee.service.ts @@ -17,10 +17,11 @@ import { AuthLicenseeLoginDto } from './dto/auth-licensee-login.dto'; import { AuthRegisterLicenseeDto } from './dto/auth-register-licensee.dto'; import { IALConcludeRegistration } from './interfaces/al-conclude-registration.interface'; import { IALInviteProfile } from './interfaces/al-invite-profile.interface'; +import { CustomLogger } from 'src/utils/custom-logger'; @Injectable() export class AuthLicenseeService { - private logger: Logger = new Logger('AuthLicenseeService', { + private logger = new CustomLogger('AuthLicenseeService', { timestamp: true, }); diff --git a/src/auth/auth.controller.ts b/src/auth/auth.controller.ts index 5bc8d5ff..a647eccf 100644 --- a/src/auth/auth.controller.ts +++ b/src/auth/auth.controller.ts @@ -28,6 +28,7 @@ import { AuthRegisterLoginDto } from './dto/auth-register-login.dto'; import { AuthResendEmailDto } from './dto/auth-resend-mail.dto'; import { AuthResetPasswordDto } from './dto/auth-reset-password.dto'; import { AuthUpdateDto } from './dto/auth-update.dto'; +import { CustomLogger } from 'src/utils/custom-logger'; @ApiTags('Auth') @Controller({ @@ -35,7 +36,7 @@ import { AuthUpdateDto } from './dto/auth-update.dto'; version: '1', }) export class AuthController { - private logger: Logger = new Logger('AuthController', { timestamp: true }); + private logger = new CustomLogger('AuthController', { timestamp: true }); constructor( private readonly authService: AuthService, diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts index e303b208..681a9b12 100644 --- a/src/auth/auth.service.ts +++ b/src/auth/auth.service.ts @@ -26,10 +26,11 @@ import { AuthEmailLoginDto } from './dto/auth-email-login.dto'; import { AuthRegisterLoginDto } from './dto/auth-register-login.dto'; import { AuthResendEmailDto } from './dto/auth-resend-mail.dto'; import { AuthUpdateDto } from './dto/auth-update.dto'; +import { CustomLogger } from 'src/utils/custom-logger'; @Injectable() export class AuthService { - private logger: Logger = new Logger('AuthService', { timestamp: true }); + private logger = new CustomLogger('AuthService', { timestamp: true }); constructor( private jwtService: JwtService, diff --git a/src/bigquery/bigquery.service.ts b/src/bigquery/bigquery.service.ts index 703a03ce..998b2a66 100644 --- a/src/bigquery/bigquery.service.ts +++ b/src/bigquery/bigquery.service.ts @@ -16,9 +16,7 @@ export enum BigquerySource { @Injectable() export class BigqueryService { private bigQueryInstances: Record = {}; - private logger: Logger = new CustomLogger(BigqueryService.name, { - timestamp: true, - }); + private logger = new CustomLogger(BigqueryService.name, { timestamp: true }); constructor(private configService: ConfigService) { const jsonCredentials = () => { diff --git a/src/bigquery/repositories/bigquery-ordem-pagamento.repository.ts b/src/bigquery/repositories/bigquery-ordem-pagamento.repository.ts index 069dee5a..9848aa5c 100644 --- a/src/bigquery/repositories/bigquery-ordem-pagamento.repository.ts +++ b/src/bigquery/repositories/bigquery-ordem-pagamento.repository.ts @@ -4,12 +4,11 @@ import { bigToNumber } from 'src/utils/pipe-utils'; import { BigquerySource, BigqueryService } from '../bigquery.service'; import { BigqueryOrdemPagamento } from '../entities/ordem-pagamento.bigquery-entity'; import { IBigqueryFindOrdemPagamento } from '../interfaces/bigquery-find-ordem-pagamento.interface'; +import { CustomLogger } from 'src/utils/custom-logger'; @Injectable() export class BigqueryOrdemPagamentoRepository { - private logger: Logger = new Logger('BigqueryOrdemPagamentoRepository', { - timestamp: true, - }); + private logger = new CustomLogger('BigqueryOrdemPagamentoRepository', { timestamp: true }); constructor( private readonly bigqueryService: BigqueryService, diff --git a/src/bigquery/repositories/bigquery-transacao.repository.ts b/src/bigquery/repositories/bigquery-transacao.repository.ts index a1231012..cec6bd50 100644 --- a/src/bigquery/repositories/bigquery-transacao.repository.ts +++ b/src/bigquery/repositories/bigquery-transacao.repository.ts @@ -15,10 +15,11 @@ import { BqTsansacaoTipoIntegracaoMap } from '../maps/bq-transacao-tipo-integrac import { BqTransacaoTipoPagamentoMap } from '../maps/bq-transacao-tipo-pagamento.map'; import { BqTransacaoTipoTransacaoMap } from '../maps/bq-transacao-tipo-transacao.map'; import { logWarn } from 'src/utils/log-utils'; +import { CustomLogger } from 'src/utils/custom-logger'; @Injectable() export class BigqueryTransacaoRepository { - private logger: Logger = new Logger('BigqueryTransacaoRepository', { + private logger = new CustomLogger('BigqueryTransacaoRepository', { timestamp: true, }); diff --git a/src/bigquery/services/bigquery-ordem-pagamento.service.ts b/src/bigquery/services/bigquery-ordem-pagamento.service.ts index 64a073d1..95a06d69 100644 --- a/src/bigquery/services/bigquery-ordem-pagamento.service.ts +++ b/src/bigquery/services/bigquery-ordem-pagamento.service.ts @@ -2,25 +2,18 @@ import { Injectable, Logger } from '@nestjs/common'; import { isFriday, nextFriday, subDays } from 'date-fns'; import { BigqueryOrdemPagamentoDTO } from '../dtos/bigquery-ordem-pagamento.dto'; import { BigqueryOrdemPagamentoRepository } from '../repositories/bigquery-ordem-pagamento.repository'; +import { CustomLogger } from 'src/utils/custom-logger'; @Injectable() export class BigqueryOrdemPagamentoService { - private logger: Logger = new Logger('BigqueryOrdemPagamentoService', { - timestamp: true, - }); + private logger = new CustomLogger('BigqueryOrdemPagamentoService', { timestamp: true }); - constructor( - private readonly bigqueryOrdemPagamentoRepository: BigqueryOrdemPagamentoRepository, - ) {} + constructor(private readonly bigqueryOrdemPagamentoRepository: BigqueryOrdemPagamentoRepository) {} /** * Get data from current payment week (qui-qua). Also with older days. */ - public async getFromWeek( - dataOrdemInicial: Date, - dataOrdemFinal: Date, - daysBefore = 0, - ): Promise { + public async getFromWeek(dataOrdemInicial: Date, dataOrdemFinal: Date, daysBefore = 0): Promise { const today = new Date(); let startDate: Date; let endDate: Date; diff --git a/src/bigquery/services/bigquery-transacao.service.ts b/src/bigquery/services/bigquery-transacao.service.ts index d1659aad..bf4515da 100644 --- a/src/bigquery/services/bigquery-transacao.service.ts +++ b/src/bigquery/services/bigquery-transacao.service.ts @@ -1,24 +1,20 @@ -import { Injectable, Logger } from '@nestjs/common'; -import { isFriday, nextFriday, subDays } from 'date-fns'; +import { Injectable } from '@nestjs/common'; +import { CustomLogger } from 'src/utils/custom-logger'; import { BigqueryTransacao } from '../entities/transacao.bigquery-entity'; import { BigqueryTransacaoRepository } from '../repositories/bigquery-transacao.repository'; @Injectable() export class BigqueryTransacaoService { - private logger: Logger = new Logger('BigqueryOrdemPagamentoService', { - timestamp: true, - }); + private logger = new CustomLogger('BigqueryOrdemPagamentoService', { timestamp: true }); - constructor( - private readonly bigqueryTransacaoRepository: BigqueryTransacaoRepository, - ) {} + constructor(private readonly bigqueryTransacaoRepository: BigqueryTransacaoRepository) {} /** * Obter dados da semana de pagamento (qui-qua). * * @param [daysBack=0] Pega a semana atual ou N dias atrás. */ - public async getFromWeek(dataOrdemInicial: Date,dataOrdemFinal: Date,daysBack = 0): Promise { + public async getFromWeek(dataOrdemInicial: Date, dataOrdemFinal: Date, daysBack = 0): Promise { const transacao = ( await this.bigqueryTransacaoRepository.findMany({ startDate: dataOrdemInicial, @@ -33,23 +29,17 @@ export class BigqueryTransacaoService { */ public async getAll(): Promise { // Read - const ordemPgto = (await this.bigqueryTransacaoRepository.findMany()).map( - (i) => ({ ...i } as BigqueryTransacao), - ); + const ordemPgto = (await this.bigqueryTransacaoRepository.findMany()).map((i) => ({ ...i } as BigqueryTransacao)); return ordemPgto; } /** * A cada 10 dias, de hoje até a dataInicio, pesquisa e chama o callback */ - public async getAllPaginated( - callback: (transacoes: BigqueryTransacao[]) => void, - cpfCnpjs: string[] = [], - ) { - const transacoes: BigqueryTransacao[] = - await this.bigqueryTransacaoRepository.findMany({ - manyCpfCnpj: cpfCnpjs, - }); + public async getAllPaginated(callback: (transacoes: BigqueryTransacao[]) => void, cpfCnpjs: string[] = []) { + const transacoes: BigqueryTransacao[] = await this.bigqueryTransacaoRepository.findMany({ + manyCpfCnpj: cpfCnpjs, + }); callback(transacoes); } } diff --git a/src/cnab/cnab.service.ts b/src/cnab/cnab.service.ts index e34db679..46f09043 100644 --- a/src/cnab/cnab.service.ts +++ b/src/cnab/cnab.service.ts @@ -56,7 +56,7 @@ import { parseCnab240Extrato, parseCnab240Pagamento, stringifyCnab104File } from */ @Injectable() export class CnabService { - private logger: CustomLogger = new CustomLogger(CnabService.name, { timestamp: true }); + private logger = new CustomLogger(CnabService.name, { timestamp: true }); constructor( private arquivoPublicacaoService: ArquivoPublicacaoService, // @@ -546,9 +546,6 @@ export class CnabService { const durationItem = formatDateInterval(new Date(), startDateItem); this.logger.log(`CNAB '${cnabName}' lido com sucesso - ${durationItem}`); success.push(cnabName); - const cnab = await this.sftpService.getFirstCnabRetorno(folder); - cnabString = cnab.cnabString; - cnabName = cnab.cnabName; } catch (error) { const durationItem = formatDateInterval(new Date(), startDateItem); this.logger.error(`Erro ao processar CNAB retorno (${durationItem}), movendo para backup de erros e finalizando... - ${error}`, error.stack, METHOD); @@ -559,9 +556,12 @@ export class CnabService { await this.sftpService.moveToBackup(cnabName, SftpBackupFolder.RetornoFailure, cnabString, folder); failed.push(cnabName); } + const cnab = await this.sftpService.getFirstCnabRetorno(folder); + cnabString = cnab.cnabString; + cnabName = cnab.cnabName; } const duration = formatDateInterval(new Date(), startDate); - this.logger.log('Leitura de retornos finalizada com sucesso.', METHOD); + this.logger.log(`Leitura de retornos finalizada com sucesso - ${duration}`, METHOD); return { duration, cnabs: cnabs.length, success, failed }; } diff --git a/src/cnab/repository/arquivo-publicacao.repository.ts b/src/cnab/repository/arquivo-publicacao.repository.ts index 8fb2d84f..ee6bb405 100644 --- a/src/cnab/repository/arquivo-publicacao.repository.ts +++ b/src/cnab/repository/arquivo-publicacao.repository.ts @@ -1,11 +1,11 @@ -import { Injectable, Logger } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; +import { CustomLogger } from 'src/utils/custom-logger'; import { Between, DeepPartial, FindManyOptions, In, InsertResult, Repository } from 'typeorm'; import { ArquivoPublicacaoResultDTO } from '../dto/arquivo-publicacao-result.dto'; import { ArquivoPublicacao } from '../entity/arquivo-publicacao.entity'; import { DetalheA } from '../entity/pagamento/detalhe-a.entity'; import { DetalheAService } from '../service/pagamento/detalhe-a.service'; -import { CommonHttpException } from 'src/utils/http-exception/common-http-exception'; export interface IArquivoPublicacaoRawWhere { id?: number[]; @@ -14,7 +14,7 @@ export interface IArquivoPublicacaoRawWhere { @Injectable() export class ArquivoPublicacaoRepository { - private logger: Logger = new Logger('ArquivoPublicacaoRepository', { timestamp: true }); + private logger = new CustomLogger('ArquivoPublicacaoRepository', { timestamp: true }); constructor( @InjectRepository(ArquivoPublicacao) @@ -92,6 +92,7 @@ export class ArquivoPublicacaoRepository { INNER JOIN item_transacao it ON it.id = ap."itemTransacaoId" INNER JOIN item_transacao_agrupado ita ON ita.id = it."itemTransacaoAgrupadoId" ${qWhere.query} + ORDER BY ap.id `, qWhere.params, ); diff --git a/src/cnab/repository/cliente-favorecido.repository.ts b/src/cnab/repository/cliente-favorecido.repository.ts index 40f3e37f..1c236619 100644 --- a/src/cnab/repository/cliente-favorecido.repository.ts +++ b/src/cnab/repository/cliente-favorecido.repository.ts @@ -1,4 +1,4 @@ -import { Injectable, Logger } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { CustomLogger } from 'src/utils/custom-logger'; import { CommonHttpException } from 'src/utils/http-exception/common-http-exception'; @@ -16,9 +16,7 @@ export interface IClienteFavorecidoRawWhere { @Injectable() export class ClienteFavorecidoRepository { - private logger: Logger = new CustomLogger(ClienteFavorecidoRepository.name, { - timestamp: true, - }); + private logger = new CustomLogger(ClienteFavorecidoRepository.name, { timestamp: true }); constructor( @InjectRepository(ClienteFavorecido) @@ -136,7 +134,7 @@ export class ClienteFavorecidoRepository { qWhere.query = `WHERE cf."cpfCnpj" = $1`; qWhere.params = [where.cpfCnpj]; } else if (where.nome) { - const nomes = where.nome.map(n => `'%${n}%'`) + const nomes = where.nome.map((n) => `'%${n}%'`); qWhere.query = `WHERE cf.nome ILIKE ANY(ARRAY[${nomes.join(',')}])`; qWhere.params = []; } @@ -145,6 +143,7 @@ export class ClienteFavorecidoRepository { SELECT cf.* FROM cliente_favorecido cf ${qWhere.query} + ORDER BY cf.id `, qWhere.params, ); diff --git a/src/cnab/repository/extrato/extrato-detalhe-e.repository.ts b/src/cnab/repository/extrato/extrato-detalhe-e.repository.ts index 212aedf3..fc63936a 100644 --- a/src/cnab/repository/extrato/extrato-detalhe-e.repository.ts +++ b/src/cnab/repository/extrato/extrato-detalhe-e.repository.ts @@ -1,7 +1,8 @@ -import { Injectable, Logger } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { endOfDay, startOfDay } from 'date-fns'; import { ExtratoDetalheE } from 'src/cnab/entity/extrato/extrato-detalhe-e.entity'; +import { CustomLogger } from 'src/utils/custom-logger'; import { asNumber } from 'src/utils/pipe-utils'; import { Nullable } from 'src/utils/types/nullable.type'; import { SaveIfNotExists } from 'src/utils/types/save-if-not-exists.type'; @@ -9,32 +10,27 @@ import { DeepPartial, FindManyOptions, FindOneOptions, LessThanOrEqual, MoreThan @Injectable() export class ExtratoDetalheERepository { - private logger: Logger = new Logger('ExtratoDetalheERepository', { - timestamp: true, - }); + private logger = new CustomLogger('ExtratoDetalheERepository', { timestamp: true }); constructor( @InjectRepository(ExtratoDetalheE) private extDetalheERepository: Repository, - ) { } + ) {} - public async saveIfNotExists(obj: DeepPartial, updateIfExists?: boolean - ): Promise> { + public async saveIfNotExists(obj: DeepPartial, updateIfExists?: boolean): Promise> { const existing = obj?.id ? await this.extDetalheERepository.findOne({ where: { id: obj.id } }) : await this.extDetalheERepository.findOne({ - where: { - extratoHeaderLote: { id: asNumber(obj.extratoHeaderLote?.id) }, - nsr: asNumber(obj.nsr), - } - }); - const item = !existing || (existing && updateIfExists) - ? await this.extDetalheERepository.save(obj) - : existing; + where: { + extratoHeaderLote: { id: asNumber(obj.extratoHeaderLote?.id) }, + nsr: asNumber(obj.nsr), + }, + }); + const item = !existing || (existing && updateIfExists) ? await this.extDetalheERepository.save(obj) : existing; return { isNewItem: !Boolean(existing), item: item, - } + }; } public async save(obj: DeepPartial): Promise { @@ -53,15 +49,14 @@ export class ExtratoDetalheERepository { /** * Obtém o próximo'Número Documento Atribuído pela Empresa' para o ExtratoDetalheE. - * + * * Baseado no mesmo dia. */ public async getNextNumeroDocumento(date: Date): Promise { - return await this.extDetalheERepository.count({ - where: [ - { createdAt: MoreThanOrEqual(startOfDay(date)) }, - { createdAt: LessThanOrEqual(endOfDay(date)) }, - ] - }) + 1; + return ( + (await this.extDetalheERepository.count({ + where: [{ createdAt: MoreThanOrEqual(startOfDay(date)) }, { createdAt: LessThanOrEqual(endOfDay(date)) }], + })) + 1 + ); } } diff --git a/src/cnab/repository/ocorrencia.repository.ts b/src/cnab/repository/ocorrencia.repository.ts index ed887e6d..8a5f5251 100644 --- a/src/cnab/repository/ocorrencia.repository.ts +++ b/src/cnab/repository/ocorrencia.repository.ts @@ -1,15 +1,14 @@ -import { Injectable, Logger } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Ocorrencia } from 'src/cnab/entity/pagamento/ocorrencia.entity'; +import { CustomLogger } from 'src/utils/custom-logger'; import { EntityCondition } from 'src/utils/types/entity-condition.type'; import { Nullable } from 'src/utils/types/nullable.type'; import { DeepPartial, FindManyOptions, Repository } from 'typeorm'; @Injectable() export class OcorrenciaRepository { - private logger: Logger = new Logger(OcorrenciaRepository.name, { - timestamp: true, - }); + private logger = new CustomLogger(OcorrenciaRepository.name, { timestamp: true }); constructor( @InjectRepository(Ocorrencia) @@ -25,17 +24,13 @@ export class OcorrenciaRepository { return await this.ocorrenciaRepository.insert(ocorrencias); } - public async findOne( - fields: EntityCondition, - ): Promise> { + public async findOne(fields: EntityCondition): Promise> { return await this.ocorrenciaRepository.findOne({ where: fields, }); } - public async findMany( - options: FindManyOptions, - ): Promise { + public async findMany(options: FindManyOptions): Promise { return await this.ocorrenciaRepository.find(options); } diff --git a/src/cnab/repository/pagamento/detalhe-a.repository.ts b/src/cnab/repository/pagamento/detalhe-a.repository.ts index 7487e5b5..4df29ec6 100644 --- a/src/cnab/repository/pagamento/detalhe-a.repository.ts +++ b/src/cnab/repository/pagamento/detalhe-a.repository.ts @@ -70,12 +70,13 @@ export class DetalheARepository { return this.detalheARepository.insert(dtos); } - public async save(dto: DeepPartial): Promise { + public async save(dto: DeepPartial, findSaved?: boolean): Promise { const saved = await this.detalheARepository.save(dto); - // return await this.detalheARepository.findOneOrFail({ - // where: { id: saved.id }, - // }); - return await this.getOneRaw({ id: [saved.id] }); + if (findSaved) { + return await this.getOneRaw({ id: [saved.id] }); + } else { + return new DetalheA({...dto, id: saved.id}); + } } public async getOne(fields: EntityCondition): Promise { @@ -84,7 +85,7 @@ export class DetalheARepository { }); } - public async getOneRaw(where: IDetalheARawWhere): Promise { + public async findRaw(where: IDetalheARawWhere): Promise { const qWhere: { query: string; params?: any[] } = { query: '' }; if (where.id) { qWhere.query = `WHERE da.id IN (${where.id.join(',')})`; @@ -121,14 +122,30 @@ export class DetalheARepository { INNER JOIN transacao_agrupado ta ON ta.id = ita."transacaoAgrupadoId" INNER JOIN transacao_status ts ON ts.id = ta."statusId" ${qWhere.query} + ORDER BY da.id `, qWhere.params, ); + const detalhes = result.map((i) => new DetalheA(i)); + return detalhes; + } + + public async findOneRaw(where: IDetalheARawWhere): Promise { + const result = await this.findRaw(where); if (result.length == 0) { - throw CommonHttpException.details('It should return at least one DetalheA'); + return null; + } else { + return result[0]; + } + } + + public async getOneRaw(where: IDetalheARawWhere): Promise { + const result = await this.findRaw(where); + if (result.length == 0) { + throw CommonHttpException.details('It should return at least one DetalheA'); + } else { + return result[0]; } - const detalhes = result.map((i) => new DetalheA(i)); - return detalhes[0]; } public async findOne(options: FindOneOptions): Promise> { diff --git a/src/cnab/service/pagamento/detalhe-a.service.ts b/src/cnab/service/pagamento/detalhe-a.service.ts index 462b5621..0ac298b6 100644 --- a/src/cnab/service/pagamento/detalhe-a.service.ts +++ b/src/cnab/service/pagamento/detalhe-a.service.ts @@ -1,5 +1,5 @@ import { PagamentosPendentes } from './../../entity/pagamento/pagamentos-pendentes.entity'; -import { Injectable, Logger } from '@nestjs/common'; +import { HttpException, Injectable, Logger } from '@nestjs/common'; import { CnabRegistros104Pgto } from 'src/cnab/interfaces/cnab-240/104/pagamento/cnab-registros-104-pgto.interface'; import { EntityCondition } from 'src/utils/types/entity-condition.type'; import { Nullable } from 'src/utils/types/nullable.type'; @@ -16,10 +16,11 @@ import { TransacaoAgrupadoService } from './transacao-agrupado.service'; import { PagamentosPendentesService } from './pagamentos-pendentes.service'; import { TransacaoStatusEnum } from 'src/cnab/enums/pagamento/transacao-status.enum'; import { TransacaoStatus } from 'src/cnab/entity/pagamento/transacao-status.entity'; +import { CustomLogger } from 'src/utils/custom-logger'; @Injectable() export class DetalheAService { - private logger: Logger = new Logger('DetalheAService', { timestamp: true }); + private logger = new CustomLogger('DetalheAService', { timestamp: true }); constructor(private detalheARepository: DetalheARepository, private clienteFavorecidoService: ClienteFavorecidoService, private transacaoAgrupadoService: TransacaoAgrupadoService, private pagamentosPendentesService: PagamentosPendentesService) {} @@ -45,26 +46,28 @@ export class DetalheAService { } public async saveRetornoFrom104(headerArq: CnabHeaderArquivo104, headerLotePgto: CnabHeaderLote104Pgto, r: CnabRegistros104Pgto, dataEfetivacao: Date): Promise { + const METHOD = 'saveRetornoFrom104'; + const logRegistro = `HeaderArquivo: ${headerArq.nsa.convertedValue}, lote: ${headerLotePgto.codigoRegistro.value}`; const favorecido = await this.clienteFavorecidoService.findOneRaw({ nome: [r.detalheA.nomeTerceiro.stringValue.trim()], }); if (!favorecido) { + this.logger.warn(logRegistro + `- Favorecido não encontrado para o nome: '${r.detalheA.nomeTerceiro.stringValue.trim()}'`); return null; } const dataVencimento = startOfDay(r.detalheA.dataVencimento.convertedValue); - let detalheARem; - try { - detalheARem = await this.detalheARepository.getOneRaw({ - detalheARem: { - dataVencimento: dataVencimento, - numeroDocumentoEmpresa: r.detalheA.numeroDocumentoEmpresa.convertedValue, - valorLancamento: r.detalheA.valorLancamento.convertedValue, - }, - }); - if (detalheARem.ocorrenciasCnab === undefined || detalheARem.ocorrenciasCnab === '' || detalheARem.ocorrenciasCnab !== r.detalheA.ocorrencias.value.trim()) { - const detalheA = new DetalheADTO({ - id: detalheARem.id, + const detalheA = await this.detalheARepository.findOneRaw({ + detalheARem: { + dataVencimento: dataVencimento, + numeroDocumentoEmpresa: r.detalheA.numeroDocumentoEmpresa.convertedValue, + valorLancamento: r.detalheA.valorLancamento.convertedValue, + }, + }); + if (detalheA) { + if (detalheA.ocorrenciasCnab === undefined || detalheA.ocorrenciasCnab === '' || detalheA.ocorrenciasCnab !== r.detalheA.ocorrencias.value.trim()) { + const saveDetalheA = new DetalheADTO({ + id: detalheA.id, loteServico: Number(r.detalheA.loteServico.value), finalidadeDOC: r.detalheA.finalidadeDOC.value, numeroDocumentoEmpresa: Number(r.detalheA.numeroDocumentoEmpresa.value), @@ -83,11 +86,10 @@ export class DetalheAService { nsr: Number(r.detalheA.nsr.value), ocorrenciasCnab: r.detalheA.ocorrencias.value.trim() || headerLotePgto.ocorrencias.value.trim() || headerArq.ocorrenciaCobrancaSemPapel.value.trim(), }); - return await this.detalheARepository.save(detalheA); + return await this.detalheARepository.save(saveDetalheA); } - } catch (err) { - this.logger.error(err); - this.logger.debug(`Detalhe não encontrado para o favorecido: `, favorecido?.nome); + } else { + this.logger.warn(logRegistro + ` Detalhe A Documento: ${r.detalheA.numeroDocumentoEmpresa.convertedValue}, favorecido: '${favorecido.nome}' - NÃO ENCONTRADO!`); } if (r.detalheA.ocorrencias !== undefined && r.detalheA.ocorrencias.value.trim() !== '' && r.detalheA.ocorrencias.value.trim() !== 'BD' && r.detalheA.ocorrencias.value.trim() !== '00') { const pg = await this.pagamentosPendentesService.findOne({ @@ -106,7 +108,7 @@ export class DetalheAService { await this.pagamentosPendentesService.save(pagamentosPendentes); } } - return detalheARem; + return detalheA; } public async save(dto: DetalheADTO): Promise { diff --git a/src/cnab/service/pagamento/remessa-retorno.service.ts b/src/cnab/service/pagamento/remessa-retorno.service.ts index bb0d5e48..99f31034 100644 --- a/src/cnab/service/pagamento/remessa-retorno.service.ts +++ b/src/cnab/service/pagamento/remessa-retorno.service.ts @@ -1,6 +1,6 @@ import { HeaderArquivo } from 'src/cnab/entity/pagamento/header-arquivo.entity'; -import { Injectable, Logger } from '@nestjs/common'; +import { HttpException, Injectable, Logger, NotFoundException } from '@nestjs/common'; import { endOfDay, isFriday, nextFriday, startOfDay, subDays } from 'date-fns'; import { DetalheADTO } from 'src/cnab/dto/pagamento/detalhe-a.dto'; import { HeaderLoteDTO } from 'src/cnab/dto/pagamento/header-lote.dto'; @@ -49,7 +49,7 @@ const PgtoRegistros = Cnab104PgtoTemplates.file104.registros; @Injectable() export class RemessaRetornoService { - private logger: Logger = new CustomLogger('CnabPagamentoService', { + private logger = new CustomLogger('CnabPagamentoService', { timestamp: true, }); @@ -447,20 +447,21 @@ export class RemessaRetornoService { } public async saveRetorno(cnab: CnabFile104Pgto) { + const detalhesANaoEncontrados: any[] = []; const dataEfetivacao = new Date(); let detalheAUpdated: DetalheA | null = null; for (const cnabLote of cnab.lotes) { for (const registro of cnabLote.registros) { - this.logger.debug(`Header Arquivo NSA: ` + cnab.headerArquivo.nsa.value); - - this.logger.debug(`Header lote : ` + cnabLote.headerLote.codigoRegistro.value); - + const logRegistro = `HeaderArquivo: ${cnab.headerArquivo.nsa.convertedValue}, lote: ${cnabLote.headerLote.codigoRegistro.value}`; + // Save Detalhes detalheAUpdated = await this.detalheAService.saveRetornoFrom104(cnab.headerArquivo, cnabLote.headerLote, registro, dataEfetivacao); if (!detalheAUpdated) { + const numeroDocumento = registro.detalheA.numeroDocumentoEmpresa.convertedValue; + detalhesANaoEncontrados.push(numeroDocumento) continue; } - this.logger.debug(`Detalhe A Documento: ` + detalheAUpdated.numeroDocumentoEmpresa); + this.logger.debug(logRegistro + ` Detalhe A Documento: ${detalheAUpdated.numeroDocumentoEmpresa}, favorecido: '${registro.detalheA.nomeTerceiro.value}' - OK`); await this.detalheBService.saveFrom104(registro, detalheAUpdated); const queryRunner = this.dataSource.createQueryRunner(); @@ -478,6 +479,9 @@ export class RemessaRetornoService { await this.detalheAService.updateDetalheAStatus(detalheAUpdated); } } + if (detalhesANaoEncontrados) { + throw new NotFoundException(`Os seguintes DetalhesA do Retorno não foram encontrados no Banco (campo: no. documento) - ${detalhesANaoEncontrados.join(',')}`) + } } // region compareRemessaToRetorno diff --git a/src/users/users.service.ts b/src/users/users.service.ts index c9f33792..696eb428 100644 --- a/src/users/users.service.ts +++ b/src/users/users.service.ts @@ -35,14 +35,9 @@ export enum userUploadEnum { @Injectable() export class UsersService { - private logger: CustomLogger = new CustomLogger(UsersService.name, { - timestamp: true, - }); + private logger = new CustomLogger(UsersService.name, { timestamp: true }); - constructor( - private usersRepository: UsersRepository, - private mailHistoryService: MailHistoryService, - ) {} + constructor(private usersRepository: UsersRepository, private mailHistoryService: MailHistoryService) {} async create(createProfileDto: CreateUserDto): Promise { const createdUser = await this.usersRepository.create(createProfileDto); @@ -58,14 +53,8 @@ export class UsersService { return await this.usersRepository.findManyRegisteredUsers(); } - async findManyWithPagination( - paginationOptions: PaginationOptions, - fields?: IFindUserPaginated, - ): Promise { - return await this.usersRepository.findManyWithPagination( - paginationOptions, - fields, - ); + async findManyWithPagination(paginationOptions: PaginationOptions, fields?: IFindUserPaginated): Promise { + return await this.usersRepository.findManyWithPagination(paginationOptions, fields); } async findOne(fields: EntityCondition): Promise> { @@ -78,18 +67,8 @@ export class UsersService { * @param requestUser Who is updating this data. Used to log properly. * @returns Updated user */ - async update( - id: number, - dataToUpdate: DeepPartial, - logContext?: string, - requestUser?: DeepPartial, - ): Promise { - return this.usersRepository.update( - id, - dataToUpdate, - logContext, - requestUser, - ); + async update(id: number, dataToUpdate: DeepPartial, logContext?: string, requestUser?: DeepPartial): Promise { + return this.usersRepository.update(id, dataToUpdate, logContext, requestUser); } /** @@ -121,19 +100,12 @@ export class UsersService { // #region createFromFile - async createFromFile( - file: Express.Multer.File, - requestUser?: DeepPartial, - ): Promise { + async createFromFile(file: Express.Multer.File, requestUser?: DeepPartial): Promise { const reqUser = new User(requestUser); const worksheet = this.getWorksheetFromFile(file); const fileUsers = await this.getUserFilesFromWorksheet(worksheet); - const invalidUsers = fileUsers.filter( - (i) => Object.keys(i.errors).length > 0, - ); - const validUsers = fileUsers.filter( - (i) => Object.keys(i.errors).length === 0, - ); + const invalidUsers = fileUsers.filter((i) => Object.keys(i.errors).length > 0); + const validUsers = fileUsers.filter((i) => Object.keys(i.errors).length === 0); if (invalidUsers.length > 0) { throw new HttpException( @@ -156,10 +128,7 @@ export class UsersService { for (const fileUser of validUsers) { const hash = await this.mailHistoryService.generateHash(); const createdUser = await this.usersRepository.create({ - permitCode: String(fileUser.user.codigo_permissionario).replace( - "'", - '', - ), + permitCode: String(fileUser.user.codigo_permissionario).replace("'", ''), email: fileUser.user.email, phone: fileUser.user.telefone, fullName: parseStringUpperUnaccent(fileUser.user.nome as string), @@ -168,10 +137,7 @@ export class UsersService { status: new Status(StatusEnum.register), role: new Role(RoleEnum.user), } as DeepPartial); - this.logger.log( - `Usuario: ${createdUser.getLogInfo()} criado.`, - 'createFromFile()', - ); + this.logger.log(`Usuario: ${createdUser.getLogInfo()} criado.`, 'createFromFile()'); await this.mailHistoryService.create( { @@ -237,33 +203,23 @@ export class UsersService { const sheetName = workbook.SheetNames[0]; worksheet = workbook.Sheets[sheetName]; } catch (error) { - throw new HttpException( - `Error parsing file`, - HttpStatus.INTERNAL_SERVER_ERROR, - ); + throw new HttpException(`Error parsing file`, HttpStatus.INTERNAL_SERVER_ERROR); } return worksheet; } - async getUserFilesFromWorksheet( - worksheet: xlsx.WorkSheet, - ): Promise { + async getUserFilesFromWorksheet(worksheet: xlsx.WorkSheet): Promise { this.validateFileHeaders(worksheet); const fileData = xlsx.utils.sheet_to_json(worksheet); - const fileUsers: IFileUser[] = fileData.map( - (item: Partial) => ({ - user: item, - errors: {}, - }), - ); + const fileUsers: IFileUser[] = fileData.map((item: Partial) => ({ + user: item, + errors: {}, + })); let row = 2; for (let i = 0; i < fileUsers.length; i++) { const fileUser = fileUsers[i]; - const errorDictionary = await this.validateFileValues( - fileUser, - fileUsers, - ); + const errorDictionary = await this.validateFileValues(fileUser, fileUsers); fileUsers[i] = { row: row, ...fileUser, @@ -300,10 +256,7 @@ export class UsersService { } } - async validateFileValues( - userFile: IFileUser, - fileUsers: IFileUser[], - ): Promise { + async validateFileValues(userFile: IFileUser, fileUsers: IFileUser[]): Promise { const schema = plainToClass(CreateUserFileDto, userFile.user); const errors = await validate(schema as Record, { stopAtFirstError: false, @@ -319,26 +272,14 @@ export class UsersService { const fields = ['email', 'permitCode', 'cpfCnpj']; // If has another user in DB with same email OR permitCode OR cpfCnpj - if ( - userFile.user.email || - userFile.user.codigo_permissionario || - userFile.user.cpf - ) { + if (userFile.user.email || userFile.user.codigo_permissionario || userFile.user.cpf) { const dbFoundUsers = await this.findMany({ - where: [ - ...(userFile.user.email ? [{ email: userFile.user.email }] : []), - ...(userFile.user.codigo_permissionario - ? [{ permitCode: userFile.user.codigo_permissionario }] - : []), - ...(userFile.user.cpf ? [{ cpfCnpj: userFile.user.cpf }] : []), - ], + where: [...(userFile.user.email ? [{ email: userFile.user.email }] : []), ...(userFile.user.codigo_permissionario ? [{ permitCode: userFile.user.codigo_permissionario }] : []), ...(userFile.user.cpf ? [{ cpfCnpj: userFile.user.cpf }] : [])], }); if (dbFoundUsers.length > 0) { for (const dbField of fields) { const dtoField = FileUserMap[dbField]; - if ( - dbFoundUsers.find((i) => i[dbField] === userFile.user[dtoField]) - ) { + if (dbFoundUsers.find((i) => i[dbField] === userFile.user[dtoField])) { if (!errorDictionary.hasOwnProperty(dtoField)) { errorDictionary[dtoField] = ''; } @@ -352,18 +293,11 @@ export class UsersService { } // If has another user in upload with same email OR permitCode OR cpfCnpj - const existingFileUser = fileUsers.filter( - (i) => - i.user.email === userFile.user.email || - i.user.codigo_permissionario === userFile.user.codigo_permissionario || - i.user.cpf === userFile.user.cpf, - ); + const existingFileUser = fileUsers.filter((i) => i.user.email === userFile.user.email || i.user.codigo_permissionario === userFile.user.codigo_permissionario || i.user.cpf === userFile.user.cpf); if (existingFileUser.length > 1) { for (const dbField of fields) { const dtoField = FileUserMap[dbField]; - const existingFUserByField = existingFileUser.filter( - (i) => i.user[dbField] === userFile.user[dbField], - ); + const existingFUserByField = existingFileUser.filter((i) => i.user[dbField] === userFile.user[dbField]); if (existingFUserByField.length > 1) { if (!errorDictionary.hasOwnProperty(dtoField)) { errorDictionary[dtoField] = ''; diff --git a/src/utils/custom-logger.ts b/src/utils/custom-logger.ts index cf3f6cf8..885b709e 100644 --- a/src/utils/custom-logger.ts +++ b/src/utils/custom-logger.ts @@ -61,9 +61,7 @@ export class CustomLogger extends Logger { } private getContext(isLocal: boolean, context?: string) { - if (!context) { - return ''; - } + const contextStr = this.color('warn', `[${context}]`); if (isLocal) { const thisContext = this.color('warn', `[${this.context}]`); @@ -93,10 +91,11 @@ export class CustomLogger extends Logger { } error(message: string, stack?: string, context?: string): void { + const _message = (message as any)?.response?.details?.message || message; if (this.isLocal()) { - console.log(this.formatMessage(message, 'ERROR', context, stack)); + console.log(this.formatMessage(_message, 'ERROR', context, stack)); } else { - super.error(this.formatMessage(message, 'ERROR', context, stack)); + super.error(this.formatMessage(_message, 'ERROR', context, stack)); } } From 8c4f7aa864d032f7390a0984e725b28d277bb4cc Mon Sep 17 00:00:00 2001 From: Raphael Rivas Date: Wed, 21 Aug 2024 15:20:29 -0300 Subject: [PATCH 07/11] fix: after save load DetalheA --- src/cnab/repository/pagamento/detalhe-a.repository.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/cnab/repository/pagamento/detalhe-a.repository.ts b/src/cnab/repository/pagamento/detalhe-a.repository.ts index 4df29ec6..5c2a1eec 100644 --- a/src/cnab/repository/pagamento/detalhe-a.repository.ts +++ b/src/cnab/repository/pagamento/detalhe-a.repository.ts @@ -70,13 +70,9 @@ export class DetalheARepository { return this.detalheARepository.insert(dtos); } - public async save(dto: DeepPartial, findSaved?: boolean): Promise { + public async save(dto: DeepPartial): Promise { const saved = await this.detalheARepository.save(dto); - if (findSaved) { - return await this.getOneRaw({ id: [saved.id] }); - } else { - return new DetalheA({...dto, id: saved.id}); - } + return await this.getOneRaw({ id: [saved.id] }); } public async getOne(fields: EntityCondition): Promise { From 0ccaa329c51c4d461d59b493e7f7241f95a35766 Mon Sep 17 00:00:00 2001 From: Raphael Rivas Date: Thu, 22 Aug 2024 08:53:52 -0300 Subject: [PATCH 08/11] fix: pollDb and mail report --- src/cron-jobs/cron-jobs.service.ts | 44 ++++++++++++++++-------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/src/cron-jobs/cron-jobs.service.ts b/src/cron-jobs/cron-jobs.service.ts index 33e7f860..001c9997 100644 --- a/src/cron-jobs/cron-jobs.service.ts +++ b/src/cron-jobs/cron-jobs.service.ts @@ -24,7 +24,7 @@ import { validateEmail } from 'validations-br'; /** * Enum CronJobServicesJobs */ -export enum CrobJobsEnum { +export enum CronJobsEnum { bulkSendInvites = 'bulkSendInvites', sendStatusReport = 'sendStatusReport', sendStatusReportTemp = 'sendStatusReportTemp', @@ -50,7 +50,7 @@ interface ICronJob { interface ICronJobSetting { setting: ISettingData; - cronJob: CrobJobsEnum; + cronJob: CronJobsEnum; isEnabledFlag?: ISettingData; } @@ -76,22 +76,31 @@ export class CronJobsService { this.jobsConfig.push( { - name: CrobJobsEnum.bulkSendInvites, + /** NÃO REMOVER ESTE JOB, É ÚTIL PARA ALTERAR OS CRONJOBS EM CASO DE URGÊNCIA */ + name: CronJobsEnum.pollDb, + cronJobParameters: { + // cronjob: * * * * - A cada minuto + cronTime: (await this.settingsService.getOneBySettingData(appSettings.any__poll_db_cronjob, true, THIS_CLASS_WITH_METHOD)).getValueAsString(), + onTick: async () => this.pollDb(), + }, + }, + { + name: CronJobsEnum.bulkSendInvites, cronJobParameters: { cronTime: (await this.settingsService.getOneBySettingData(appSettings.any__mail_invite_cronjob, true, THIS_CLASS_WITH_METHOD)).getValueAsString(), onTick: async () => this.bulkSendInvites(), }, }, - /** NÃO DESABILITAR ENVIO DE REPORT */ { - name: CrobJobsEnum.sendStatusReport, + /** NÃO DESABILITAR ENVIO DE REPORT */ + name: CronJobsEnum.sendStatusReport, cronJobParameters: { cronTime: (await this.settingsService.getOneBySettingData(appSettings.any__mail_report_cronjob, true, THIS_CLASS_WITH_METHOD)).getValueAsString(), onTick: () => this.sendStatusReport(), }, }, { - name: CrobJobsEnum.bulkResendInvites, + name: CronJobsEnum.bulkResendInvites, cronJobParameters: { cronTime: '45 14 15 * *', // Day 15, 14:45 GMT = 11:45 BRT (GMT-3) onTick: async () => { @@ -100,7 +109,7 @@ export class CronJobsService { }, }, { - name: CrobJobsEnum.updateTransacaoViewVan, + name: CronJobsEnum.updateTransacaoViewVan, cronJobParameters: { cronTime: '*/30 * * * *', // Every 30 min onTick: async () => { @@ -109,7 +118,7 @@ export class CronJobsService { }, }, { - name: CrobJobsEnum.updateTransacaoViewEmpresa, + name: CronJobsEnum.updateTransacaoViewEmpresa, cronJobParameters: { cronTime: '0 9 * * *', // Every day, 06:00 GMT = 09:00 BRT (GMT-3) onTick: async () => { @@ -118,7 +127,7 @@ export class CronJobsService { }, }, { - name: CrobJobsEnum.updateTransacaoViewVLT, + name: CronJobsEnum.updateTransacaoViewVLT, cronJobParameters: { cronTime: '0 9 * * *', // Every day, 06:00 GMT = 09:00 BRT (GMT-3) onTick: async () => { @@ -127,7 +136,7 @@ export class CronJobsService { }, }, { - name: CrobJobsEnum.syncTransacaoViewOrdemPgto, + name: CronJobsEnum.syncTransacaoViewOrdemPgto, cronJobParameters: { cronTime: '0 9 * * *', // Every day, 06:00 GMT = 09:00 BRT (GMT-3) onTick: async () => { @@ -136,7 +145,7 @@ export class CronJobsService { }, }, { - name: CrobJobsEnum.updateRetorno, + name: CronJobsEnum.updateRetorno, cronJobParameters: { cronTime: '*/30 * * * *', // Every 30 min onTick: async () => { @@ -559,7 +568,6 @@ export class CronJobsService { return; } if (!settingPollDbActive.getValueAsBoolean()) { - this.logger.log(`Tarefa cancelada pois setting.${appSettings.any__poll_db_enabled.name}' = 'false'` + ` Para ativar, altere na tabela 'setting'`, METHOD); return; } @@ -568,15 +576,15 @@ export class CronJobsService { const cronjobSettings: ICronJobSetting[] = [ { setting: appSettings.any__poll_db_cronjob, - cronJob: CrobJobsEnum.pollDb, + cronJob: CronJobsEnum.pollDb, }, { setting: appSettings.any__mail_invite_cronjob, - cronJob: CrobJobsEnum.bulkSendInvites, + cronJob: CronJobsEnum.bulkSendInvites, }, { setting: appSettings.any__mail_report_cronjob, - cronJob: CrobJobsEnum.sendStatusReport, + cronJob: CronJobsEnum.sendStatusReport, }, ]; for (const setting of cronjobSettings) { @@ -585,12 +593,6 @@ export class CronJobsService { hasDbChanges = true; } } - - if (hasDbChanges) { - this.logger.log('Tarefa finalizada.', METHOD); - } else { - this.logger.log('Tarefa finalizada, sem alterações no banco.', METHOD); - } } async handleCronjobSettings(args: ICronJobSetting, thisMethod: string): Promise { From cc7e089c6eb3076b26bc38b09930082f56b41d27 Mon Sep 17 00:00:00 2001 From: Raphael Rivas Date: Thu, 22 Aug 2024 09:07:44 -0300 Subject: [PATCH 09/11] fix: sync TransacaoView --- src/cnab/cnab.service.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/cnab/cnab.service.ts b/src/cnab/cnab.service.ts index 46f09043..b27667c1 100644 --- a/src/cnab/cnab.service.ts +++ b/src/cnab/cnab.service.ts @@ -274,7 +274,9 @@ export class CnabService { const startDate = new Date(); let count = 0; try { + await queryRunner.startTransaction(); [, count] = await queryRunner.query(query); + await queryRunner.commitTransaction(); } catch (error) { await queryRunner.rollbackTransaction(); } finally { From 3580ea71782031edc4d07d464cc2c0a9960e6ad5 Mon Sep 17 00:00:00 2001 From: Raphael Rivas Date: Thu, 22 Aug 2024 10:36:42 -0300 Subject: [PATCH 10/11] fix: try-catch for cronjobs - fix: sftp rename if origin/dest is the same --- .../service/pagamento/detalhe-a.service.ts | 3 +- .../pagamento/remessa-retorno.service.ts | 4 +- src/cron-jobs/cron-jobs.service.ts | 465 +++++++++--------- src/settings/settings.service.ts | 4 +- src/sftp/sftp-client/sftp-client.service.ts | 50 +- src/sftp/sftp.service.ts | 2 +- 6 files changed, 259 insertions(+), 269 deletions(-) diff --git a/src/cnab/service/pagamento/detalhe-a.service.ts b/src/cnab/service/pagamento/detalhe-a.service.ts index 0ac298b6..636f6fa8 100644 --- a/src/cnab/service/pagamento/detalhe-a.service.ts +++ b/src/cnab/service/pagamento/detalhe-a.service.ts @@ -46,14 +46,13 @@ export class DetalheAService { } public async saveRetornoFrom104(headerArq: CnabHeaderArquivo104, headerLotePgto: CnabHeaderLote104Pgto, r: CnabRegistros104Pgto, dataEfetivacao: Date): Promise { - const METHOD = 'saveRetornoFrom104'; const logRegistro = `HeaderArquivo: ${headerArq.nsa.convertedValue}, lote: ${headerLotePgto.codigoRegistro.value}`; const favorecido = await this.clienteFavorecidoService.findOneRaw({ nome: [r.detalheA.nomeTerceiro.stringValue.trim()], }); if (!favorecido) { - this.logger.warn(logRegistro + `- Favorecido não encontrado para o nome: '${r.detalheA.nomeTerceiro.stringValue.trim()}'`); + this.logger.warn(logRegistro + ` Detalhe A Documento: ${r.detalheA.numeroDocumentoEmpresa.convertedValue} - Favorecido não encontrado para o nome: '${r.detalheA.nomeTerceiro.stringValue.trim()}'`); return null; } const dataVencimento = startOfDay(r.detalheA.dataVencimento.convertedValue); diff --git a/src/cnab/service/pagamento/remessa-retorno.service.ts b/src/cnab/service/pagamento/remessa-retorno.service.ts index 99f31034..9161f728 100644 --- a/src/cnab/service/pagamento/remessa-retorno.service.ts +++ b/src/cnab/service/pagamento/remessa-retorno.service.ts @@ -461,7 +461,7 @@ export class RemessaRetornoService { detalhesANaoEncontrados.push(numeroDocumento) continue; } - this.logger.debug(logRegistro + ` Detalhe A Documento: ${detalheAUpdated.numeroDocumentoEmpresa}, favorecido: '${registro.detalheA.nomeTerceiro.value}' - OK`); + this.logger.debug(logRegistro + ` Detalhe A Documento: ${detalheAUpdated.numeroDocumentoEmpresa}, favorecido: '${registro.detalheA.nomeTerceiro.value.trim()}' - OK`); await this.detalheBService.saveFrom104(registro, detalheAUpdated); const queryRunner = this.dataSource.createQueryRunner(); @@ -479,7 +479,7 @@ export class RemessaRetornoService { await this.detalheAService.updateDetalheAStatus(detalheAUpdated); } } - if (detalhesANaoEncontrados) { + if (detalhesANaoEncontrados.length > 0) { throw new NotFoundException(`Os seguintes DetalhesA do Retorno não foram encontrados no Banco (campo: no. documento) - ${detalhesANaoEncontrados.join(',')}`) } } diff --git a/src/cron-jobs/cron-jobs.service.ts b/src/cron-jobs/cron-jobs.service.ts index 001c9997..fc5474bd 100644 --- a/src/cron-jobs/cron-jobs.service.ts +++ b/src/cron-jobs/cron-jobs.service.ts @@ -1,8 +1,8 @@ import { HttpStatus, Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; -import { CronExpression, SchedulerRegistry } from '@nestjs/schedule'; +import { SchedulerRegistry } from '@nestjs/schedule'; import { CronJob, CronJobParameters } from 'cron'; -import { addDays, endOfDay, isFriday, isMonday, isSaturday, isSunday, isThursday, isTuesday, startOfDay, subDays, subHours } from 'date-fns'; +import { addDays, endOfDay, isFriday, isMonday, isThursday, isTuesday, startOfDay, subDays, subHours } from 'date-fns'; import { CnabService } from 'src/cnab/cnab.service'; import { PagadorContaEnum } from 'src/cnab/enums/pagamento/pagador.enum'; import { InviteStatus } from 'src/mail-history-statuses/entities/mail-history-status.entity'; @@ -81,14 +81,14 @@ export class CronJobsService { cronJobParameters: { // cronjob: * * * * - A cada minuto cronTime: (await this.settingsService.getOneBySettingData(appSettings.any__poll_db_cronjob, true, THIS_CLASS_WITH_METHOD)).getValueAsString(), - onTick: async () => this.pollDb(), + onTick: async () => await this.pollDb(), }, }, { name: CronJobsEnum.bulkSendInvites, cronJobParameters: { cronTime: (await this.settingsService.getOneBySettingData(appSettings.any__mail_invite_cronjob, true, THIS_CLASS_WITH_METHOD)).getValueAsString(), - onTick: async () => this.bulkSendInvites(), + onTick: async () => await this.bulkSendInvites(), }, }, { @@ -96,61 +96,49 @@ export class CronJobsService { name: CronJobsEnum.sendStatusReport, cronJobParameters: { cronTime: (await this.settingsService.getOneBySettingData(appSettings.any__mail_report_cronjob, true, THIS_CLASS_WITH_METHOD)).getValueAsString(), - onTick: () => this.sendStatusReport(), + onTick: async () => await this.sendStatusReport(), }, }, { name: CronJobsEnum.bulkResendInvites, cronJobParameters: { cronTime: '45 14 15 * *', // Day 15, 14:45 GMT = 11:45 BRT (GMT-3) - onTick: async () => { - await this.bulkResendInvites(); - }, + onTick: async () => await this.bulkResendInvites(), }, }, { name: CronJobsEnum.updateTransacaoViewVan, cronJobParameters: { cronTime: '*/30 * * * *', // Every 30 min - onTick: async () => { - await this.updateTransacaoView('Van'); - }, + onTick: async () => await this.updateTransacaoView('Van'), }, }, { name: CronJobsEnum.updateTransacaoViewEmpresa, cronJobParameters: { cronTime: '0 9 * * *', // Every day, 06:00 GMT = 09:00 BRT (GMT-3) - onTick: async () => { - await this.updateTransacaoView('Empresa'); - }, + onTick: async () => await this.updateTransacaoView('Empresa'), }, }, { name: CronJobsEnum.updateTransacaoViewVLT, cronJobParameters: { cronTime: '0 9 * * *', // Every day, 06:00 GMT = 09:00 BRT (GMT-3) - onTick: async () => { - await this.updateTransacaoView('VLT'); - }, + onTick: async () => await this.updateTransacaoView('VLT'), }, }, { name: CronJobsEnum.syncTransacaoViewOrdemPgto, cronJobParameters: { cronTime: '0 9 * * *', // Every day, 06:00 GMT = 09:00 BRT (GMT-3) - onTick: async () => { - this.syncTransacaoViewOrdemPgto(); - }, + onTick: async () => await this.syncTransacaoViewOrdemPgto(), }, }, { name: CronJobsEnum.updateRetorno, cronJobParameters: { cronTime: '*/30 * * * *', // Every 30 min - onTick: async () => { - await this.updateRetorno(); - }, + onTick: async () => await this.updateRetorno(), }, }, // { @@ -184,9 +172,13 @@ export class CronJobsService { ); /** NÃO COMENTE ISTO, É A GERAÇÃO DE JOBS */ - for (const jobConfig of this.jobsConfig) { - this.startCron(jobConfig); - this.logger.log(`Tarefa agendada: ${jobConfig.name}, ${jobConfig.cronJobParameters.cronTime}`); + if (process.env.CRONJOBS != 'false') { + for (const jobConfig of this.jobsConfig) { + this.startCron(jobConfig); + this.logger.log(`Tarefa agendada: ${jobConfig.name}, ${jobConfig.cronJobParameters.cronTime}`); + } + } else { + this.logger.warn(`env->CRONJOBS = false. Cronjobs inativos.`); } } @@ -315,7 +307,12 @@ export class CronJobsService { } public async syncTransacaoViewOrdemPgto() { - await this.cnabService.sincronizeTransacaoViewOrdemPgto(subDays(new Date(), 1).toString(), new Date().toString()); + const METHOD = 'syncTransacaoViewOrdemPgto'; + try { + await this.cnabService.sincronizeTransacaoViewOrdemPgto(subDays(new Date(), 1).toString(), new Date().toString()); + } catch (error) { + this.logger.error('Erro ao executar tarefa.', error?.stack, METHOD); + } } public async saveAndSendRemessa(dataPgto: Date, isConference = false, isCancelamento = false, nsaInicial = 0, nsaFinal = 0, dataCancelamento = new Date()) { @@ -345,99 +342,71 @@ export class CronJobsService { */ async updateTransacaoView(consorcio: 'Van' | 'Empresa' | 'VLT', debug?: ICronjobDebug) { const METHOD = this.updateTransacaoView.name; - if (!(await this.getIsCnabJobEnabled(METHOD)) && !debug?.force) { - return; - } - const today = debug?.today || new Date(); - let startDate = today; - let endDate = today; - try { - this.logger.log('Iniciando tarefa.', METHOD); - if (consorcio == 'Van') { - startDate = subHours(startDate, 2); - } else if (consorcio == 'VLT') { - startDate = subDays(startDate, 1); - } else { - /** Empresa */ - startDate = startOfDay(startDate); - endDate = endOfDay(endDate); + if (!(await this.getIsCnabJobEnabled(METHOD)) && !debug?.force) { + return; + } + const today = debug?.today || new Date(); + let startDate = today; + let endDate = today; + + try { + this.logger.log('Iniciando tarefa.', METHOD); + if (consorcio == 'Van') { + startDate = subHours(startDate, 2); + } else if (consorcio == 'VLT') { + startDate = subDays(startDate, 1); + } else { + /** Empresa */ + startDate = startOfDay(startDate); + endDate = endOfDay(endDate); + } + await this.cnabService.updateTransacaoViewBigquery(startDate, endDate, 0, consorcio); + this.logger.log('TransacaoViews atualizados com sucesso.', METHOD); + } catch (error) { + this.logger.error(`ERRO CRÍTICO - ${JSON.stringify(error)}`, error?.stack, METHOD); } - await this.cnabService.updateTransacaoViewBigquery(startDate, endDate, 0, consorcio); - this.logger.log('TransacaoViews atualizados com sucesso.', METHOD); } catch (error) { - this.logger.error(`ERRO CRÍTICO - ${JSON.stringify(error)}`, error?.stack, METHOD); + this.logger.error('Erro ao executar tarefa.', error?.stack, METHOD); } } async bulkSendInvites() { const METHOD = this.bulkSendInvites.name; - const activateAutoSendInvite = await this.settingsService.findOneBySettingData(appSettings.any__activate_auto_send_invite); - if (!activateAutoSendInvite) { - this.logger.log(`Tarefa cancelada pois 'setting.${appSettings.any__activate_auto_send_invite.name}' ` + ' não foi encontrado no banco.', METHOD); - return; - } else if (activateAutoSendInvite.getValueAsBoolean() === false) { - this.logger.log(`Tarefa cancelada pois 'setting.${appSettings.any__activate_auto_send_invite.name}' = 'false'.` + ` Para ativar, altere na tabela 'setting'`, METHOD); - return; - } - - // get data - const sentToday = (await this.mailHistoryService.findSentToday()) || []; - const unsent = (await this.mailHistoryService.findUnsent()) || []; - const remainingQuota = await this.mailHistoryService.getRemainingQuota(); - const dailyQuota = () => this.configService.getOrThrow('mail.dailyQuota'); - - this.logger.log(`Iniciando tarefa - a enviar: ${unsent.length},enviado: ${sentToday.length}/${dailyQuota()},falta enviar: ${remainingQuota}`, METHOD); - for (let i = 0; i < remainingQuota && i < unsent.length; i++) { - const invite = new MailHistory(unsent[i]); + try { + const activateAutoSendInvite = await this.settingsService.findOneBySettingData(appSettings.any__activate_auto_send_invite); + if (!activateAutoSendInvite) { + this.logger.log(`Tarefa cancelada pois 'setting.${appSettings.any__activate_auto_send_invite.name}' ` + ' não foi encontrado no banco.', METHOD); + return; + } else if (activateAutoSendInvite.getValueAsBoolean() === false) { + this.logger.log(`Tarefa cancelada pois 'setting.${appSettings.any__activate_auto_send_invite.name}' = 'false'.` + ` Para ativar, altere na tabela 'setting'`, METHOD); + return; + } - const user = await this.usersService.findOne({ id: invite.user.id }); + // get data + const sentToday = (await this.mailHistoryService.findSentToday()) || []; + const unsent = (await this.mailHistoryService.findUnsent()) || []; + const remainingQuota = await this.mailHistoryService.getRemainingQuota(); + const dailyQuota = () => this.configService.getOrThrow('mail.dailyQuota'); - // User mail error - if (!user?.email) { - this.logger.error(`Usuário não tem email válido (${user?.email}), este email não será enviado.`, METHOD); - invite.setInviteError({ - httpErrorCode: HttpStatus.UNPROCESSABLE_ENTITY, - smtpErrorCode: null, - }); - invite.sentAt = null; - invite.failedAt = new Date(Date.now()); - await this.mailHistoryService.update( - invite.id, - { - httpErrorCode: invite.httpErrorCode, - smtpErrorCode: invite.smtpErrorCode, - sentAt: invite.sentAt, - failedAt: invite.failedAt, - }, - METHOD, - ); - continue; - } + this.logger.log(`Iniciando tarefa - a enviar: ${unsent.length},enviado: ${sentToday.length}/${dailyQuota()},falta enviar: ${remainingQuota}`, METHOD); + for (let i = 0; i < remainingQuota && i < unsent.length; i++) { + const invite = new MailHistory(unsent[i]); - // Send mail - try { - const { mailSentInfo } = await this.mailService.sendConcludeRegistration({ - to: user.email, - data: { - hash: invite.hash, - userName: user?.fullName as string, - }, - }); + const user = await this.usersService.findOne({ id: invite.user.id }); - // Success - if (mailSentInfo.success === true) { + // User mail error + if (!user?.email) { + this.logger.error(`Usuário não tem email válido (${user?.email}), este email não será enviado.`, METHOD); invite.setInviteError({ - httpErrorCode: null, + httpErrorCode: HttpStatus.UNPROCESSABLE_ENTITY, smtpErrorCode: null, }); - invite.setInviteStatus(InviteStatusEnum.sent); - invite.sentAt = new Date(Date.now()); - invite.failedAt = null; + invite.sentAt = null; + invite.failedAt = new Date(Date.now()); await this.mailHistoryService.update( invite.id, { - inviteStatus: invite.inviteStatus, httpErrorCode: invite.httpErrorCode, smtpErrorCode: invite.smtpErrorCode, sentAt: invite.sentAt, @@ -445,15 +414,70 @@ export class CronJobsService { }, METHOD, ); - this.logger.log('Email enviado com sucesso.', METHOD); + continue; } - // SMTP error - else { - this.logger.error(`Email enviado retornou erro. - mailSentInfo: ${mailSentInfo}`, new Error().stack, METHOD); + // Send mail + try { + const { mailSentInfo } = await this.mailService.sendConcludeRegistration({ + to: user.email, + data: { + hash: invite.hash, + userName: user?.fullName as string, + }, + }); + + // Success + if (mailSentInfo.success === true) { + invite.setInviteError({ + httpErrorCode: null, + smtpErrorCode: null, + }); + invite.setInviteStatus(InviteStatusEnum.sent); + invite.sentAt = new Date(Date.now()); + invite.failedAt = null; + await this.mailHistoryService.update( + invite.id, + { + inviteStatus: invite.inviteStatus, + httpErrorCode: invite.httpErrorCode, + smtpErrorCode: invite.smtpErrorCode, + sentAt: invite.sentAt, + failedAt: invite.failedAt, + }, + METHOD, + ); + this.logger.log('Email enviado com sucesso.', METHOD); + } + + // SMTP error + else { + this.logger.error(`Email enviado retornou erro. - mailSentInfo: ${mailSentInfo}`, new Error().stack, METHOD); + invite.setInviteError({ + httpErrorCode: HttpStatus.INTERNAL_SERVER_ERROR, + smtpErrorCode: mailSentInfo.response.code, + }); + invite.sentAt = null; + invite.failedAt = new Date(Date.now()); + await this.mailHistoryService.update( + invite.id, + { + httpErrorCode: invite.httpErrorCode, + smtpErrorCode: invite.smtpErrorCode, + sentAt: invite.sentAt, + failedAt: invite.failedAt, + }, + METHOD, + ); + } + + // API error + } catch (httpException) { + this.logger.error('Email falhou ao enviar.', httpException.stack, METHOD); + invite.httpErrorCode = httpException.statusCode; invite.setInviteError({ httpErrorCode: HttpStatus.INTERNAL_SERVER_ERROR, - smtpErrorCode: mailSentInfo.response.code, + smtpErrorCode: null, }); invite.sentAt = null; invite.failedAt = new Date(Date.now()); @@ -468,130 +492,114 @@ export class CronJobsService { METHOD, ); } - - // API error - } catch (httpException) { - this.logger.error('Email falhou ao enviar.', httpException.stack, METHOD); - invite.httpErrorCode = httpException.statusCode; - invite.setInviteError({ - httpErrorCode: HttpStatus.INTERNAL_SERVER_ERROR, - smtpErrorCode: null, - }); - invite.sentAt = null; - invite.failedAt = new Date(Date.now()); - await this.mailHistoryService.update( - invite.id, - { - httpErrorCode: invite.httpErrorCode, - smtpErrorCode: invite.smtpErrorCode, - sentAt: invite.sentAt, - failedAt: invite.failedAt, - }, - METHOD, - ); } - } - if (unsent.length == 0 || remainingQuota == 0) { - const reasons: string[] = [...(unsent.length == 0 ? ['no mails to sent'] : []), ...(remainingQuota == 0 ? ['no remaining quota'] : [])]; - this.logger.log(`Tarefa cancelada pois ${reasons.join(' e ')}`, METHOD); - } else { - this.logger.log('Tarefa finalizada com sucesso.', METHOD); + if (unsent.length == 0 || remainingQuota == 0) { + const reasons: string[] = [...(unsent.length == 0 ? ['no mails to sent'] : []), ...(remainingQuota == 0 ? ['no remaining quota'] : [])]; + this.logger.log(`Tarefa cancelada pois ${reasons.join(' e ')}`, METHOD); + } else { + this.logger.log('Tarefa finalizada com sucesso.', METHOD); + } + } catch (error) { + this.logger.error('Erro ao executar tarefa.', error?.stack, METHOD); } } async sendStatusReport() { const METHOD = this.sendStatusReport.name; - if (!(await this.getIsProd(METHOD))) { - return; - } - this.logger.log('Iniciando tarefa.', METHOD); + try { + if (!(await this.getIsProd(METHOD))) { + return; + } + this.logger.log('Iniciando tarefa.', METHOD); - const isEnabledFlag = await this.settingsService.findOneBySettingData(appSettings.any__mail_report_enabled); - if (!isEnabledFlag) { - this.logger.error(`Tarefa cancelada pois 'setting.${appSettings.any__mail_report_enabled.name}' ` + 'não foi encontrado no banco.', undefined, METHOD); - return; - } else if (isEnabledFlag.getValueAsBoolean() === false) { - this.logger.log(`Tarefa cancelada pois 'setting.${appSettings.any__mail_report_enabled.name}' = 'false'.` + ` Para ativar, altere na tabela 'setting'`, METHOD); - return; - } + const isEnabledFlag = await this.settingsService.findOneBySettingData(appSettings.any__mail_report_enabled); + if (!isEnabledFlag) { + this.logger.error(`Tarefa cancelada pois 'setting.${appSettings.any__mail_report_enabled.name}' ` + 'não foi encontrado no banco.', undefined, METHOD); + return; + } else if (isEnabledFlag.getValueAsBoolean() === false) { + this.logger.log(`Tarefa cancelada pois 'setting.${appSettings.any__mail_report_enabled.name}' = 'false'.` + ` Para ativar, altere na tabela 'setting'`, METHOD); + return; + } - if (!isEnabledFlag) { - this.logger.error(`Tarefa cancelada pois 'setting.${appSettings.any__mail_report_enabled.name}' ` + 'não foi encontrado no banco.', undefined, METHOD); - return; - } else if (isEnabledFlag.getValueAsBoolean() === false) { - this.logger.log(`Tarefa cancelada pois 'setting.${appSettings.any__mail_report_enabled.name}' = 'false'.` + ` Para ativar, altere na tabela 'setting'`, METHOD); - return; - } + if (!isEnabledFlag) { + this.logger.error(`Tarefa cancelada pois 'setting.${appSettings.any__mail_report_enabled.name}' ` + 'não foi encontrado no banco.', undefined, METHOD); + return; + } else if (isEnabledFlag.getValueAsBoolean() === false) { + this.logger.log(`Tarefa cancelada pois 'setting.${appSettings.any__mail_report_enabled.name}' = 'false'.` + ` Para ativar, altere na tabela 'setting'`, METHOD); + return; + } - const mailRecipients = await this.settingsService.findManyBySettingDataGroup(appSettings.any__mail_report_recipient); + const mailRecipients = await this.settingsService.findManyBySettingDataGroup(appSettings.any__mail_report_recipient); - if (!mailRecipients) { - this.logger.error(`Tarefa cancelada pois a configuração 'mail.statusReportRecipients'` + ` não foi encontrada (retornou: ${mailRecipients}).`, undefined, METHOD); - return; - } else if (mailRecipients.some((i) => !validateEmail(i.getValueAsString()))) { - this.logger.error(`Tarefa cancelada pois a configuração 'mail.statusReportRecipients'` + ` não contém uma lista de emails válidos. Retornou: ${mailRecipients}.`, undefined, METHOD); - return; - } + if (!mailRecipients) { + this.logger.error(`Tarefa cancelada pois a configuração 'mail.statusReportRecipients'` + ` não foi encontrada (retornou: ${mailRecipients}).`, undefined, METHOD); + return; + } else if (mailRecipients.some((i) => !validateEmail(i.getValueAsString()))) { + this.logger.error(`Tarefa cancelada pois a configuração 'mail.statusReportRecipients'` + ` não contém uma lista de emails válidos. Retornou: ${mailRecipients}.`, undefined, METHOD); + return; + } - // Send mail - const emails = mailRecipients.reduce((l: string[], i) => [...l, i.getValueAsString()], []); - try { - const mailSentInfo = await this.mailService.sendStatusReport({ - to: emails, - data: { - statusCount: await this.mailHistoryService.getStatusCount(), - }, - } as any); + // Send mail + const emails = mailRecipients.reduce((l: string[], i) => [...l, i.getValueAsString()], []); + try { + const mailSentInfo = await this.mailService.sendStatusReport({ + to: emails, + data: { + statusCount: await this.mailHistoryService.getStatusCount(), + }, + } as any); - // Success - if (mailSentInfo.success === true) { - this.logger.log(`Relatório enviado com sucesso para os emails ${emails}`, METHOD); - } + // Success + if (mailSentInfo.success === true) { + this.logger.log(`Relatório enviado com sucesso para os emails ${emails}`, METHOD); + } - // SMTP error - else { - this.logger.error(`Relatório enviado para os emails ${emails} retornou erro. - ` + `mailSentInfo: ${JSON.stringify(mailSentInfo)}`, new Error().stack, METHOD); - } + // SMTP error + else { + this.logger.error(`Relatório enviado para os emails ${emails} retornou erro. - ` + `mailSentInfo: ${JSON.stringify(mailSentInfo)}`, new Error().stack, METHOD); + } - // API error - } catch (httpException) { - this.logger.error(`Email falhou ao enviar para ${emails}`, httpException?.stack, METHOD); + // API error + } catch (httpException) { + this.logger.error(`Email falhou ao enviar para ${emails}`, httpException?.stack, METHOD); + } + this.logger.log('Tarefa finalizada.', METHOD); + } catch (error) { + this.logger.error('Erro ao executar tarefa.', error?.stack, METHOD); } - this.logger.log('Tarefa finalizada.', METHOD); } async pollDb() { const METHOD = this.pollDb.name; - const settingPollDbActive = await this.settingsService.findOneBySettingData(appSettings.any__poll_db_enabled); - if (!settingPollDbActive) { - this.logger.error(`Tarefa cancelada pois 'setting.${appSettings.any__poll_db_enabled.name}' não foi encontrado no banco.`, new Error().stack, METHOD); - return; - } - if (!settingPollDbActive.getValueAsBoolean()) { - return; - } - - let hasDbChanges = false; + try { + const settingPollDbActive = await this.settingsService.findOneBySettingData(appSettings.any__poll_db_enabled); + if (!settingPollDbActive) { + this.logger.error(`Tarefa cancelada pois 'setting.${appSettings.any__poll_db_enabled.name}' não foi encontrado no banco.`, new Error().stack, METHOD); + return; + } + if (!settingPollDbActive.getValueAsBoolean()) { + return; + } - const cronjobSettings: ICronJobSetting[] = [ - { - setting: appSettings.any__poll_db_cronjob, - cronJob: CronJobsEnum.pollDb, - }, - { - setting: appSettings.any__mail_invite_cronjob, - cronJob: CronJobsEnum.bulkSendInvites, - }, - { - setting: appSettings.any__mail_report_cronjob, - cronJob: CronJobsEnum.sendStatusReport, - }, - ]; - for (const setting of cronjobSettings) { - const dbChanged = await this.handleCronjobSettings(setting, METHOD); - if (dbChanged) { - hasDbChanges = true; + const cronjobSettings: ICronJobSetting[] = [ + { + setting: appSettings.any__poll_db_cronjob, + cronJob: CronJobsEnum.pollDb, + }, + { + setting: appSettings.any__mail_invite_cronjob, + cronJob: CronJobsEnum.bulkSendInvites, + }, + { + setting: appSettings.any__mail_report_cronjob, + cronJob: CronJobsEnum.sendStatusReport, + }, + ]; + for (const setting of cronjobSettings) { + await this.handleCronjobSettings(setting, METHOD); } + } catch (error) { + this.logger.error(`Erro ao executar tarefa.`, error?.stack, METHOD); } } @@ -657,21 +665,26 @@ export class CronJobsService { async bulkResendInvites(): Promise { const METHOD = this.bulkResendInvites.name; - const notRegisteredUsers = await this.usersService.getNotRegisteredUsers(); + try { + const notRegisteredUsers = await this.usersService.getNotRegisteredUsers(); - if (notRegisteredUsers.length === 0) { - this.logger.log('Não há usuários para enviar, abortando...', METHOD); - return HttpStatus.NOT_FOUND; - } - this.logger.log('Enviando emails específicos para ' + `${notRegisteredUsers.length} usuários não totalmente registrados`, METHOD); - for (const user of notRegisteredUsers) { - await this.resendInvite(user, METHOD); + if (notRegisteredUsers.length === 0) { + this.logger.log('Não há usuários para enviar, abortando...', METHOD); + return HttpStatus.NOT_FOUND; + } + this.logger.log('Enviando emails específicos para ' + `${notRegisteredUsers.length} usuários não totalmente registrados`, METHOD); + for (const user of notRegisteredUsers) { + await this.resendInvite(user, METHOD); + } + return HttpStatus.OK; + } catch (error) { + this.logger.error(`Erro ao executar tarefa, abortando. - ${error}`, error?.stack, METHOD); + return HttpStatus.INTERNAL_SERVER_ERROR; } - return HttpStatus.OK; } async resendInvite(user: User, outerMethod: string) { - const THIS_METHOD = `${outerMethod} > ${this.resendInvite.name}`; + const METHOD = `${outerMethod} > ${this.resendInvite.name}`; try { const mailSentInfo = await this.mailService.reSendEmailBank({ to: user.email as string, @@ -686,15 +699,15 @@ export class CronJobsService { const mailHistory = await this.mailHistoryService.getOne({ user: { email: user.email as string }, }); - this.logger.log(`Email enviado com sucesso para ${mailSentInfo.envelope.to}. (último envio: ${mailHistory.sentAt?.toISOString()})`, THIS_METHOD); + this.logger.log(`Email enviado com sucesso para ${mailSentInfo.envelope.to}. (último envio: ${mailHistory.sentAt?.toISOString()})`, METHOD); await this.mailHistoryService.update(mailHistory.id, { sentAt: new Date(Date.now()), }); } else { - this.logger.error('Email enviado retornou erro.' + ` - mailSentInfo: ${JSON.stringify(mailSentInfo)}`, new Error().stack, THIS_METHOD); + this.logger.error('Email enviado retornou erro.' + ` - mailSentInfo: ${JSON.stringify(mailSentInfo)}`, new Error().stack, METHOD); } } catch (httpException) { - this.logger.error('Email falhou ao enviar.', httpException.stack, THIS_METHOD); + this.logger.error(`Erro ao executar tarefa, abortando. - ${httpException}`, httpException?.stack, METHOD); } } @@ -705,7 +718,7 @@ export class CronJobsService { await this.cnabService.sendRemessa(listCnab); this.logger.log('Tarefa finalizada com sucesso.', METHOD); } catch (error) { - this.logger.error('Erro, abortando.', error.stack, METHOD); + this.logger.error(`Erro ao executar tarefa, abortando. - ${error}`, error?.stack, METHOD); } } @@ -715,7 +728,7 @@ export class CronJobsService { await this.cnabService.updateRetorno(); this.logger.log('Tarefa finalizada com sucesso.', METHOD); } catch (error) { - this.logger.error(`Erro, abortando. - ${error}`, error.stack, METHOD); + this.logger.error(`Erro ao executar tarefa, abortando. - ${error}`, error?.stack, METHOD); } } @@ -725,7 +738,7 @@ export class CronJobsService { await this.cnabService.saveExtrato(); this.logger.log('Tarefa finalizada com sucesso.', METHOD); } catch (error) { - this.logger.error(`Erro, abortando. - ${error}`, error.stack, METHOD); + this.logger.error(`Erro ao executar tarefa, abortando. - ${error}`, error?.stack, METHOD); } } } diff --git a/src/settings/settings.service.ts b/src/settings/settings.service.ts index 09ef66a6..b0790a2d 100644 --- a/src/settings/settings.service.ts +++ b/src/settings/settings.service.ts @@ -63,9 +63,7 @@ export class SettingsService { defaultValueIfNotFound?: boolean, logContext?: string, ): Promise { - const METHOD = logContext - ? `${logContext}>${this.getOneBySettingData.name}` - : this.getOneBySettingData.name; + const METHOD = logContext ? `${logContext}>${this.getOneBySettingData.name}` : this.getOneBySettingData.name; const dbSetting = await this.findOneBySettingData(setting); if (defaultValueIfNotFound && !dbSetting) { this.logger.warn( diff --git a/src/sftp/sftp-client/sftp-client.service.ts b/src/sftp/sftp-client/sftp-client.service.ts index a0c64b06..b3943a9e 100644 --- a/src/sftp/sftp-client/sftp-client.service.ts +++ b/src/sftp/sftp-client/sftp-client.service.ts @@ -79,11 +79,7 @@ export class SftpClientService { return await this.sftpClient.realPath(remotePath); } - async upload( - contents: string | Buffer | NodeJS.ReadableStream, - remoteFilePath: string, - transferOptions?: SftpClient.TransferOptions, - ): Promise { + async upload(contents: string | Buffer | NodeJS.ReadableStream, remoteFilePath: string, transferOptions?: SftpClient.TransferOptions): Promise { return await this.sftpClient.put(contents, remoteFilePath, transferOptions); } @@ -97,21 +93,15 @@ export class SftpClientService { * If function, it will add if return is `true`. * If no filter, all items wil be returned. */ - async list( - remoteDirectory: string, - filter?: string | RegExp | ((item: FileInfo) => boolean), - ): Promise { + async list(remoteDirectory: string, filter?: string | RegExp | ((item: FileInfo) => boolean)): Promise { return await this.sftpClient.list(remoteDirectory, (item: FileInfo) => { if (typeof filter === 'function') { return filter(item); - } - else if (typeof filter === 'string') { + } else if (typeof filter === 'string') { return item.name.includes(filter); - } - else if (filter) { + } else if (filter) { return item.name.match(filter); - } - else { + } else { return true; } }); @@ -138,11 +128,7 @@ export class SftpClientService { * } * ``` */ - async download( - path: string, - dst?: string | NodeJS.WritableStream, - options?: SftpClient.TransferOptions, - ): Promise { + async download(path: string, dst?: string | NodeJS.WritableStream, options?: SftpClient.TransferOptions): Promise { return await this.sftpClient.get(path, dst, options); } @@ -154,23 +140,18 @@ export class SftpClientService { await this.sftpClient.mkdir(remoteFilePath, recursive); } - async removeDirectory( - remoteFilePath: string, - recursive = true, - ): Promise { + async removeDirectory(remoteFilePath: string, recursive = true): Promise { await this.sftpClient.rmdir(remoteFilePath, recursive); } - async rename( - remoteSourcePath: string, - remoteDestinationPath: string, - overwriteDestination?: boolean, - ): Promise { - if (overwriteDestination) { - if (await this.exists(remoteDestinationPath)) { - this.logger.debug(`Overwriting rename destination: ${remoteDestinationPath}`); - await this.delete(remoteDestinationPath); - } + async rename(remoteSourcePath: string, remoteDestinationPath: string): Promise { + if (remoteSourcePath == remoteDestinationPath) { + this.logger.debug(`Origin and destination paths are the same. Nothing to do.`); + return; + } + if (await this.exists(remoteDestinationPath)) { + this.logger.debug(`Overwriting rename destination: ${remoteDestinationPath}`); + await this.delete(remoteDestinationPath); } await this.sftpClient.rename(remoteSourcePath, remoteDestinationPath); } @@ -189,5 +170,4 @@ export class SftpClientService { async connect(config: ConnectConfig) { await this.sftpClient.connect(config); } - } diff --git a/src/sftp/sftp.service.ts b/src/sftp/sftp.service.ts index c7c840a9..8a40de4a 100644 --- a/src/sftp/sftp.service.ts +++ b/src/sftp/sftp.service.ts @@ -202,7 +202,7 @@ export class SftpService implements OnModuleInit, OnModuleLoad { this.logger.log(`Origem não existe: '${originPath}'. Salvando cnab no backup.`) await this.sftpClient.upload(Buffer.from(cnabContentIfNoOrigin, 'utf-8'), destPath); } else { - await this.sftpClient.rename(originPath, destPath, true); + await this.sftpClient.rename(originPath, destPath); } this.logger.debug(`Arquivo CNAB movido de '${originPath}' para ${destPath}`, METHOD); } From ceac9110a9faad460325691ac2ead8a2fef2ec67 Mon Sep 17 00:00:00 2001 From: Raphael Rivas Date: Thu, 22 Aug 2024 17:35:18 -0300 Subject: [PATCH 11/11] fix: faster save, find favorecido, find detalheA --- src/cnab/cnab.controller.ts | 4 +- src/cnab/cnab.service.ts | 13 ++++- .../arquivo-publicacao.repository.ts | 1 + .../cliente-favorecido.repository.ts | 12 +++- .../pagamento/detalhe-a.repository.ts | 15 ++--- .../service/arquivo-publicacao.service.ts | 42 +++++++++----- .../service/cliente-favorecido.service.ts | 5 +- .../service/pagamento/detalhe-a.service.ts | 11 +--- .../pagamento/remessa-retorno.service.ts | 10 ++-- src/utils/entity-helper.ts | 22 +++++++ src/utils/log-utils.ts | 58 +++++-------------- 11 files changed, 104 insertions(+), 89 deletions(-) diff --git a/src/cnab/cnab.controller.ts b/src/cnab/cnab.controller.ts index 6916b6ba..4c8f8733 100644 --- a/src/cnab/cnab.controller.ts +++ b/src/cnab/cnab.controller.ts @@ -142,6 +142,7 @@ export class CnabController { } @ApiQuery({ name: 'folder', description: ApiDescription({ _: 'Pasta para ler os retornos', default: '`/retorno`' }), required: false, type: String }) + @ApiQuery({ name: 'maxItems', description: ApiDescription({ _: 'Número máximo de itens para ler', min: 1 }), required: false, type: Number }) @HttpCode(HttpStatus.OK) @ApiBearerAuth() @UseGuards(AuthGuard('jwt'), RolesGuard) @@ -149,8 +150,9 @@ export class CnabController { @Get('updateRetorno') async getUpdateRetorno( @Query('folder') folder: string | undefined, // + @Query('maxItems', new ParseNumberPipe({ min: 1, optional: true})) maxItems: number | undefined, ) { - return await this.cnabService.updateRetorno(folder); + return await this.cnabService.updateRetorno(folder, maxItems); } @ApiQuery({ name: 'dataOrdemInicial', description: 'Data da Ordem de Pagamento Inicial', required: true, type: Date }) diff --git a/src/cnab/cnab.service.ts b/src/cnab/cnab.service.ts index b27667c1..a1bcdbe9 100644 --- a/src/cnab/cnab.service.ts +++ b/src/cnab/cnab.service.ts @@ -50,6 +50,8 @@ import { RemessaRetornoService } from './service/pagamento/remessa-retorno.servi import { TransacaoAgrupadoService } from './service/pagamento/transacao-agrupado.service'; import { TransacaoService } from './service/pagamento/transacao.service'; import { parseCnab240Extrato, parseCnab240Pagamento, stringifyCnab104File } from './utils/cnab/cnab-104-utils'; +import { CommonHttpException } from 'src/utils/http-exception/common-http-exception'; +import { formatErrMsg } from 'src/utils/log-utils'; /** * User cases for CNAB and Payments @@ -526,8 +528,12 @@ export class CnabService { * @param folder Example: `/retorno` * @returns */ - public async updateRetorno(folder?: string) { + public async updateRetorno(folder?: string, maxItems?: number) { const METHOD = this.updateRetorno.name; + const INVALID_FOLDERS = ['/backup/retorno/success', '/backup/retorno/failed']; + if (folder && INVALID_FOLDERS.includes(folder)) { + throw CommonHttpException.message(`Não é possível ler retornos das pastas ${INVALID_FOLDERS}`); + } let { cnabName, cnabString } = await this.sftpService.getFirstCnabRetorno(folder); const cnabs: string[] = []; const success: any[] = []; @@ -550,7 +556,7 @@ export class CnabService { success.push(cnabName); } catch (error) { const durationItem = formatDateInterval(new Date(), startDateItem); - this.logger.error(`Erro ao processar CNAB retorno (${durationItem}), movendo para backup de erros e finalizando... - ${error}`, error.stack, METHOD); + this.logger.error(`Erro ao processar CNAB ${cnabName}. Movendo para backup de erros e finalizando - ${durationItem} - ${formatErrMsg(error)}`, error.stack, METHOD); if (!cnabName || !cnabString) { this.logger.log('Retorno não encontrado, abortando tarefa.', METHOD); return; @@ -563,6 +569,9 @@ export class CnabService { cnabName = cnab.cnabName; } const duration = formatDateInterval(new Date(), startDate); + if (maxItems && cnabs.length >= maxItems) { + this.logger.log(`Leitura de retornos finalizou a leitura de ${cnabs.length}/${maxItems} CNABs - ${duration}`, METHOD); + } this.logger.log(`Leitura de retornos finalizada com sucesso - ${duration}`, METHOD); return { duration, cnabs: cnabs.length, success, failed }; } diff --git a/src/cnab/repository/arquivo-publicacao.repository.ts b/src/cnab/repository/arquivo-publicacao.repository.ts index ee6bb405..6ddd52f0 100644 --- a/src/cnab/repository/arquivo-publicacao.repository.ts +++ b/src/cnab/repository/arquivo-publicacao.repository.ts @@ -86,6 +86,7 @@ export class ArquivoPublicacaoRepository { ap."horaGeracaoRetorno", ap."idTransacao", ap."isPago", ap."updatedAt", ap."valorRealEfetivado", json_build_object( 'id', it.id, + 'valor', it.valor, 'itemTransacaoAgrupado', json_build_object('id', ita.id) ) AS "itemTransacao" FROM arquivo_publicacao ap diff --git a/src/cnab/repository/cliente-favorecido.repository.ts b/src/cnab/repository/cliente-favorecido.repository.ts index 1c236619..89cf48a6 100644 --- a/src/cnab/repository/cliente-favorecido.repository.ts +++ b/src/cnab/repository/cliente-favorecido.repository.ts @@ -12,6 +12,8 @@ export interface IClienteFavorecidoRawWhere { id?: number[]; nome?: string[]; cpfCnpj?: string; + /** numeroDocumentoEmpresa */ + detalheANumeroDocumento?: number[]; } @Injectable() @@ -125,7 +127,7 @@ export class ClienteFavorecidoRepository { return first || null; } - public async findOneRaw(where: IClienteFavorecidoRawWhere): Promise { + public async findManyRaw(where: IClienteFavorecidoRawWhere): Promise { const qWhere: { query: string; params?: any[] } = { query: '' }; if (where.id) { qWhere.query = 'WHERE cf.id IN ($1)'; @@ -137,17 +139,23 @@ export class ClienteFavorecidoRepository { const nomes = where.nome.map((n) => `'%${n}%'`); qWhere.query = `WHERE cf.nome ILIKE ANY(ARRAY[${nomes.join(',')}])`; qWhere.params = []; + } else if (where.detalheANumeroDocumento) { + qWhere.query = `WHERE da."numeroDocumentoEmpresa" IN (${where.detalheANumeroDocumento.join(',')})`; + qWhere.params = []; } const result: any[] = await this.clienteFavorecidoRepository.query( ` SELECT cf.* FROM cliente_favorecido cf + ${where?.detalheANumeroDocumento ? `INNER JOIN item_transacao it ON it."clienteFavorecidoId" = cf.id + INNER JOIN item_transacao_agrupado ita ON ita.id = it."itemTransacaoAgrupadoId" + INNER JOIN detalhe_a da ON da."itemTransacaoAgrupadoId" = ita.id` : ''} ${qWhere.query} ORDER BY cf.id `, qWhere.params, ); const itens = result.map((i) => new ClienteFavorecido(i)); - return itens[0]; + return itens; } } diff --git a/src/cnab/repository/pagamento/detalhe-a.repository.ts b/src/cnab/repository/pagamento/detalhe-a.repository.ts index 5c2a1eec..978c5f9d 100644 --- a/src/cnab/repository/pagamento/detalhe-a.repository.ts +++ b/src/cnab/repository/pagamento/detalhe-a.repository.ts @@ -13,11 +13,7 @@ import { CommonHttpException } from 'src/utils/http-exception/common-http-except export interface IDetalheARawWhere { id?: number[]; - detalheARem?: { - dataVencimento: Date; - numeroDocumentoEmpresa: number; - valorLancamento: number; - }; + numeroDocumentoEmpresa?: number; } @Injectable() @@ -86,10 +82,9 @@ export class DetalheARepository { if (where.id) { qWhere.query = `WHERE da.id IN (${where.id.join(',')})`; qWhere.params = []; - } else if (where.detalheARem) { - const { dataVencimento, numeroDocumentoEmpresa, valorLancamento } = where.detalheARem; - qWhere.query = `WHERE da."dataVencimento"::DATE = $1 AND da."numeroDocumentoEmpresa" = $2 AND da."valorLancamento" = $3`; - qWhere.params = [getDateYMDString(dataVencimento), numeroDocumentoEmpresa, valorLancamento]; + } else if (where.numeroDocumentoEmpresa) { + qWhere.query = `WHERE da."numeroDocumentoEmpresa" = $1`; + qWhere.params = [where.numeroDocumentoEmpresa]; } const result: any[] = await this.detalheARepository.query( ` @@ -138,7 +133,7 @@ export class DetalheARepository { public async getOneRaw(where: IDetalheARawWhere): Promise { const result = await this.findRaw(where); if (result.length == 0) { - throw CommonHttpException.details('It should return at least one DetalheA'); + throw CommonHttpException.details('It should return at least one DetalheA'); } else { return result[0]; } diff --git a/src/cnab/service/arquivo-publicacao.service.ts b/src/cnab/service/arquivo-publicacao.service.ts index e240f0e7..f5df712f 100644 --- a/src/cnab/service/arquivo-publicacao.service.ts +++ b/src/cnab/service/arquivo-publicacao.service.ts @@ -9,6 +9,9 @@ import { ArquivoPublicacaoRepository, IArquivoPublicacaoRawWhere } from '../repo import { OcorrenciaService } from './ocorrencia.service'; import { ItemTransacaoService } from './pagamento/item-transacao.service'; import { IFindPublicacaoRelatorio } from 'src/relatorio/interfaces/find-publicacao-relatorio.interface'; +import { EntityHelper } from 'src/utils/entity-helper'; + +export type ArquivoPublicacaoFields = 'savePublicacaoRetorno'; @Injectable() export class ArquivoPublicacaoService { @@ -16,11 +19,7 @@ export class ArquivoPublicacaoService { timestamp: true, }); - constructor( - private arquivoPublicacaoRepository: ArquivoPublicacaoRepository, - private transacaoOcorrenciaService: OcorrenciaService, - private itemTransacaoService: ItemTransacaoService, - ) {} + constructor(private arquivoPublicacaoRepository: ArquivoPublicacaoRepository, private transacaoOcorrenciaService: OcorrenciaService, private itemTransacaoService: ItemTransacaoService) {} public findMany(options: FindManyOptions) { return this.arquivoPublicacaoRepository.findMany(options); @@ -31,10 +30,7 @@ export class ArquivoPublicacaoService { } public async findManyByDate(startDate: Date, endDate: Date) { - return await this.arquivoPublicacaoRepository.findManyByDate( - startDate, - endDate, - ); + return await this.arquivoPublicacaoRepository.findManyByDate(startDate, endDate); } /** @@ -42,9 +38,7 @@ export class ArquivoPublicacaoService { * * **status** is Created. */ - async convertPublicacaoDTO( - itemTransacao: ItemTransacao, - ): Promise { + async convertPublicacaoDTO(itemTransacao: ItemTransacao): Promise { const existing = await this.arquivoPublicacaoRepository.findOne({ where: { itemTransacao: { @@ -76,12 +70,30 @@ export class ArquivoPublicacaoService { return arquivo; } - public async save( - dto: DeepPartial,queryRunner:QueryRunner - ): Promise { + public async save(dto: DeepPartial, queryRunner: QueryRunner): Promise { return await queryRunner.manager.getRepository(ArquivoPublicacao).save(dto); } + public async updateManyRaw(dtos: DeepPartial[], fields: ArquivoPublicacaoFields, queryRunner: QueryRunner): Promise { + let fieldNames: string[] = []; + let fieldTypes: string[] = []; + if (fields == 'savePublicacaoRetorno') { + fieldNames = ['id', 'isPago', 'valorRealEfetivado', 'dataEfetivacao', 'dataGeracaoRetorno']; + fieldTypes = ['INT', 'BOOLEAN', 'NUMERIC', 'VARCHAR', 'TIMESTAMP']; + } + const fieldValues = dtos.map((dto) => `(${EntityHelper.getQueryFieldValues(dto, fieldNames, fieldTypes)})`).join(', '); + const query = ` + UPDATE arquivo_publicacao + SET ${fieldNames.map((f) => `"${f}" = sub.${f == 'id' ? '_id' : `"${f}"`}`).join(', ')}, "updatedAt" = NOW() + FROM ( + VALUES ${fieldValues} + ) AS sub(${fieldNames.map((i) => (i == 'id' ? '_id' : `"${i}"`)).join(', ')}) + WHERE id = sub._id; + `; + await queryRunner.manager.query(query); + return dtos.map((dto) => new ArquivoPublicacao(dto)); + } + /** * Publicacao está associada com a ordem, portanto é sex-qui */ diff --git a/src/cnab/service/cliente-favorecido.service.ts b/src/cnab/service/cliente-favorecido.service.ts index 2f2e6e26..38efd62f 100644 --- a/src/cnab/service/cliente-favorecido.service.ts +++ b/src/cnab/service/cliente-favorecido.service.ts @@ -206,7 +206,8 @@ export class ClienteFavorecidoService { return await this.clienteFavorecidoRepository.findOne(options); } - public async findOneRaw(where: IClienteFavorecidoRawWhere): Promise { - return await this.clienteFavorecidoRepository.findOneRaw(where); + public async findOneRaw(where: IClienteFavorecidoRawWhere): Promise { + const result = await this.clienteFavorecidoRepository.findManyRaw(where); + return result?.[0] || null; } } diff --git a/src/cnab/service/pagamento/detalhe-a.service.ts b/src/cnab/service/pagamento/detalhe-a.service.ts index 636f6fa8..f1a8880c 100644 --- a/src/cnab/service/pagamento/detalhe-a.service.ts +++ b/src/cnab/service/pagamento/detalhe-a.service.ts @@ -46,22 +46,17 @@ export class DetalheAService { } public async saveRetornoFrom104(headerArq: CnabHeaderArquivo104, headerLotePgto: CnabHeaderLote104Pgto, r: CnabRegistros104Pgto, dataEfetivacao: Date): Promise { - const logRegistro = `HeaderArquivo: ${headerArq.nsa.convertedValue}, lote: ${headerLotePgto.codigoRegistro.value}`; + const logRegistro = `HeaderArquivo: ${headerArq.nsa.convertedValue}, lote: ${headerLotePgto.codigoRegistro.value}`; const favorecido = await this.clienteFavorecidoService.findOneRaw({ - nome: [r.detalheA.nomeTerceiro.stringValue.trim()], + detalheANumeroDocumento: [r.detalheA.numeroDocumentoEmpresa.convertedValue], }); if (!favorecido) { this.logger.warn(logRegistro + ` Detalhe A Documento: ${r.detalheA.numeroDocumentoEmpresa.convertedValue} - Favorecido não encontrado para o nome: '${r.detalheA.nomeTerceiro.stringValue.trim()}'`); return null; } - const dataVencimento = startOfDay(r.detalheA.dataVencimento.convertedValue); const detalheA = await this.detalheARepository.findOneRaw({ - detalheARem: { - dataVencimento: dataVencimento, - numeroDocumentoEmpresa: r.detalheA.numeroDocumentoEmpresa.convertedValue, - valorLancamento: r.detalheA.valorLancamento.convertedValue, - }, + numeroDocumentoEmpresa: r.detalheA.numeroDocumentoEmpresa.convertedValue, }); if (detalheA) { if (detalheA.ocorrenciasCnab === undefined || detalheA.ocorrenciasCnab === '' || detalheA.ocorrenciasCnab !== r.detalheA.ocorrencias.value.trim()) { diff --git a/src/cnab/service/pagamento/remessa-retorno.service.ts b/src/cnab/service/pagamento/remessa-retorno.service.ts index 9161f728..5a6f7eb8 100644 --- a/src/cnab/service/pagamento/remessa-retorno.service.ts +++ b/src/cnab/service/pagamento/remessa-retorno.service.ts @@ -452,13 +452,13 @@ export class RemessaRetornoService { let detalheAUpdated: DetalheA | null = null; for (const cnabLote of cnab.lotes) { for (const registro of cnabLote.registros) { - const logRegistro = `HeaderArquivo: ${cnab.headerArquivo.nsa.convertedValue}, lote: ${cnabLote.headerLote.codigoRegistro.value}`; - + const logRegistro = `HeaderArquivo: ${cnab.headerArquivo.nsa.convertedValue}, lote: ${cnabLote.headerLote.codigoRegistro.value}`; + // Save Detalhes detalheAUpdated = await this.detalheAService.saveRetornoFrom104(cnab.headerArquivo, cnabLote.headerLote, registro, dataEfetivacao); if (!detalheAUpdated) { const numeroDocumento = registro.detalheA.numeroDocumentoEmpresa.convertedValue; - detalhesANaoEncontrados.push(numeroDocumento) + detalhesANaoEncontrados.push(numeroDocumento); continue; } this.logger.debug(logRegistro + ` Detalhe A Documento: ${detalheAUpdated.numeroDocumentoEmpresa}, favorecido: '${registro.detalheA.nomeTerceiro.value.trim()}' - OK`); @@ -480,7 +480,7 @@ export class RemessaRetornoService { } } if (detalhesANaoEncontrados.length > 0) { - throw new NotFoundException(`Os seguintes DetalhesA do Retorno não foram encontrados no Banco (campo: no. documento) - ${detalhesANaoEncontrados.join(',')}`) + throw new NotFoundException(`Os seguintes DetalhesA do Retorno não foram encontrados no Banco (campo: no. documento) - ${detalhesANaoEncontrados.join(',')}`); } } @@ -543,8 +543,8 @@ export class RemessaRetornoService { } publicacao.dataGeracaoRetorno = detalheARetorno.headerLote.headerArquivo.dataGeracao; publicacao.horaGeracaoRetorno = detalheARetorno.headerLote.headerArquivo.horaGeracao; - await this.arquivoPublicacaoService.save(publicacao, queryRunner); } + await this.arquivoPublicacaoService.updateManyRaw(publicacoes, 'savePublicacaoRetorno', queryRunner); } async compareTransacaoViewPublicacao(detalheA: DetalheA, queryRunner: QueryRunner) { diff --git a/src/utils/entity-helper.ts b/src/utils/entity-helper.ts index e6bbcb20..ed314b48 100644 --- a/src/utils/entity-helper.ts +++ b/src/utils/entity-helper.ts @@ -1,4 +1,5 @@ import { Exclude, instanceToPlain } from 'class-transformer'; +import { isDate } from 'date-fns'; import { AfterLoad, BaseEntity, DeepPartial } from 'typeorm'; export class EntityHelper extends BaseEntity { @@ -29,4 +30,25 @@ export class EntityHelper extends BaseEntity { public static getUniqueId(entity: DeepPartial): string { return `${entity}`; } + + public static getQueryFieldValues(dto: DeepPartial, fields: string[], types: string[]) { + const query = fields + .map((f, i) => { + const value = dto[f]; + const _type = types[i]; + if (typeof value === 'string') { + return `'${value}'`; + } else if (typeof value === 'number' || typeof value === 'boolean') { + return value; + } else if (value === null) { + return `NULL::${_type}`; + } else if (isDate(new Date(value))) { + return `'${new Date(value).toISOString()}'::${_type}`; + } else { + return value; + } + }) + .join(', '); + return query; + } } diff --git a/src/utils/log-utils.ts b/src/utils/log-utils.ts index a71e22db..4238f475 100644 --- a/src/utils/log-utils.ts +++ b/src/utils/log-utils.ts @@ -1,16 +1,10 @@ -import { Logger } from "@nestjs/common"; -import { asJSONStrOrObj } from "./pipe-utils"; - +import { Logger } from '@nestjs/common'; +import { asJSONStrOrObj } from './pipe-utils'; /** * Run logger.debug() with formatted content. */ -export function logDebug( - logger: Logger | Console, - log: string, - context?: string, - outerContext?: string, -) { +export function logDebug(logger: Logger | Console, log: string, context?: string, outerContext?: string) { logger.debug(formatLog(log, 'DEBUG', context, outerContext)); } @@ -25,13 +19,7 @@ export function getLogger(injectLogger: Logger | Console) { /** * Run logger.log() with formatted content. */ -export function logLog( - logger: Logger | Console, - log: string, - context?: string, - outerContext?: string, -) { - +export function logLog(logger: Logger | Console, log: string, context?: string, outerContext?: string) { const _logger = getLogger(logger); _logger.log(formatLog(log, 'LOG', context, outerContext)); } @@ -39,12 +27,7 @@ export function logLog( /** * Run logger.warn() with formatted content. */ -export function logWarn( - logger: Logger | Console, - log: string, - context?: string, - outerContext?: string, -) { +export function logWarn(logger: Logger | Console, log: string, context?: string, outerContext?: string) { const _logger = getLogger(logger); _logger.warn(formatLog(log, 'WARN', context, outerContext)); } @@ -52,27 +35,15 @@ export function logWarn( /** * Run logger.error() with formatted content. */ -export function logError( - logger: Logger | Console, - firstLine: string, - context?: string, - message?: object | string, - traceback?: Error, -) { +export function logError(logger: Logger | Console, firstLine: string, context?: string, message?: object | string, traceback?: Error) { const _logger = getLogger(logger); _logger.error(formatError(firstLine, message, traceback, context)); } - /** * Format log content for log, debug and warn. */ -export function formatLog( - log: string, - logLevel: 'DEBUG' | 'LOG' | 'WARN' | 'ERROR', - context?: string, - outerContext?: string, -) { +export function formatLog(log: string, logLevel: 'DEBUG' | 'LOG' | 'WARN' | 'ERROR', context?: string, outerContext?: string) { let startLog = `${context}`; if (context) { if (outerContext) { @@ -83,7 +54,7 @@ export function formatLog( startLog = ''; } if (isLocal()) { - startLog = `[Nest] ${logLevel} - ${startLog}: ` + startLog = `[Nest] ${logLevel} - ${startLog}: `; } return startLog + log; } @@ -91,12 +62,7 @@ export function formatLog( /** * Format log for error content. */ -export function formatError( - firstLine: string, - message?: object | string, - traceback?: Error, - context?: string, -): string { +export function formatError(firstLine: string, message?: object | string, traceback?: Error, context?: string): string { let formattedString = firstLine; if (message) { formattedString += `\n - Message: ${asJSONStrOrObj(message)}`; @@ -114,5 +80,9 @@ export function getLogFromError(error: any) { return JSON.stringify({ message: (error as Error)?.message, traceback: (error as Error)?.stack, - }) + }); +} + +export function formatErrMsg(error) { + return error?.response?.details?.message || error; }