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.controller.ts b/src/cnab/cnab.controller.ts index 8c5835e9..4c8f8733 100644 --- a/src/cnab/cnab.controller.ts +++ b/src/cnab/cnab.controller.ts @@ -141,16 +141,18 @@ 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 }) + @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) @Roles(RoleEnum.admin) @Get('updateRetorno') async getUpdateRetorno( - @Query('folder', new ParseDatePipe()) folder: string | undefined, // + @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 a0417ddb..a1bcdbe9 100644 --- a/src/cnab/cnab.service.ts +++ b/src/cnab/cnab.service.ts @@ -50,13 +50,15 @@ 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 */ @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, // @@ -88,7 +90,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} @@ -274,7 +276,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 { @@ -319,7 +323,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 +501,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), }); } @@ -524,32 +528,52 @@ 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; - let { cnabString, cnabName } = await this.sftpService.getFirstCnabRetorno(folder); + 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[] = []; + 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); - const cnab = await this.sftpService.getFirstCnabRetorno(folder); - cnabString = cnab.cnabString; - cnabName = cnab.cnabName; + 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); } 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 ${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; } - await this.sftpService.moveToBackup(cnabName, SftpBackupFolder.RetornoFailure, cnabString); + 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; } - return; + 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/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..6ddd52f0 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 { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { - Between, - DeepPartial, - FindManyOptions, - In, - InsertResult, - QueryRunner, - Repository, -} from '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 { 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'; + +export interface IArquivoPublicacaoRawWhere { + id?: number[]; + itemTransacaoAgrupadoId?: number[]; +} @Injectable() export class ArquivoPublicacaoRepository { - private logger: Logger = new Logger('ArquivoPublicacaoRepository', { - timestamp: true, - }); + private logger = new CustomLogger('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,59 @@ 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, + 'valor', it.valor, + '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} + ORDER BY ap.id + `, + 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..89cf48a6 100644 --- a/src/cnab/repository/cliente-favorecido.repository.ts +++ b/src/cnab/repository/cliente-favorecido.repository.ts @@ -1,29 +1,24 @@ -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'; 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; + /** numeroDocumentoEmpresa */ + detalheANumeroDocumento?: number[]; +} @Injectable() export class ClienteFavorecidoRepository { - private logger: Logger = new CustomLogger(ClienteFavorecidoRepository.name, { - timestamp: true, - }); + private logger = new CustomLogger(ClienteFavorecidoRepository.name, { timestamp: true }); constructor( @InjectRepository(ClienteFavorecido) @@ -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,40 @@ 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 findManyRaw(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 = []; + } 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; + } } 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 3802f95a..978c5f9d 100644 --- a/src/cnab/repository/pagamento/detalhe-a.repository.ts +++ b/src/cnab/repository/pagamento/detalhe-a.repository.ts @@ -5,24 +5,20 @@ 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[]; + numeroDocumentoEmpresa?: 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 +31,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 +45,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 +68,7 @@ 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.getOneRaw({ id: [saved.id] }); } public async getOne(fields: EntityCondition): Promise { @@ -100,17 +77,75 @@ export class DetalheARepository { }); } - public async findOne( - options: FindOneOptions, - ): 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(',')})`; + qWhere.params = []; + } else if (where.numeroDocumentoEmpresa) { + qWhere.query = `WHERE da."numeroDocumentoEmpresa" = $1`; + qWhere.params = [where.numeroDocumentoEmpresa]; + } + 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} + 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) { + 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]; + } + } + + 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 +157,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..f5df712f 100644 --- a/src/cnab/service/arquivo-publicacao.service.ts +++ b/src/cnab/service/arquivo-publicacao.service.ts @@ -5,10 +5,13 @@ 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'; +import { EntityHelper } from 'src/utils/entity-helper'; + +export type ArquivoPublicacaoFields = 'savePublicacaoRetorno'; @Injectable() export class ArquivoPublicacaoService { @@ -16,21 +19,18 @@ 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); } + public findManyRaw(where: IArquivoPublicacaoRawWhere) { + return this.arquivoPublicacaoRepository.findManyRaw(where); + } + public async findManyByDate(startDate: Date, endDate: Date) { - return await this.arquivoPublicacaoRepository.findManyByDate( - startDate, - endDate, - ); + return await this.arquivoPublicacaoRepository.findManyByDate(startDate, endDate); } /** @@ -38,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: { @@ -72,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 ba528032..38efd62f 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,9 @@ export class ClienteFavorecidoService { ): Promise { return await this.clienteFavorecidoRepository.findOne(options); } + + public async findOneRaw(where: IClienteFavorecidoRawWhere): Promise { + const result = await this.clienteFavorecidoRepository.findManyRaw(where); + return result?.[0] || null; + } } 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..f1a8880c 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,17 +16,13 @@ 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, - ) {} + 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 +30,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,39 +41,27 @@ 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 logRegistro = `HeaderArquivo: ${headerArq.nsa.convertedValue}, lote: ${headerLotePgto.codigoRegistro.value}`; + const favorecido = await this.clienteFavorecidoService.findOneRaw({ + 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); - let detalheARem; - try { - detalheARem = await this.detalheARepository.getOne({ - 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({ + numeroDocumentoEmpresa: r.detalheA.numeroDocumentoEmpresa.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), @@ -90,32 +73,19 @@ 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); + 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' - ) { + 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,20 +94,15 @@ 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); } } - return detalheARem; + return detalheA; } public async save(dto: DetalheADTO): Promise { @@ -149,21 +114,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..5a6f7eb8 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.trim()}' - 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.length > 0) { + throw new NotFoundException(`Os seguintes DetalhesA do Retorno não foram encontrados no Banco (campo: no. documento) - ${detalhesANaoEncontrados.join(',')}`); + } } // region compareRemessaToRetorno @@ -520,21 +524,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; @@ -545,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/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/cron-jobs/cron-jobs.service.ts b/src/cron-jobs/cron-jobs.service.ts index 33e7f860..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'; @@ -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,72 +76,69 @@ 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 () => 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(), }, }, - /** 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(), + onTick: async () => await 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 () => { - await this.bulkResendInvites(); - }, + onTick: async () => await this.bulkResendInvites(), }, }, { - name: CrobJobsEnum.updateTransacaoViewVan, + name: CronJobsEnum.updateTransacaoViewVan, cronJobParameters: { cronTime: '*/30 * * * *', // Every 30 min - onTick: async () => { - await this.updateTransacaoView('Van'); - }, + onTick: async () => await this.updateTransacaoView('Van'), }, }, { - name: CrobJobsEnum.updateTransacaoViewEmpresa, + 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: CrobJobsEnum.updateTransacaoViewVLT, + 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: CrobJobsEnum.syncTransacaoViewOrdemPgto, + 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: CrobJobsEnum.updateRetorno, + name: CronJobsEnum.updateRetorno, cronJobParameters: { cronTime: '*/30 * * * *', // Every 30 min - onTick: async () => { - await this.updateRetorno(); - }, + onTick: async () => await this.updateRetorno(), }, }, // { @@ -175,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.`); } } @@ -306,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()) { @@ -336,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, @@ -436,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()); @@ -459,137 +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()) { - this.logger.log(`Tarefa cancelada pois setting.${appSettings.any__poll_db_enabled.name}' = 'false'` + ` Para ativar, altere na tabela 'setting'`, METHOD); - return; - } - - let hasDbChanges = false; - - const cronjobSettings: ICronJobSetting[] = [ - { - setting: appSettings.any__poll_db_cronjob, - cronJob: CrobJobsEnum.pollDb, - }, - { - setting: appSettings.any__mail_invite_cronjob, - cronJob: CrobJobsEnum.bulkSendInvites, - }, - { - setting: appSettings.any__mail_report_cronjob, - cronJob: CrobJobsEnum.sendStatusReport, - }, - ]; - for (const setting of cronjobSettings) { - const dbChanged = await this.handleCronjobSettings(setting, METHOD); - if (dbChanged) { - hasDbChanges = true; + 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; } - } - if (hasDbChanges) { - this.logger.log('Tarefa finalizada.', METHOD); - } else { - this.logger.log('Tarefa finalizada, sem alterações no banco.', METHOD); + 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); } } @@ -655,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, @@ -684,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); } } @@ -703,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); } } @@ -713,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); } } @@ -723,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/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/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 b02e33e5..3e2c609f 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}`; @@ -44,57 +42,14 @@ 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 - group by cs."consorcio"` + 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(valorMin!==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 @@ -124,7 +79,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) @@ -137,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){ 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; + } } 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 7f0c47a2..8a40de4a 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 }; @@ -202,16 +192,17 @@ 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))) { 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); } 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 3079fe95..885b709e 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,16 +57,11 @@ 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) { - if (!context) { - return ''; - } + const contextStr = this.color('warn', `[${context}]`); if (isLocal) { const thisContext = this.color('warn', `[${this.context}]`); @@ -101,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)); } } @@ -132,38 +123,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)}`; 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; }