From 81645ef963400c87d08d975dcb303e084cb9174d Mon Sep 17 00:00:00 2001 From: Raphael Rivas Date: Wed, 28 Aug 2024 15:47:40 -0300 Subject: [PATCH 01/29] wip: cliente favorecido --- src/cnab/cnab.controller.ts | 61 +- .../seeds/user/user-seed-data.service.ts | 571 +++++++++--------- src/lancamento/dtos/lancamentoDto.ts | 16 +- src/lancamento/lancamento.controller.ts | 137 ++--- src/lancamento/lancamento.service.ts | 12 +- test/admin/lancamento.e2e-spec.ts | 430 +++++++++++++ 6 files changed, 808 insertions(+), 419 deletions(-) create mode 100644 test/admin/lancamento.e2e-spec.ts diff --git a/src/cnab/cnab.controller.ts b/src/cnab/cnab.controller.ts index 0bbbacea..35601f41 100644 --- a/src/cnab/cnab.controller.ts +++ b/src/cnab/cnab.controller.ts @@ -5,19 +5,16 @@ import { Roles } from 'src/roles/roles.decorator'; import { RoleEnum } from 'src/roles/roles.enum'; import { RolesGuard } from 'src/roles/roles.guard'; import { ApiDescription } from 'src/utils/api-param/description-api-param'; +import { CustomLogger } from 'src/utils/custom-logger'; import { ParseDatePipe } from 'src/utils/pipes/parse-date.pipe'; +import { ParseListPipe } from 'src/utils/pipes/parse-list.pipe'; import { ParseNumberPipe } from 'src/utils/pipes/parse-number.pipe'; import { CnabService } from './cnab.service'; import { ClienteFavorecido } from './entity/cliente-favorecido.entity'; -import { PagadorContaEnum } from './enums/pagamento/pagador.enum'; import { ArquivoPublicacaoService } from './service/arquivo-publicacao.service'; import { ClienteFavorecidoService } from './service/cliente-favorecido.service'; import { ExtratoDto } from './service/dto/extrato.dto'; import { ExtratoHeaderArquivoService } from './service/extrato/extrato-header-arquivo.service'; -import { ParseListPipe } from 'src/utils/pipes/parse-list.pipe'; -import { CommonHttpException } from 'src/utils/http-exception/common-http-exception'; -import { CustomLogger } from 'src/utils/custom-logger'; -import { formatDateInterval } from 'src/utils/date-utils'; @ApiTags('Cnab') @Controller({ @@ -35,26 +32,31 @@ export class CnabController { ) {} @Get('clientes-favorecidos') + @UseGuards(AuthGuard('jwt'), RolesGuard) + @Roles(RoleEnum.master, RoleEnum.admin, RoleEnum.admin_finan, RoleEnum.lancador_financeiro, RoleEnum.aprovador_financeiro) + @ApiBearerAuth() @ApiQuery({ name: 'nome', description: 'Pesquisa por parte do nome, sem distinção de acento ou maiúsculas.', required: false, type: String, example: 'joao' }) + @ApiQuery({ name: 'consorcio', description: 'Nome do consorcio - salvar transações', required: true, type: String, example: 'Todos / Van / Empresa /Nome Consorcio' }) @ApiQuery({ name: 'limit', description: 'Itens exibidos por página', required: false, type: Number, example: 1 }) @ApiQuery({ name: 'page', description: ApiDescription({ description: 'Itens exibidos por página', min: 1 }), required: false, type: Number, example: 1 }) getClienteFavorecido( @Query('nome', new ParseArrayPipe({ items: String, separator: ',', optional: true })) nome: string[], // - @Query('limit', new ParseNumberPipe({ min: 0, optional: true })) limit: number, - @Query('page', new ParseNumberPipe({ min: 1, optional: true })) page: number, + @Query('consorcio') consorcio: string, + @Query('limit', new ParseNumberPipe({ min: 0, optional: true })) limit: number | undefined, + @Query('page', new ParseNumberPipe({ min: 1, optional: true })) page: number | undefined, ): Promise { return this.clienteFavorecidoService.findBy({ nome, limit, page }); } + @Get('extratoLancamento') + @HttpCode(HttpStatus.OK) + @UseGuards(AuthGuard('jwt'), RolesGuard) + @Roles(RoleEnum.master, RoleEnum.admin_finan, RoleEnum.lancador_financeiro, RoleEnum.aprovador_financeiro) + @ApiBearerAuth() @ApiQuery({ name: 'conta', required: true, type: String }) @ApiQuery({ name: 'dt_inicio', required: true, type: String, example: '2024-01-01' }) @ApiQuery({ name: 'dt_fim', required: true, type: String, example: '2024-12-25' }) @ApiQuery({ name: 'tipo', required: false, type: String, example: 'cett' }) - @HttpCode(HttpStatus.OK) - @ApiBearerAuth() - @UseGuards(AuthGuard('jwt'), RolesGuard) - @Roles(RoleEnum.master, RoleEnum.admin_finan, RoleEnum.lancador_financeiro, RoleEnum.aprovador_financeiro) - @Get('extratoLancamento') async getLancamentoExtrato( @Query('conta') conta: string, // @Query('dt_inicio', new ParseDatePipe()) dt_inicio: string, @@ -64,15 +66,15 @@ export class CnabController { return await this.extratoHeaderArquivoService.getExtrato(conta, dt_inicio, dt_fim, tipoLancamento); } + @Get('arquivoPublicacao') + @HttpCode(HttpStatus.OK) + @UseGuards(AuthGuard('jwt')) @ApiOperation({ description: `Endpoint para a equipe de dados (Bigquery) realizar a leitura das publicacoes de pagamentos realizados.` }) + @ApiBearerAuth() @ApiQuery({ name: 'dt_inicio', description: 'dataOrdem', required: true, type: String, example: '2024-01-01' }) @ApiQuery({ name: 'dt_fim', description: 'dataOrdem', required: true, type: String, example: '2024-12-25' }) @ApiQuery({ name: 'limit', required: false, type: Number, example: 10 }) @ApiQuery({ name: 'page', required: false, type: Number, example: 1 }) - @HttpCode(HttpStatus.OK) - @ApiBearerAuth() - @UseGuards(AuthGuard('jwt')) - @Get('arquivoPublicacao') async getArquivoPublicacao( @Query('dt_inicio', new ParseDatePipe({ dateOnly: true, transform: true })) _dt_inicio: any, // Date @Query('dt_fim', new ParseDatePipe({ dateOnly: true, transform: true })) _dt_fim: any, // Date @@ -85,7 +87,12 @@ export class CnabController { return result; } + @Get('generateRemessa') + @HttpCode(HttpStatus.OK) + @UseGuards(AuthGuard('jwt'), RolesGuard) + @Roles(RoleEnum.admin) @ApiOperation({ description: 'Feito para manutenção pelos admins.\n\nExecuta a geração e envio de remessa - que normalmente é feita via cronjob' }) + @ApiBearerAuth() @ApiQuery({ name: 'dataOrdemInicial', description: ApiDescription({ _: 'Data da Ordem de Pagamento Inicial - salvar transações', example: '2024-07-15' }), required: true, type: String }) @ApiQuery({ name: 'dataOrdemFinal', description: ApiDescription({ _: 'Data da Ordem de Pagamento Final - salvar transações', example: '2024-07-16' }), required: true, type: String }) @ApiQuery({ name: 'diasAnterioresOrdem', description: ApiDescription({ _: 'Procurar também por dias Anteriores a dataOrdemInicial - salvar transações', default: 0 }), required: false, type: Number, example: 7 }) @@ -96,11 +103,6 @@ export class CnabController { @ApiQuery({ name: 'nsaInicial', description: ApiDescription({ default: 'O NSA atual' }), required: false, type: Number }) @ApiQuery({ name: 'nsaFinal', description: ApiDescription({ default: 'nsaInicial' }), required: false, type: Number }) @ApiQuery({ name: 'dataCancelamento', description: ApiDescription({ _: 'Data de vencimento da transação a ser cancelada (DetalheA).', 'Required if': 'isCancelamento = true' }), required: false, type: String, example: '2024-07-16' }) - @HttpCode(HttpStatus.OK) - @ApiBearerAuth() - @UseGuards(AuthGuard('jwt'), RolesGuard) - @Roles(RoleEnum.admin) - @Get('generateRemessa') async getGenerateRemessa( @Query('dataOrdemInicial', new ParseDatePipe({ transform: true })) _dataOrdemInicial: any, // Date @Query('dataOrdemFinal', new ParseDatePipe({ transform: true })) _dataOrdemFinal: any, // Date @@ -136,14 +138,14 @@ export class CnabController { }); } - @ApiOperation({ description: 'Feito para manutenção pelos admins.\n\nExecuta a leitura do retorno - que normalmente é feita via cronjob' }) - @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 }) + @Get('updateRetorno') @HttpCode(HttpStatus.OK) - @ApiBearerAuth() @UseGuards(AuthGuard('jwt'), RolesGuard) @Roles(RoleEnum.admin) - @Get('updateRetorno') + @ApiOperation({ description: 'Feito para manutenção pelos admins.\n\nExecuta a leitura do retorno - que normalmente é feita via cronjob' }) + @ApiBearerAuth() + @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 }) async getUpdateRetorno( @Query('folder') folder: string | undefined, // @Query('maxItems', new ParseNumberPipe({ min: 1, optional: true })) maxItems: number | undefined, @@ -151,15 +153,14 @@ export class CnabController { return await this.cnabService.updateRetorno(folder, maxItems); } + @Get('syncTransacaoViewOrdemPgto') + @UseGuards(AuthGuard('jwt'), RolesGuard) + @Roles(RoleEnum.admin) @ApiOperation({ description: 'Feito para manutenção pelos admins.\n\nExecuta o sincronismo de TransacaoView com as OrdensPagamento (ItemTransacaoAgrupado) - que normalmente é feia via cronjob' }) @ApiQuery({ name: 'dataOrdemInicial', description: 'Data da Ordem de Pagamento Inicial', required: false, type: Date }) @ApiQuery({ name: 'dataOrdemFinal', description: 'Data da Ordem de Pagamento Final', required: false, type: Date }) @ApiQuery({ name: 'nomeFavorecido', description: 'Lista de nomes dos favorecidos', required: false, type: String }) @HttpCode(HttpStatus.OK) - @ApiBearerAuth() - @UseGuards(AuthGuard('jwt'), RolesGuard) - @Roles(RoleEnum.admin) - @Get('syncTransacaoViewOrdemPgto') async getSyncTransacaoViewOrdemPgto( @Query('dataOrdemInicial', new ParseDatePipe({ transform: true, optional: true })) dataOrdemInicial: Date | undefined, // @Query('dataOrdemFinal', new ParseDatePipe({ transform: true, optional: true })) dataOrdemFinal: Date | undefined, diff --git a/src/database/seeds/user/user-seed-data.service.ts b/src/database/seeds/user/user-seed-data.service.ts index 816433cc..28a75116 100644 --- a/src/database/seeds/user/user-seed-data.service.ts +++ b/src/database/seeds/user/user-seed-data.service.ts @@ -15,12 +15,8 @@ export class UserSeedDataService { cpfSamples: string[] = []; cnpjSamples: string[] = []; - constructor( - private configService: ConfigService, - private bigqueryService: BigqueryService, - ) { - this.nodeEnv = () => - this.configService.getOrThrow('app.nodeEnv', { infer: true }); + constructor(private configService: ConfigService, private bigqueryService: BigqueryService) { + this.nodeEnv = () => this.configService.getOrThrow('app.nodeEnv', { infer: true }); } async getData(): Promise { @@ -58,288 +54,324 @@ LIMIT 5 } return [ - // Dev team - { - fullName: 'Alexander Rivail Ruiz', - email: 'ruiz.smtr@gmail.com', - password: this.generateRandomPassword(), - role: new Role(RoleEnum.admin), - status: new Status(StatusEnum.active), - }, - { - fullName: 'Bernardo Marcos', - email: 'bernardo.marcos64@gmail.com', - password: this.generateRandomPassword(), - role: new Role(RoleEnum.admin), - status: new Status(StatusEnum.active), - }, - { - fullName: 'Gabriel Fortes', - email: 'glfg01092001@gmail.com', - password: this.generateRandomPassword(), - role: new Role(RoleEnum.admin), - status: new Status(StatusEnum.active), - }, - { - fullName: 'Raphael Baptista Rivas de Araújo', - email: 'raphaelrivasbra@gmail.com', - password: this.generateRandomPassword(), - role: new Role(RoleEnum.admin), - status: new Status(StatusEnum.active), - }, - { - fullName: 'William FL 2007', - email: 'williamfl2007@gmail.com', - password: this.generateRandomPassword(), - role: new Role(RoleEnum.admin), - status: new Status(StatusEnum.active), - }, - // Admin CCT - { - fullName: 'Felipe Ribeiro', - email: 'felipe.ribeiro@prefeitura.rio', - password: this.generateRandomPassword(), - role: new Role(RoleEnum.admin), - status: new Status(StatusEnum.active), - }, - { - fullName: 'Monique Lopes', - email: 'monique.lopes@prefeitura.rio', - password: this.generateRandomPassword(), - role: new Role(RoleEnum.admin), - status: new Status(StatusEnum.active), - }, + // // Dev team + // { + // fullName: 'Alexander Rivail Ruiz', + // email: 'ruiz.smtr@gmail.com', + // password: this.generateRandomPassword(), + // role: new Role(RoleEnum.admin), + // status: new Status(StatusEnum.active), + // }, + // { + // fullName: 'Bernardo Marcos', + // email: 'bernardo.marcos64@gmail.com', + // password: this.generateRandomPassword(), + // role: new Role(RoleEnum.admin), + // status: new Status(StatusEnum.active), + // }, + // { + // fullName: 'Gabriel Fortes', + // email: 'glfg01092001@gmail.com', + // password: this.generateRandomPassword(), + // role: new Role(RoleEnum.admin), + // status: new Status(StatusEnum.active), + // }, + // { + // fullName: 'Raphael Baptista Rivas de Araújo', + // email: 'raphaelrivasbra@gmail.com', + // password: this.generateRandomPassword(), + // role: new Role(RoleEnum.admin), + // status: new Status(StatusEnum.active), + // }, + // { + // fullName: 'William FL 2007', + // email: 'williamfl2007@gmail.com', + // password: this.generateRandomPassword(), + // role: new Role(RoleEnum.admin), + // status: new Status(StatusEnum.active), + // }, + // // Admin CCT + // { + // fullName: 'Felipe Ribeiro', + // email: 'felipe.ribeiro@prefeitura.rio', + // password: this.generateRandomPassword(), + // role: new Role(RoleEnum.admin), + // status: new Status(StatusEnum.active), + // }, + // { + // fullName: 'Monique Lopes', + // email: 'monique.lopes@prefeitura.rio', + // password: this.generateRandomPassword(), + // role: new Role(RoleEnum.admin), + // status: new Status(StatusEnum.active), + // }, - // Admins - { - fullName: 'Jéssica Venancio Teixeira Cardoso Simas', - email: 'jessicasimas.smtr@gmail.com', - password: this.generateRandomPassword(), - role: new Role(RoleEnum.admin), - status: new Status(StatusEnum.active), - }, - { - fullName: 'Leandro Almeida dos Santos', - email: 'leandro.smtr@gmail.com', - password: this.generateRandomPassword(), - role: new Role(RoleEnum.admin), - status: new Status(StatusEnum.active), - }, - { - fullName: 'Carolina Maia dos Santos', - email: 'cms.smtr@gmail.com', - password: this.generateRandomPassword(), - role: new Role(RoleEnum.admin), - status: new Status(StatusEnum.active), - }, - { - fullName: 'Carolina Salomé Kingma Orlando', - email: 'carolkingma2013@gmail.com', - password: this.generateRandomPassword(), - role: new Role(RoleEnum.admin), - status: new Status(StatusEnum.active), - }, - { - fullName: 'Lauro Costa Silvestre', - email: 'laurosilvestre.smtr@gmail.com', - password: this.generateRandomPassword(), - role: new Role(RoleEnum.admin), - status: new Status(StatusEnum.active), - }, - { - fullName: 'Admin bigquery', - email: 'admin_bigquery@example.com', - password: 'secret', - role: { id: RoleEnum.admin } as Role, - status: { id: StatusEnum.active } as Status, - }, + // // Admins + // { + // fullName: 'Jéssica Venancio Teixeira Cardoso Simas', + // email: 'jessicasimas.smtr@gmail.com', + // password: this.generateRandomPassword(), + // role: new Role(RoleEnum.admin), + // status: new Status(StatusEnum.active), + // }, + // { + // fullName: 'Leandro Almeida dos Santos', + // email: 'leandro.smtr@gmail.com', + // password: this.generateRandomPassword(), + // role: new Role(RoleEnum.admin), + // status: new Status(StatusEnum.active), + // }, + // { + // fullName: 'Carolina Maia dos Santos', + // email: 'cms.smtr@gmail.com', + // password: this.generateRandomPassword(), + // role: new Role(RoleEnum.admin), + // status: new Status(StatusEnum.active), + // }, + // { + // fullName: 'Carolina Salomé Kingma Orlando', + // email: 'carolkingma2013@gmail.com', + // password: this.generateRandomPassword(), + // role: new Role(RoleEnum.admin), + // status: new Status(StatusEnum.active), + // }, + // { + // fullName: 'Lauro Costa Silvestre', + // email: 'laurosilvestre.smtr@gmail.com', + // password: this.generateRandomPassword(), + // role: new Role(RoleEnum.admin), + // status: new Status(StatusEnum.active), + // }, + // { + // fullName: 'Admin bigquery', + // email: 'admin_bigquery@example.com', + // password: 'secret', + // role: { id: RoleEnum.admin } as Role, + // status: { id: StatusEnum.active } as Status, + // }, - // Usuários lançamento financeiro - { - fullName: 'Usuário lançamento', - email: 'ruizalexander@id.uff.br', - password: this.generateRandomPassword(), - role: new Role(RoleEnum.lancador_financeiro), - status: new Status(StatusEnum.active), - }, - { - fullName: 'João Victor Spala', - email: 'jvspala.smtr@gmail.com', - password: this.generateRandomPassword(), - role: new Role(RoleEnum.lancador_financeiro), - status: new Status(StatusEnum.active), - }, - { - fullName: 'Marcia Marques', - email: 'marques.mcc@gmail.com', - password: this.generateRandomPassword(), - role: new Role(RoleEnum.lancador_financeiro), - status: new Status(StatusEnum.active), - }, - { - fullName: 'Letícia Correa', - email: 'leticiacorrea.smtr@gmail.com', - password: this.generateRandomPassword(), - role: new Role(RoleEnum.lancador_financeiro), - status: new Status(StatusEnum.active), - }, - { - fullName: 'Louise Sanglard', - email: 'louise.smtr@gmail.com', - password: this.generateRandomPassword(), - role: new Role(RoleEnum.lancador_financeiro), - status: new Status(StatusEnum.active), - }, - { - fullName: 'Simone Costa', - email: 'simonecosta.smtr@gmail.com', - password: this.generateRandomPassword(), - role: new Role(RoleEnum.admin_finan), - status: new Status(StatusEnum.active), - }, - { - fullName: 'Luciana Fernandes', - email: 'lucianafernandes.smtr@gmail.com', - password: this.generateRandomPassword(), - role: new Role(RoleEnum.admin_finan), - status: new Status(StatusEnum.active), - }, + // // Usuários lançamento financeiro + // { + // fullName: 'Usuário lançamento', + // email: 'ruizalexander@id.uff.br', + // password: this.generateRandomPassword(), + // role: new Role(RoleEnum.lancador_financeiro), + // status: new Status(StatusEnum.active), + // }, + // { + // fullName: 'João Victor Spala', + // email: 'jvspala.smtr@gmail.com', + // password: this.generateRandomPassword(), + // role: new Role(RoleEnum.lancador_financeiro), + // status: new Status(StatusEnum.active), + // }, + // { + // fullName: 'Marcia Marques', + // email: 'marques.mcc@gmail.com', + // password: this.generateRandomPassword(), + // role: new Role(RoleEnum.lancador_financeiro), + // status: new Status(StatusEnum.active), + // }, + // { + // fullName: 'Letícia Correa', + // email: 'leticiacorrea.smtr@gmail.com', + // password: this.generateRandomPassword(), + // role: new Role(RoleEnum.lancador_financeiro), + // status: new Status(StatusEnum.active), + // }, + // { + // fullName: 'Louise Sanglard', + // email: 'louise.smtr@gmail.com', + // password: this.generateRandomPassword(), + // role: new Role(RoleEnum.lancador_financeiro), + // status: new Status(StatusEnum.active), + // }, + // { + // fullName: 'Simone Costa', + // email: 'simonecosta.smtr@gmail.com', + // password: this.generateRandomPassword(), + // role: new Role(RoleEnum.admin_finan), + // status: new Status(StatusEnum.active), + // }, + // { + // fullName: 'Luciana Fernandes', + // email: 'lucianafernandes.smtr@gmail.com', + // password: this.generateRandomPassword(), + // role: new Role(RoleEnum.admin_finan), + // status: new Status(StatusEnum.active), + // }, - //apagar após teste - { - fullName: 'alex test seed approval', - email: 'approval@example.com', - password: 'secret', - permitCode: '', - cpfCnpj: this.cpfSamples?.[0], - role: { id: RoleEnum.lancador_financeiro } as Role, - status: { id: StatusEnum.active } as Status, - }, - { - fullName: 'teste approval', - email: 'approval@example.com', - password: 'secret', - permitCode: '', - cpfCnpj: this.cpfSamples?.[0], - role: { id: RoleEnum.lancador_financeiro } as Role, - status: { id: StatusEnum.active } as Status, - }, - { - fullName: 'teste launcher', - email: 'launcher@example.com', - password: 'secret', - permitCode: '', - cpfCnpj: this.cpfSamples?.[0], - role: { id: RoleEnum.aprovador_financeiro } as Role, - status: { id: StatusEnum.active } as Status, - }, + // //apagar após teste + // { + // fullName: 'alex test seed approval', + // email: 'approval@example.com', + // password: 'secret', + // permitCode: '', + // cpfCnpj: this.cpfSamples?.[0], + // role: { id: RoleEnum.lancador_financeiro } as Role, + // status: { id: StatusEnum.active } as Status, + // }, + // { + // fullName: 'teste approval', + // email: 'approval@example.com', + // password: 'secret', + // permitCode: '', + // cpfCnpj: this.cpfSamples?.[0], + // role: { id: RoleEnum.lancador_financeiro } as Role, + // status: { id: StatusEnum.active } as Status, + // }, + // { + // fullName: 'teste launcher', + // email: 'launcher@example.com', + // password: 'secret', + // permitCode: '', + // cpfCnpj: this.cpfSamples?.[0], + // role: { id: RoleEnum.aprovador_financeiro } as Role, + // status: { id: StatusEnum.active } as Status, + // }, // Development only ...(this.nodeEnv() === 'local' || this.nodeEnv() === 'test' ? ([ + // // Vanzeiros + // { + // fullName: 'Henrique Santos Template Cpf Van', + // email: 'henrique@example.com', + // password: 'secret', + // permitCode: '213890329890312', + // cpfCnpj: this.cpfSamples?.[0], + // role: { id: RoleEnum.user } as Role, + // status: { id: StatusEnum.active } as Status, + // bankAccount: '000000000567', + // bankAccountDigit: '8', + // }, + // { + // fullName: 'Márcia Clara Template Cnpj Brt etc', + // email: 'marcia@example.com', + // password: 'secret', + // permitCode: '319274392832023', + // cpfCnpj: this.cnpjSamples?.[0], + // role: { id: RoleEnum.user } as Role, + // status: { id: StatusEnum.active } as Status, + // }, + // // Roles + // { + // fullName: 'Usuário Teste dos Santos Oliveira', + // email: 'user@example.com', + // password: 'secret', + // permitCode: '213890329890749', + // cpfCnpj: this.cpfSamples?.[0], + // role: { id: RoleEnum.user } as Role, + // status: { id: StatusEnum.active } as Status, + // }, { - fullName: 'Henrique Santos Template Cpf Van', - email: 'henrique@example.com', - password: 'secret', - permitCode: '213890329890312', - cpfCnpj: this.cpfSamples?.[0], - role: { id: RoleEnum.user } as Role, - status: { id: StatusEnum.active } as Status, - bankAccount: '000000000567', - bankAccountDigit: '8', - }, - { - fullName: 'Márcia Clara Template Cnpj Brt etc', - email: 'marcia@example.com', - password: 'secret', - permitCode: '319274392832023', - cpfCnpj: this.cnpjSamples?.[0], - role: { id: RoleEnum.user } as Role, - status: { id: StatusEnum.active } as Status, - }, - { - fullName: 'Usuário Teste dos Santos Oliveira', - email: 'user@example.com', - password: 'secret', - permitCode: '213890329890749', - cpfCnpj: this.cpfSamples?.[0], - role: { id: RoleEnum.user } as Role, - status: { id: StatusEnum.active } as Status, - }, - { - fullName: 'Administrador Teste', - email: 'admin@example.com', - password: 'secret', + fullName: 'Administrador Financeiro Teste', + email: 'finan.admin@example.com', + password: 'ob>&+H%=&+H%=&+H%=&+H%= ({ - id: params.value, - })) - @ValidateValue((v) => isNumber(v.id) && v.id > 0, { - message: (v) => - `Is not a number > 0 but ${v.value?.id} (type ${typeof v.value?.id})`, - }) - id_cliente_favorecido: DeepPartial; + @Transform((params) => ({ id: params.value })) + @ValidateValue((v) => { + return isNumber(v.id) && v.id > 0 + }, { message: (v) => `Is not a number > 0 but ${v.value?.id} (type ${typeof v.value?.id})` })id_cliente_favorecido: DeepPartial; } diff --git a/src/lancamento/lancamento.controller.ts b/src/lancamento/lancamento.controller.ts index 4bd1ca79..668464b2 100644 --- a/src/lancamento/lancamento.controller.ts +++ b/src/lancamento/lancamento.controller.ts @@ -1,17 +1,4 @@ -import { - Body, - Controller, - Delete, - Get, - HttpCode, - HttpStatus, - Param, - Post, - Put, - Query, - Request, - UseGuards, -} from '@nestjs/common'; +import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put, Query, Request, UseGuards } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; import { ApiBearerAuth, ApiBody, ApiQuery, ApiTags } from '@nestjs/swagger'; import { Roles } from 'src/roles/roles.decorator'; @@ -21,6 +8,8 @@ import { LancamentoDto } from './dtos/lancamentoDto'; import { ItfLancamento } from './interfaces/lancamento.interface'; import { LancamentoService } from './lancamento.service'; import { AutorizaLancamentoDto } from './dtos/AutorizaLancamentoDto'; +import { ApiDescription } from 'src/utils/api-param/description-api-param'; +import { ParseNumberPipe } from 'src/utils/pipes/parse-number.pipe'; @ApiTags('Lancamento') @Controller({ @@ -33,72 +22,40 @@ export class LancamentoController { @ApiBearerAuth() @UseGuards(AuthGuard('jwt'), RolesGuard) @Roles( - RoleEnum.master, + RoleEnum.master, // RoleEnum.admin_finan, RoleEnum.lancador_financeiro, RoleEnum.aprovador_financeiro, ) @Get('/') - @ApiQuery({ - name: 'mes', - type: Number, - required: false, - description: 'Mês do lançamento', - }) - @ApiQuery({ - name: 'periodo', - type: Number, - required: false, - description: - 'Período do lançamento. primeira quinzena ou segunda quinzena.', - }) - @ApiQuery({ - name: 'ano', - type: Number, - required: false, - description: 'Ano do lançamento.', - }) - @ApiQuery({ - name: 'autorizado', - type: Number, - required: false, - description: - 'use 1 ou 0 (ou deixe vazio) para filtrar por autorizado ou não autorizado.', - }) + @ApiQuery({ name: 'mes', type: Number, required: false, description: 'Mês do lançamento' }) + @ApiQuery({ name: 'periodo', type: Number, required: false, description: ApiDescription({ _: 'Período do lançamento. Primeira quinzena ou segunda quinzena.', min: 1, max: 2 }) }) + @ApiQuery({ name: 'ano', type: Number, required: false, description: 'Ano do lançamento.' }) + @ApiQuery({ name: 'autorizado', type: Number, required: false, description: 'use 1 ou 0 (ou deixe vazio) para filtrar por autorizado ou não autorizado.' }) @HttpCode(HttpStatus.OK) async getLancamento( - @Request() request, + @Request() request, // @Query('mes') mes: number, - @Query('periodo') periodo: number, + @Query('periodo', new ParseNumberPipe({ min: 1, max: 2, optional: true })) periodo: number | undefined, @Query('ano') ano: number, @Query('autorizado') authorized: number, ): Promise { - return await this.lancamentoService.findByPeriod( - mes, - periodo, - ano, - authorized, - ); + return await this.lancamentoService.findByPeriod(mes, periodo, ano, authorized); } - @ApiBearerAuth() @UseGuards(AuthGuard('jwt'), RolesGuard) @Roles( - RoleEnum.master, + RoleEnum.master, // RoleEnum.admin_finan, RoleEnum.lancador_financeiro, RoleEnum.aprovador_financeiro, ) @Get('/getbystatus') - @ApiQuery({ - name: 'status', - required: false, - description: 'use 1 ou 0 para autorizado ou não autorizado.', - }) + @ApiQuery({ name: 'status', required: false, description: 'use 1 ou 0 para autorizado ou não autorizado.' }) @HttpCode(HttpStatus.OK) async getByStatus( - @Request() request, + @Request() request, // @Query('status') status: number, ): Promise { return await this.lancamentoService.findByStatus(status); @@ -107,31 +64,18 @@ export class LancamentoController { @ApiBearerAuth() @UseGuards(AuthGuard('jwt'), RolesGuard) @Roles( - RoleEnum.master, + RoleEnum.master, // RoleEnum.admin_finan, RoleEnum.lancador_financeiro, RoleEnum.aprovador_financeiro, ) @Get('/getValorAutorizado') - @ApiQuery({ - name: 'mes', - required: true, - description: 'Mês do lançamento', - }) - @ApiQuery({ - name: 'periodo', - required: true, - description: - 'Período do lançamento. primeira quinzena ou segunda quinzena.', - }) - @ApiQuery({ - name: 'ano', - required: true, - description: 'Ano do lançamento.', - }) + @ApiQuery({ name: 'mes', required: true, description: 'Mês do lançamento' }) + @ApiQuery({ name: 'periodo', required: true, description: 'Período do lançamento. primeira quinzena ou segunda quinzena.' }) + @ApiQuery({ name: 'ano', required: true, description: 'Ano do lançamento.' }) @HttpCode(HttpStatus.OK) async getValorAutorizado( - @Request() request, + @Request() request, // @Query('mes') mes: number, @Query('periodo') periodo: number, @Query('ano') ano: number, @@ -142,7 +86,7 @@ export class LancamentoController { @ApiBearerAuth() @UseGuards(AuthGuard('jwt'), RolesGuard) @Roles( - RoleEnum.master, + RoleEnum.master, // RoleEnum.admin_finan, RoleEnum.lancador_financeiro, RoleEnum.aprovador_financeiro, @@ -151,45 +95,38 @@ export class LancamentoController { @HttpCode(HttpStatus.CREATED) @Post('/create') async postCreateLancamento( - @Request() req: any, - @Body() lancamentoData: LancamentoDto, // It was ItfLancamento + @Request() req: any, // + @Body() lancamentoData: LancamentoDto, ): Promise { const userId = req.user.id; - const createdLancamento = await this.lancamentoService.create( - lancamentoData, - userId, - ); + const createdLancamento = await this.lancamentoService.create(lancamentoData, userId); return createdLancamento.toItfLancamento(); } @ApiBearerAuth() @UseGuards(AuthGuard('jwt'), RolesGuard) - @Roles(RoleEnum.master, RoleEnum.admin_finan, RoleEnum.aprovador_financeiro) + @Roles( + RoleEnum.master, // + RoleEnum.admin_finan, + RoleEnum.aprovador_financeiro, + ) @Put('/authorize') - @ApiQuery({ - name: 'lancamentoId', - required: true, - description: 'Id do lançamento', - }) + @ApiQuery({ name: 'lancamentoId', required: true, description: 'Id do lançamento' }) @ApiBody({ type: AutorizaLancamentoDto }) @HttpCode(HttpStatus.OK) async putAutorizarPagamento( - @Request() req, + @Request() req, // @Body() autorizaLancamentoDto: AutorizaLancamentoDto, ) { const userId = req.user.id; const lancamentoId = req.query.lancamentoId; - return await this.lancamentoService.autorizarPagamento( - userId, - lancamentoId, - autorizaLancamentoDto, - ); + return await this.lancamentoService.autorizarPagamento(userId, lancamentoId, autorizaLancamentoDto); } @ApiBearerAuth() @UseGuards(AuthGuard('jwt'), RolesGuard) @Roles( - RoleEnum.master, + RoleEnum.master, // RoleEnum.admin_finan, RoleEnum.lancador_financeiro, RoleEnum.aprovador_financeiro, @@ -197,11 +134,7 @@ export class LancamentoController { @Put('/') @ApiBody({ type: LancamentoDto }) @HttpCode(HttpStatus.OK) - @ApiQuery({ - name: 'lancamentoId', - required: true, - description: 'Id do lançamento', - }) + @ApiQuery({ name: 'lancamentoId', required: true, description: 'Id do lançamento' }) async atualizaLancamento( @Request() req, @Body() lancamentoData: LancamentoDto, // It was ItfLancamento @@ -214,7 +147,7 @@ export class LancamentoController { @ApiBearerAuth() @UseGuards(AuthGuard('jwt'), RolesGuard) @Roles( - RoleEnum.master, + RoleEnum.master, // RoleEnum.admin_finan, RoleEnum.lancador_financeiro, RoleEnum.aprovador_financeiro, @@ -230,4 +163,4 @@ export class LancamentoController { async deleteById(@Param('id') id: number) { return await this.lancamentoService.delete(id); } -} \ No newline at end of file +} diff --git a/src/lancamento/lancamento.service.ts b/src/lancamento/lancamento.service.ts index f9f07783..d6e81d77 100644 --- a/src/lancamento/lancamento.service.ts +++ b/src/lancamento/lancamento.service.ts @@ -57,19 +57,17 @@ export class LancamentoService { } async findByPeriod( - month: number | null = null, - period: number | null = null, - year: number | null = null, - authorized: number | null = null, + month?: number, + period?: number, + year?: number, + authorized?: number, ): Promise { let startDate: Date | undefined; let endDate: Date | undefined; this.logger.debug(String(`Find Lancamento by period: ${period}`)); if ( - month !== null && - period !== null && - year !== null && + month && period && year && !isNaN(month) && !isNaN(period) && !isNaN(year) && diff --git a/test/admin/lancamento.e2e-spec.ts b/test/admin/lancamento.e2e-spec.ts new file mode 100644 index 00000000..0f457bc7 --- /dev/null +++ b/test/admin/lancamento.e2e-spec.ts @@ -0,0 +1,430 @@ +import { HttpStatus } from '@nestjs/common'; +import { differenceInSeconds } from 'date-fns'; +import * as fs from 'fs'; +import { generate } from 'gerador-validador-cpf'; +import * as path from 'path'; +import * as request from 'supertest'; +import * as XLSX from 'xlsx'; +import { ADMIN_EMAIL, ADMIN_PASSWORD, APP_URL, LICENSEE_CASE_ACCENT, LICENSEE_CPF_PERMIT_CODE, MAILDEV_URL, TO_UPDATE_PERMIT_CODE } from '../utils/constants'; +import { parseStringUpperUnaccent } from 'src/utils/string-utils'; + +describe('Admin Financeiro managing Lancamento (e2e)', () => { + const app = APP_URL; + const tempFolder = path.join(__dirname, 'temp'); + let apiToken: any = {}; + + beforeAll(async () => { + await request(app) + .post('/api/v1/auth/admin/email/login') + .send({ email: ADMIN_EMAIL, password: ADMIN_PASSWORD }) + .then(({ body }) => { + apiToken = body.token; + }); + + if (!fs.existsSync(tempFolder)) { + fs.mkdirSync(tempFolder); + } + }); + + describe('Setup tests', () => { + it('should have UTC and local timezones', () => { + new Date().getTimezoneOffset(); + expect(process.env.TZ).toEqual('UTC'); + expect(global.__localTzOffset).toBeDefined(); + }); + + it('should have mailDev server', async () => { + await request(MAILDEV_URL).get('').expect(HttpStatus.OK); + }); + }); + + describe('Manage users', () => { + test('Filter users', /** + * Requirement: 2023/11/16 {@link https://github.com/RJ-SMTR/api-cct/issues/94#issuecomment-1815016208 #94, item 7 - GitHub} + */ async () => { + // Arrange + await request(app).get('/api/v1/test/users/reset-testing-users').expect(200); + const licensee = await request(app) + .get('/api/v1/users/') + .auth(apiToken, { + type: 'bearer', + }) + .query({ permitCode: LICENSEE_CPF_PERMIT_CODE }) + .expect(({ body }) => { + expect(body.data?.length).toBe(1); + }) + .then(({ body }) => body.data); + const licenseePartOfName = 'user'; + const args = [ + { + filter: { name: parseStringUpperUnaccent(LICENSEE_CASE_ACCENT) }, + expect: (body: any) => expect(body.data.some((i: any) => i.fullName === LICENSEE_CASE_ACCENT)).toBeTruthy(), + }, + { + filter: { permitCode: licensee.permitCode }, + expect: (body: any) => expect(body.data.some((i: any) => i.permitCode === LICENSEE_CPF_PERMIT_CODE)).toBeTruthy(), + }, + { + filter: { name: licensee.fullName }, + expect: (body: any) => expect(body.data.some((i: any) => i.permitCode === LICENSEE_CPF_PERMIT_CODE)).toBeTruthy(), + }, + { + filter: { email: licensee.email }, + expect: (body: any) => expect(body.data.some((i: any) => i.permitCode === LICENSEE_CPF_PERMIT_CODE)).toBeTruthy(), + }, + { + filter: { name: licenseePartOfName, inviteStatus: 'queued' }, + expect: (body: any) => expect(body.data.some((i: any) => i.fullName === 'Queued user')).toBeTruthy(), + }, + { + filter: { name: licenseePartOfName, inviteStatus: 'sent' }, + expect: (body: any) => expect(body.data.some((i: any) => i.fullName === 'Sent user')).toBeTruthy(), + }, + { + filter: { name: licenseePartOfName, inviteStatus: 'used' }, + expect: (body: any) => expect(body.data.some((i: any) => i.fullName === 'Used user')).toBeTruthy(), + }, + ]; + + // Assert + for (const arg of args) { + await request(app) + .get('/api/v1/users/') + .auth(apiToken, { + type: 'bearer', + }) + .query(arg.filter) + .expect(HttpStatus.OK) + .then(({ body }) => { + arg.expect(body); + return body.data; + }); + } + }, 20000); + }); + + describe('Upload users', () => { + let users: any[] = []; + + test(`Upload users, status = 'queued'`, /** + * Requirement: 2023/11/16 {@link https://github.com/RJ-SMTR/api-cct/issues/94#issuecomment-1815016208 #94, item 3 - GitHub} + */ async () => { + // Arrange + const randomCode = Math.random().toString(36).slice(-8); + const uploadUsers = [ + { + codigo_permissionario: `permitCode_${randomCode}`, + nome: `Café_${randomCode}`, + email: `user.${randomCode}@test.com`, + telefone: `219${Math.random().toString().slice(2, 10)}`, + cpf: generate(), + }, + ]; + const excelFilePath = path.join(tempFolder, 'newUsers.xlsx'); + const workbook = XLSX.utils.book_new(); + const worksheet = XLSX.utils.json_to_sheet(uploadUsers); + XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1'); + XLSX.writeFile(workbook, excelFilePath); + + // Assert + await request(app) + .post('/api/v1/users/upload') + .auth(apiToken, { + type: 'bearer', + }) + .attach('file', excelFilePath) + .expect(HttpStatus.CREATED) + .expect(({ body }) => { + expect(body.uploadedUsers).toEqual(1); + }); + + users = await request(app) + .get('/api/v1/users/') + .auth(apiToken, { + type: 'bearer', + }) + .query({ permitCode: uploadUsers[0].codigo_permissionario }) + .expect(({ body }) => { + expect(body.data?.length).toBe(1); + expect(body.data[0]?.fullName).toEqual(parseStringUpperUnaccent(uploadUsers[0].nome)); + expect(body.data[0]?.aux_inviteStatus?.name).toEqual('queued'); + }) + .then(({ body }) => body.data); + }); + + test(`Resend new user invite, status = 'sent'`, /** + * Requirement: 2023/11/16 {@link https://github.com/RJ-SMTR/api-cct/issues/94#issuecomment-1815016208 #94, item 4 - GitHub} + */ async () => { + const newUser = users[0]; + expect(newUser?.id).toBeDefined(); + + await request(APP_URL) + .post('/api/v1/auth/email/resend') + .auth(apiToken, { + type: 'bearer', + }) + .send({ + id: newUser.id, + }) + .expect(HttpStatus.NO_CONTENT); + const forgotLocalDate = new Date(); + forgotLocalDate.setMinutes(forgotLocalDate.getMinutes() + global.__localTzOffset); + + newUser.hash = await request(MAILDEV_URL) + .get('/email') + .then(({ body }) => + (body as any[]) + .filter((letter: any) => letter.to[0].address.toLowerCase() === newUser.email.toLowerCase() && /.*conclude\-registration\/(\w+).*/g.test(letter.text) && differenceInSeconds(forgotLocalDate, new Date(letter.date)) <= 10) + .pop() + ?.text.replace(/.*conclude\-registration\/(\w+).*/g, '$1'), + ); + + await request(APP_URL) + .post(`/api/v1/auth/licensee/invite/${newUser.hash}`) + .expect(HttpStatus.OK) + .expect(({ body }) => { + expect(body.email).toEqual(newUser.email); + expect(body?.inviteStatus?.name).toEqual('sent'); + }); + + users[0] = newUser; + }); + + test(`New user conclude registration, status = 'used'`, /** + * Requirement: 2023/11/16 {@link https://github.com/RJ-SMTR/api-cct/issues/94#issuecomment-1815016208 #94, item 5 - GitHub} + */ async () => { + const newUser = users[0]; + expect(newUser?.hash).toBeDefined(); + + const newPassword = Math.random().toString(36).slice(-8); + await request(APP_URL) + .post(`/api/v1/auth/licensee/register/${newUser.hash}`) + .send({ password: newPassword }) + .expect(HttpStatus.OK) + .expect(({ body }) => { + expect(body.user.aux_inviteStatus?.name).toEqual('used'); + expect(body.token).toBeDefined(); + }); + + newUser.password = newPassword; + users[0] = newUser; + }); + + test('New user login', /** + * Requirement: 2023/11/16 {@link https://github.com/RJ-SMTR/api-cct/issues/94#issuecomment-1815016208 #94, item 6 - GitHub} + */ async () => { + const newUser = users[0]; + await request(APP_URL) + .post(`/api/v1/auth/licensee/login`) + .send({ permitCode: newUser.permitCode, password: newUser.password }) + .expect(HttpStatus.OK) + .expect(({ body }) => { + expect(body.token).toBeDefined(); + }); + }); + + test('Upload invalid cpf, block upload', /** + * Requirement: 2023/11/16 {@link https://github.com/RJ-SMTR/api-cct/issues/94#issuecomment-1815016208 #94, item 3 - GitHub} + */ async () => { + // Arrange + const randomCode1 = Math.random().toString(36).slice(-8); + const randomCode2 = Math.random().toString(36).slice(-8); + const uploadUsers = [ + { + codigo_permissionario: `permitCode_${randomCode1}`, + nome: `Café_${randomCode1}`, + email: `user.${randomCode1}@mai.com`, + telefone: `219${Math.random().toString().slice(2, 10)}`, + cpf: `invalid_cpf_${randomCode1}`, + }, + { + codigo_permissionario: `permitCode_${randomCode2}`, + nome: `Café_${randomCode2}`, + email: `user.${randomCode2}@mai.com`, + telefone: `219${Math.random().toString().slice(2, 10)}`, + cpf: `invalid_cpf_${randomCode2}`, + }, + ]; + const excelFilePath = path.join(tempFolder, 'newUsers.xlsx'); + const workbook = XLSX.utils.book_new(); + const worksheet = XLSX.utils.json_to_sheet(uploadUsers); + XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1'); + XLSX.writeFile(workbook, excelFilePath); + + // Assert + await request(app) + .post('/api/v1/users/upload') + .auth(apiToken, { + type: 'bearer', + }) + .attach('file', excelFilePath) + .expect(HttpStatus.UNPROCESSABLE_ENTITY) + .expect(({ body }) => { + const invalidRows = body.error.file.invalidRows; + expect(invalidRows.length).toBeGreaterThan(0); + expect(invalidRows[0].errors).toMatchObject({ + cpf: 'CPF inválido', + }); + }); + }); + + test('Patch cpf, throw error', /** + * Requirement: 2023/11/16 {@link https://github.com/RJ-SMTR/api-cct/issues/94#issuecomment-1815016208 #94, item 3 - GitHub} + */ async () => { + const user = await request(app) + .get('/api/v1/users/') + .auth(apiToken, { + type: 'bearer', + }) + .query({ permitCode: TO_UPDATE_PERMIT_CODE }) + .expect(({ body }) => { + expect(body.data.length).toBe(1); + }) + .then(({ body }) => body.data[0]); + + // Assert + await request(app) + .patch(`/api/v1/users/${user.id}`) + .auth(apiToken, { + type: 'bearer', + }) + .send({ + cpfCnpj: generate(), + }) + .expect(HttpStatus.UNPROCESSABLE_ENTITY); + }); + + test('Patch bankAccount, success', /** + * Requirement: 2023/11/16 {@link https://github.com/RJ-SMTR/api-cct/issues/94#issuecomment-1815016208 #94, item 3 - GitHub} + */ async () => { + // Arrange + const random4Nums = Math.floor(1000 + Math.random() * 9000); + const user = await request(app) + .get('/api/v1/users/') + .auth(apiToken, { + type: 'bearer', + }) + .query({ permitCode: TO_UPDATE_PERMIT_CODE }) + .expect(({ body }) => { + expect(body.data.length).toBe(1); + }) + .then(({ body }) => body.data[0]); + + // Assert + await request(app) + .patch(`/api/v1/users/${user.id}`) + .auth(apiToken, { + type: 'bearer', + }) + .send({ + bankAccount: random4Nums, + }) + .expect(HttpStatus.OK) + .expect(({ body }) => { + expect(body.bankAccount).toEqual(String(random4Nums)); + }); + }); + + test('Upload invalid cpf, block upload', /** + * Requirement: 2023/11/16 {@link https://github.com/RJ-SMTR/api-cct/issues/94#issuecomment-1815016208 #94, item 3 - GitHub} + */ async () => { + // Arrange + const randomCode1 = Math.random().toString(36).slice(-8); + const randomCode2 = Math.random().toString(36).slice(-8); + const uploadUsers = [ + { + codigo_permissionario: `permitCode_${randomCode1}`, + nome: `Café_${randomCode1}`, + email: `user.${randomCode1}@mai.com`, + telefone: `219${Math.random().toString().slice(2, 10)}`, + cpf: `invalid_cpf_${randomCode1}`, + }, + { + codigo_permissionario: `permitCode_${randomCode2}`, + nome: `Café_${randomCode2}`, + email: `user.${randomCode2}@mai.com`, + telefone: `219${Math.random().toString().slice(2, 10)}`, + cpf: `invalid_cpf_${randomCode2}`, + }, + ]; + const excelFilePath = path.join(tempFolder, 'newUsers.xlsx'); + const workbook = XLSX.utils.book_new(); + const worksheet = XLSX.utils.json_to_sheet(uploadUsers); + XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1'); + XLSX.writeFile(workbook, excelFilePath); + + // Assert + await request(app) + .post('/api/v1/users/upload') + .auth(apiToken, { + type: 'bearer', + }) + .attach('file', excelFilePath) + .expect(HttpStatus.UNPROCESSABLE_ENTITY) + .expect(({ body }) => { + const invalidRows = body.error.file.invalidRows; + expect(invalidRows.length).toBeGreaterThan(0); + expect(invalidRows[0].errors).toMatchObject({ + cpf: 'CPF inválido', + }); + }); + }); + + test('Patch cpf, throw error', /** + * Requirement: 2023/11/16 {@link https://github.com/RJ-SMTR/api-cct/issues/94#issuecomment-1815016208 #94, item 3 - GitHub} + */ async () => { + const user = await request(app) + .get('/api/v1/users/') + .auth(apiToken, { + type: 'bearer', + }) + .query({ permitCode: TO_UPDATE_PERMIT_CODE }) + .expect(({ body }) => { + expect(body.data.length).toBe(1); + }) + .then(({ body }) => body.data[0]); + + // Assert + await request(app) + .patch(`/api/v1/users/${user.id}`) + .auth(apiToken, { + type: 'bearer', + }) + .send({ + cpfCnpj: generate(), + }) + .expect(HttpStatus.UNPROCESSABLE_ENTITY); + }); + + test('Patch bankAccount, success', /** + * Requirement: 2023/11/16 {@link https://github.com/RJ-SMTR/api-cct/issues/94#issuecomment-1815016208 #94, item 3 - GitHub} + */ async () => { + // Arrange + const random4Nums = Math.floor(1000 + Math.random() * 9000); + const user = await request(app) + .get('/api/v1/users/') + .auth(apiToken, { + type: 'bearer', + }) + .query({ permitCode: TO_UPDATE_PERMIT_CODE }) + .expect(({ body }) => { + expect(body.data.length).toBe(1); + }) + .then(({ body }) => body.data[0]); + + // Assert + await request(app) + .patch(`/api/v1/users/${user.id}`) + .auth(apiToken, { + type: 'bearer', + }) + .send({ + bankAccount: random4Nums, + }) + .expect(HttpStatus.OK) + .expect(({ body }) => { + expect(body.bankAccount).toEqual(String(random4Nums)); + }); + }); + }); +}); From 40992e65f82950737ea9266629d961221415274a Mon Sep 17 00:00:00 2001 From: Raphael Rivas Date: Wed, 28 Aug 2024 16:40:12 -0300 Subject: [PATCH 02/29] feat: cliente-favorecido consorcio type --- .../bank-statements.controller.ts | 12 ++-- src/cnab/cnab.controller.ts | 14 ++-- .../get-cliente-favorecido-consorcio.enum.ts | 4 ++ .../cliente-favorecido-find-by.interface.ts | 1 + .../cliente-favorecido.repository.ts | 19 ++++- .../service/cliente-favorecido.service.ts | 71 +++++-------------- .../cliente-favorecido-seed-data.ts | 18 ++--- src/tipo-favorecido/tipo-favorecido.enum.ts | 4 +- src/utils/pipes/validate-enum.pipe.ts | 31 ++++---- 9 files changed, 77 insertions(+), 97 deletions(-) create mode 100644 src/cnab/enums/get-cliente-favorecido-consorcio.enum.ts diff --git a/src/bank-statements/bank-statements.controller.ts b/src/bank-statements/bank-statements.controller.ts index e16609c5..e3f62185 100644 --- a/src/bank-statements/bank-statements.controller.ts +++ b/src/bank-statements/bank-statements.controller.ts @@ -10,7 +10,7 @@ import { TimeIntervalEnum } from 'src/utils/enums/time-interval.enum'; import { getPagination } from 'src/utils/get-pagination'; import { IRequest } from 'src/utils/interfaces/request.interface'; import { ParseNumberPipe } from 'src/utils/pipes/parse-number.pipe'; -import { ValidateEnumPipe } from 'src/utils/pipes/validate-enum.pipe'; +import { ParseEnumPipe } from 'src/utils/pipes/validate-enum.pipe'; import { DateQueryParams } from 'src/utils/query-param/date.query-param'; import { PaginationQueryParams } from 'src/utils/query-param/pagination.query-param'; import { getRequestLog } from 'src/utils/request-utils'; @@ -60,12 +60,10 @@ export class BankStatementsController { @ApiQuery(CommonApiParams.userId) @HttpCode(HttpStatus.OK) async getMe( - @Request() request: IRequest, + @Request() request: IRequest, // @Query(...DateQueryParams.yearMonth) yearMonth: string, - @Query('timeInterval', new ValidateEnumPipe(BSMeTimeIntervalEnum, false, BSMeTimeIntervalEnum.LAST_MONTH)) - timeInterval?: BSMeTimeIntervalEnum | undefined, - @Query('userId', new ParseNumberPipe({ min: 1, optional: true })) - userId?: number | null, + @Query('timeInterval', new ParseEnumPipe(BSMeTimeIntervalEnum, { optional: true, defaultValue: BSMeTimeIntervalEnum.LAST_MONTH })) timeInterval: BSMeTimeIntervalEnum | undefined, + @Query('userId', new ParseNumberPipe({ min: 1, optional: true })) userId?: number | null, ): Promise { this.logger.log(getRequestLog(request)); @@ -118,7 +116,7 @@ export class BankStatementsController { @Query(...PaginationQueryParams.page) page: number, @Query(...PaginationQueryParams.limit) limit: number, @Query('endDate', new ParseDatePipe()) endDate: string, - @Query('timeInterval', new ValidateEnumPipe(BSMePrevDaysTimeIntervalEnum, true)) timeInterval: BSMePrevDaysTimeIntervalEnum, + @Query('timeInterval', new ParseEnumPipe(BSMePrevDaysTimeIntervalEnum)) timeInterval: BSMePrevDaysTimeIntervalEnum, @Query('userId', new ParseNumberPipe({ min: 1, optional: true })) userId?: number | null, ): Promise> { const isUserIdParam = userId !== null && !isNaN(Number(userId)); diff --git a/src/cnab/cnab.controller.ts b/src/cnab/cnab.controller.ts index 35601f41..70f0286b 100644 --- a/src/cnab/cnab.controller.ts +++ b/src/cnab/cnab.controller.ts @@ -15,6 +15,8 @@ import { ArquivoPublicacaoService } from './service/arquivo-publicacao.service'; import { ClienteFavorecidoService } from './service/cliente-favorecido.service'; import { ExtratoDto } from './service/dto/extrato.dto'; import { ExtratoHeaderArquivoService } from './service/extrato/extrato-header-arquivo.service'; +import { GetClienteFavorecidoConsorcioEnum } from './enums/get-cliente-favorecido-consorcio.enum'; +import { ParseEnumPipe } from 'src/utils/pipes/validate-enum.pipe'; @ApiTags('Cnab') @Controller({ @@ -35,17 +37,17 @@ export class CnabController { @UseGuards(AuthGuard('jwt'), RolesGuard) @Roles(RoleEnum.master, RoleEnum.admin, RoleEnum.admin_finan, RoleEnum.lancador_financeiro, RoleEnum.aprovador_financeiro) @ApiBearerAuth() - @ApiQuery({ name: 'nome', description: 'Pesquisa por parte do nome, sem distinção de acento ou maiúsculas.', required: false, type: String, example: 'joao' }) - @ApiQuery({ name: 'consorcio', description: 'Nome do consorcio - salvar transações', required: true, type: String, example: 'Todos / Van / Empresa /Nome Consorcio' }) - @ApiQuery({ name: 'limit', description: 'Itens exibidos por página', required: false, type: Number, example: 1 }) - @ApiQuery({ name: 'page', description: ApiDescription({ description: 'Itens exibidos por página', min: 1 }), required: false, type: Number, example: 1 }) + @ApiQuery({ name: 'nome', description: 'Pesquisa por parte do nome, sem distinção de acento ou maiúsculas.', required: false, type: String }) + @ApiQuery({ name: 'consorcio', description: 'Nome do consorcio', required: false, enum: GetClienteFavorecidoConsorcioEnum }) + @ApiQuery({ name: 'limit', description: ApiDescription({ _: 'Itens exibidos por página', min: 1 }), required: false, type: Number }) + @ApiQuery({ name: 'page', description: ApiDescription({ _: 'Itens exibidos por página', min: 1 }), required: false, type: Number }) getClienteFavorecido( @Query('nome', new ParseArrayPipe({ items: String, separator: ',', optional: true })) nome: string[], // - @Query('consorcio') consorcio: string, + @Query('consorcio', new ParseEnumPipe(GetClienteFavorecidoConsorcioEnum, { optional: true })) consorcio: GetClienteFavorecidoConsorcioEnum | undefined, @Query('limit', new ParseNumberPipe({ min: 0, optional: true })) limit: number | undefined, @Query('page', new ParseNumberPipe({ min: 1, optional: true })) page: number | undefined, ): Promise { - return this.clienteFavorecidoService.findBy({ nome, limit, page }); + return this.clienteFavorecidoService.findBy({ nome, limit, page, consorcio }); } @Get('extratoLancamento') diff --git a/src/cnab/enums/get-cliente-favorecido-consorcio.enum.ts b/src/cnab/enums/get-cliente-favorecido-consorcio.enum.ts new file mode 100644 index 00000000..c1f2eb23 --- /dev/null +++ b/src/cnab/enums/get-cliente-favorecido-consorcio.enum.ts @@ -0,0 +1,4 @@ +export enum GetClienteFavorecidoConsorcioEnum { + Van = 'Van', + Empresa = 'Empresa', +} diff --git a/src/cnab/interfaces/cliente-favorecido-find-by.interface.ts b/src/cnab/interfaces/cliente-favorecido-find-by.interface.ts index 806c99d8..599fcdda 100644 --- a/src/cnab/interfaces/cliente-favorecido-find-by.interface.ts +++ b/src/cnab/interfaces/cliente-favorecido-find-by.interface.ts @@ -1,6 +1,7 @@ export interface IClienteFavorecidoFindBy { /** ILIKE unaccent */ nome?: string[]; + consorcio?: string; limit?: number; page?: number; } diff --git a/src/cnab/repository/cliente-favorecido.repository.ts b/src/cnab/repository/cliente-favorecido.repository.ts index 3a1b0b3d..c4e3b035 100644 --- a/src/cnab/repository/cliente-favorecido.repository.ts +++ b/src/cnab/repository/cliente-favorecido.repository.ts @@ -97,6 +97,7 @@ export class ClienteFavorecidoRepository { public async findManyBy(where?: IClienteFavorecidoFindBy): Promise { let isFirstWhere = false; let qb = this.clienteFavorecidoRepository.createQueryBuilder('favorecido'); + function cmd() { if (isFirstWhere) { isFirstWhere = false; @@ -105,6 +106,7 @@ export class ClienteFavorecidoRepository { return 'andWhere'; } } + if (where?.nome?.length) { for (const nome of where.nome) { qb = qb[cmd()]('favorecido."nome" ILIKE UNACCENT(UPPER(:nome))', { @@ -113,6 +115,15 @@ export class ClienteFavorecidoRepository { } } + if (where?.consorcio) { + const consorcio = where.consorcio; + if (consorcio === 'Van') { + qb = qb[cmd()](' favorecido.tipo = :tipo', { tipo: 'vanzeiro' }); + } else if (consorcio === 'Empresa') { + qb = qb[cmd()](' favorecido.tipo = :tipo', { tipo: 'consorcio'}); + } + } + if (where?.limit && where?.page) { const skip = where?.limit * (where?.page - 1); qb = qb.take(where?.limit).skip(skip); @@ -148,9 +159,13 @@ export class ClienteFavorecidoRepository { compactQuery(` SELECT cf.* FROM cliente_favorecido cf - ${where?.detalheANumeroDocumento ? `INNER JOIN item_transacao it ON it."clienteFavorecidoId" = cf.id + ${ + 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` : ''} + INNER JOIN detalhe_a da ON da."itemTransacaoAgrupadoId" = ita.id` + : '' + } ${qWhere.query} ORDER BY cf.id `), diff --git a/src/cnab/service/cliente-favorecido.service.ts b/src/cnab/service/cliente-favorecido.service.ts index 38efd62f..748da522 100644 --- a/src/cnab/service/cliente-favorecido.service.ts +++ b/src/cnab/service/cliente-favorecido.service.ts @@ -20,9 +20,7 @@ export class ClienteFavorecidoService { timestamp: true, }); - constructor( - private clienteFavorecidoRepository: ClienteFavorecidoRepository, - ) {} + constructor(private clienteFavorecidoRepository: ClienteFavorecidoRepository) {} /** * All ClienteFavoecidos will be created or updated from users based of cpfCnpj. @@ -43,9 +41,7 @@ export class ClienteFavorecidoService { await this.clienteFavorecidoRepository.upsert(newFavorecidos); } - public async findBy( - where?: IClienteFavorecidoFindBy, - ): Promise { + public async findBy(where?: IClienteFavorecidoFindBy): Promise { return await this.clienteFavorecidoRepository.findManyBy(where); } @@ -55,9 +51,7 @@ export class ClienteFavorecidoService { }); } - public async findManyFromLancamentos( - lancamentos: LancamentoEntity[], - ): Promise { + public async findManyFromLancamentos(lancamentos: LancamentoEntity[]): Promise { const ids = [...new Set(lancamentos.map((i) => i.id_cliente_favorecido))]; return await this.clienteFavorecidoRepository.findMany({ where: { @@ -66,17 +60,8 @@ export class ClienteFavorecidoService { }); } - public async findManyFromOrdens( - ordens: BigqueryOrdemPagamentoDTO[], - ): Promise { - const documentos = ordens.reduce( - (l, i) => [ - ...l, - ...(i.consorcioCnpj ? [i.consorcioCnpj] : []), - ...(i.operadoraCpfCnpj ? [i.operadoraCpfCnpj] : []), - ], - [], - ); + public async findManyFromOrdens(ordens: BigqueryOrdemPagamentoDTO[]): Promise { + const documentos = ordens.reduce((l, i) => [...l, ...(i.consorcioCnpj ? [i.consorcioCnpj] : []), ...(i.operadoraCpfCnpj ? [i.operadoraCpfCnpj] : [])], []); const uniqueDocumentos = [...new Set(documentos)]; return await this.clienteFavorecidoRepository.findMany({ where: { @@ -93,18 +78,12 @@ export class ClienteFavorecidoService { return await this.clienteFavorecidoRepository.findAll(); } - public async getOneByIdClienteFavorecido( - idClienteFavorecido: number, - ): Promise { + public async getOneByIdClienteFavorecido(idClienteFavorecido: number): Promise { const cliente_favorecido = await this.clienteFavorecidoRepository.getOne({ id: idClienteFavorecido, }); if (!cliente_favorecido) { - throw CommonHttpException.errorDetails( - 'cliente_favorecido.conta not found', - { pagadorConta: idClienteFavorecido }, - HttpStatus.NOT_FOUND, - ); + throw CommonHttpException.errorDetails('cliente_favorecido.conta not found', { pagadorConta: idClienteFavorecido }, HttpStatus.NOT_FOUND); } else { return cliente_favorecido; } @@ -113,20 +92,13 @@ export class ClienteFavorecidoService { public async getClienteFavorecido(): Promise { const cliente_favorecido = await this.clienteFavorecidoRepository.findAll(); if (!cliente_favorecido) { - throw CommonHttpException.errorDetails( - 'cliente_favorecido.conta not found', - {}, - HttpStatus.NOT_FOUND, - ); + throw CommonHttpException.errorDetails('cliente_favorecido.conta not found', {}, HttpStatus.NOT_FOUND); } else { return cliente_favorecido; } } - private async getManyFavorecidoDTOsFromUsers( - users: User[], - existingId_facorecido?: number, - ): Promise { + private async getManyFavorecidoDTOsFromUsers(users: User[], existingId_facorecido?: number): Promise { const newItems: SaveClienteFavorecidoDTO[] = []; for (const user of users) { const newItem: SaveClienteFavorecidoDTO = { @@ -146,7 +118,7 @@ export class ClienteFavorecidoService { cep: null, complementoCep: null, uf: null, - tipo: TipoFavorecidoEnum.operadora, + tipo: TipoFavorecidoEnum.van, }; await validateDTO(SaveClienteFavorecidoDTO, newItem); newItems.push(newItem); @@ -154,10 +126,7 @@ export class ClienteFavorecidoService { return newItems; } - private async saveFavorecidoFromUser( - user: User, - existingId_facorecido?: number, - ): Promise { + private async saveFavorecidoFromUser(user: User, existingId_facorecido?: number): Promise { const saveObject: SaveClienteFavorecidoDTO = { id: existingId_facorecido, nome: asString(user.fullName), @@ -175,22 +144,16 @@ export class ClienteFavorecidoService { cep: null, complementoCep: null, uf: null, - tipo: TipoFavorecidoEnum.operadora, + tipo: TipoFavorecidoEnum.van, }; await validateDTO(SaveClienteFavorecidoDTO, saveObject); await this.clienteFavorecidoRepository.save(saveObject); } - public async getOne( - fields: EntityCondition, - ): Promise { + public async getOne(fields: EntityCondition): Promise { const cliente = await this.clienteFavorecidoRepository.getOne(fields); if (!cliente) { - throw CommonHttpException.errorDetails( - 'cliente_favorecido.conta not found', - { pagadorConta: cliente }, - HttpStatus.NOT_FOUND, - ); + throw CommonHttpException.errorDetails('cliente_favorecido.conta not found', { pagadorConta: cliente }, HttpStatus.NOT_FOUND); } else { return cliente; } @@ -200,12 +163,10 @@ export class ClienteFavorecidoService { return await this.clienteFavorecidoRepository.findOneByNome(nome); } - public async findOne( - options: FindOneOptions, - ): Promise { + public async findOne(options: FindOneOptions): 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/database/seeds/cliente-favorecido/cliente-favorecido-seed-data.ts b/src/database/seeds/cliente-favorecido/cliente-favorecido-seed-data.ts index 1a376e87..3b65abf2 100644 --- a/src/database/seeds/cliente-favorecido/cliente-favorecido-seed-data.ts +++ b/src/database/seeds/cliente-favorecido/cliente-favorecido-seed-data.ts @@ -1,8 +1,8 @@ -import { ClienteFavorecido } from "src/cnab/entity/cliente-favorecido.entity"; -import { TipoFavorecidoEnum } from "src/tipo-favorecido/tipo-favorecido.enum"; +import { ClienteFavorecido } from 'src/cnab/entity/cliente-favorecido.entity'; +import { TipoFavorecidoEnum } from 'src/tipo-favorecido/tipo-favorecido.enum'; /** - * Favorecidos from + * Favorecidos from */ export enum FavorecidoCpfCnpjEnum { /** Pagador is CB */ @@ -23,7 +23,7 @@ export const ClienteFavorecidoSeedData: ClienteFavorecido[] = [ dvAgencia: '', contaCorrente: '13098785', dvContaCorrente: '7', - tipo: TipoFavorecidoEnum.consorcio, + tipo: TipoFavorecidoEnum.empresa, }), new ClienteFavorecido({ nome: 'Consórcio Intersul Transportes', @@ -33,7 +33,7 @@ export const ClienteFavorecidoSeedData: ClienteFavorecido[] = [ dvAgencia: '', contaCorrente: '13080446', dvContaCorrente: '4', - tipo: TipoFavorecidoEnum.consorcio, + tipo: TipoFavorecidoEnum.empresa, }), new ClienteFavorecido({ nome: 'Consórcio Internorte de Transportes', @@ -43,7 +43,7 @@ export const ClienteFavorecidoSeedData: ClienteFavorecido[] = [ dvAgencia: '', contaCorrente: '13004445', dvContaCorrente: '9', - tipo: TipoFavorecidoEnum.consorcio, + tipo: TipoFavorecidoEnum.empresa, }), new ClienteFavorecido({ @@ -54,7 +54,7 @@ export const ClienteFavorecidoSeedData: ClienteFavorecido[] = [ dvAgencia: '', contaCorrente: '13010758', dvContaCorrente: '7', - tipo: TipoFavorecidoEnum.consorcio, + tipo: TipoFavorecidoEnum.empresa, }), new ClienteFavorecido({ nome: 'Consórcio Santa Cruz Transportes', @@ -64,7 +64,7 @@ export const ClienteFavorecidoSeedData: ClienteFavorecido[] = [ dvAgencia: '', contaCorrente: '13004442', dvContaCorrente: '8', - tipo: TipoFavorecidoEnum.consorcio, + tipo: TipoFavorecidoEnum.empresa, }), new ClienteFavorecido({ nome: 'Companhia Municipal de Transportes Coletivos CMTC Rio', @@ -74,6 +74,6 @@ export const ClienteFavorecidoSeedData: ClienteFavorecido[] = [ dvAgencia: '9', contaCorrente: '296001', dvContaCorrente: 'X', - tipo: TipoFavorecidoEnum.consorcio, + tipo: TipoFavorecidoEnum.empresa, }), ]; diff --git a/src/tipo-favorecido/tipo-favorecido.enum.ts b/src/tipo-favorecido/tipo-favorecido.enum.ts index 38097f5c..14e77e32 100644 --- a/src/tipo-favorecido/tipo-favorecido.enum.ts +++ b/src/tipo-favorecido/tipo-favorecido.enum.ts @@ -1,4 +1,4 @@ export enum TipoFavorecidoEnum { - operadora = 'vanzeiro', - consorcio = 'consorcio', + van = 'van', + empresa = 'empresa', } diff --git a/src/utils/pipes/validate-enum.pipe.ts b/src/utils/pipes/validate-enum.pipe.ts index 4833f4f7..075a74e7 100644 --- a/src/utils/pipes/validate-enum.pipe.ts +++ b/src/utils/pipes/validate-enum.pipe.ts @@ -1,29 +1,28 @@ -import { - PipeTransform, - Injectable, - ArgumentMetadata, - BadRequestException, -} from '@nestjs/common'; +import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common'; +export interface IValidateEnumPipe { + readonly optional: boolean; + readonly defaultValue?: any; +} @Injectable() -export class ValidateEnumPipe implements PipeTransform { +export class ParseEnumPipe implements PipeTransform { constructor( - private readonly enumType: Record, - private readonly required: boolean = false, - private readonly defaultValue?: any, + private readonly enumType: Record, // + private readonly options?: IValidateEnumPipe, ) {} transform(value: any, metadata: ArgumentMetadata) { + const field = metadata.data; if (value === undefined) { - return this.defaultValue; + if (this.options?.optional) { + return value; + } else if (this.options?.defaultValue) { + return this.options.defaultValue; + } } const enumValue = this.isEnumValue(value, this.enumType); if (!enumValue) { - throw new BadRequestException( - `Invalid ${metadata.type} value. It must be one of [${Object.values( - this.enumType, - )}].`, - ); + throw new BadRequestException(`${field}: Invalid value '${value}'. It must be one of [${Object.values(this.enumType)}].`); } return value; } From a8d228572a63a8021a493d6961fe5545dac57565 Mon Sep 17 00:00:00 2001 From: Raphael Rivas Date: Thu, 29 Aug 2024 15:58:51 -0300 Subject: [PATCH 03/29] wip: create Lancamento --- .github/workflows/cd.yaml | 3 + .github/workflows/cd_stag.yaml | 3 + .github/workflows/ci.yaml | 3 + .github/workflows/docker-e2e.yml | 3 + package.json | 2 +- src/cnab/cliente-favorecido.module.ts | 13 + src/cnab/cnab.module.ts | 114 ++++++- src/cnab/cnab.service.ts | 8 +- src/cnab/dto/pagamento/transacao.dto.ts | 4 +- src/cnab/entity/cliente-favorecido.entity.ts | 2 + .../pagamento/transacao-agrupado.entity.ts | 18 +- src/cnab/entity/pagamento/transacao.entity.ts | 18 +- .../service/cliente-favorecido.service.ts | 10 +- .../pagamento/item-transacao.service.ts | 69 +---- .../pagamento/transacao-agrupado.service.ts | 28 +- .../service/pagamento/transacao.service.ts | 29 +- .../migrations/1724957667914-Lancamento.ts | 26 ++ .../lancamento-seed-data.service.ts | 40 +-- .../lancamento/lancamento-seed.module.ts | 6 +- .../lancamento/lancamento-seed.service.ts | 14 +- src/lancamento/dtos/lancamento-input.dto.ts | 67 +++++ src/lancamento/dtos/lancamentoDto.ts | 71 ----- .../lancamento-seed-data.interface.ts | 16 +- .../interfaces/lancamento.interface.ts | 24 -- src/lancamento/lancamento.controller.ts | 63 ++-- src/lancamento/lancamento.entity.ts | 121 ++++---- src/lancamento/lancamento.module.ts | 9 +- src/lancamento/lancamento.repository.ts | 32 +- src/lancamento/lancamento.service.ts | 278 +++++------------- src/users/entities/user.entity.ts | 52 +--- .../filters/all-exceptions.filter.ts | 13 +- 31 files changed, 514 insertions(+), 645 deletions(-) create mode 100644 src/cnab/cliente-favorecido.module.ts create mode 100644 src/database/migrations/1724957667914-Lancamento.ts create mode 100644 src/lancamento/dtos/lancamento-input.dto.ts delete mode 100644 src/lancamento/dtos/lancamentoDto.ts delete mode 100644 src/lancamento/interfaces/lancamento.interface.ts diff --git a/.github/workflows/cd.yaml b/.github/workflows/cd.yaml index 317fad98..3c1bde80 100644 --- a/.github/workflows/cd.yaml +++ b/.github/workflows/cd.yaml @@ -4,6 +4,9 @@ on: push: branches: - "main" + paths-ignore: + - 'README.md' + - 'docs' env: PROJECT_ID: ${{ secrets.GKE_PROJECT }} diff --git a/.github/workflows/cd_stag.yaml b/.github/workflows/cd_stag.yaml index 028826f9..83b0b2f1 100644 --- a/.github/workflows/cd_stag.yaml +++ b/.github/workflows/cd_stag.yaml @@ -6,6 +6,9 @@ on: - "release/*" - "devops" - "develop" + paths-ignore: + - 'README.md' + - 'docs' env: PROJECT_ID: ${{ secrets.GKE_PROJECT }} diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 1e67d56f..8f183727 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -5,6 +5,9 @@ on: branches: [develop] pull_request: branches: [develop] + paths-ignore: + - 'README.md' + - 'docs' jobs: lint: diff --git a/.github/workflows/docker-e2e.yml b/.github/workflows/docker-e2e.yml index 71542ecc..c0194c55 100644 --- a/.github/workflows/docker-e2e.yml +++ b/.github/workflows/docker-e2e.yml @@ -5,6 +5,9 @@ on: branches: [main] pull_request: branches: [main] + paths-ignore: + - 'README.md' + - 'docs' jobs: build: diff --git a/package.json b/package.json index 04f11d5a..7ef964e8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "api-cct", - "version": "0.12.6", + "version": "0.12.7", "description": "", "author": "", "private": true, diff --git a/src/cnab/cliente-favorecido.module.ts b/src/cnab/cliente-favorecido.module.ts new file mode 100644 index 00000000..eb4ec7af --- /dev/null +++ b/src/cnab/cliente-favorecido.module.ts @@ -0,0 +1,13 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { ClienteFavorecido } from './entity/cliente-favorecido.entity'; +import { ClienteFavorecidoRepository } from './repository/cliente-favorecido.repository'; +import { ClienteFavorecidoService } from './service/cliente-favorecido.service'; + +@Module({ + imports: [TypeOrmModule.forFeature([ClienteFavorecido])], + providers: [ClienteFavorecidoRepository, ClienteFavorecidoService], + exports: [ClienteFavorecidoRepository, ClienteFavorecidoService], + controllers: [], +}) +export class ClienteFavorecidoModule {} diff --git a/src/cnab/cnab.module.ts b/src/cnab/cnab.module.ts index 7da8ad98..1732319b 100644 --- a/src/cnab/cnab.module.ts +++ b/src/cnab/cnab.module.ts @@ -70,11 +70,119 @@ import { PagamentosPendentesService } from './service/pagamento/pagamentos-pende import { RemessaRetornoService } from './service/pagamento/remessa-retorno.service'; import { TransacaoAgrupadoService } from './service/pagamento/transacao-agrupado.service'; import { TransacaoService } from './service/pagamento/transacao.service'; +import { ClienteFavorecidoModule } from './cliente-favorecido.module'; @Module({ - imports: [BanksModule, BigqueryModule, LancamentoModule, SettingsModule, SftpModule, TransacaoViewModule, UsersModule, TypeOrmModule.forFeature([HeaderArquivoConf, HeaderLoteConf, DetalheAConf, DetalheBConf, HeaderArquivo, HeaderLote, DetalheA, DetalheB, PagamentosPendentes, ClienteFavorecido, ArquivoPublicacao, Transacao, TransacaoAgrupado, Ocorrencia, ItemTransacao, ItemTransacaoAgrupado, Pagador, ExtratoHeaderArquivo, ExtratoHeaderLote, ExtratoDetalheE])], - providers: [ArquivoPublicacaoRepository, ArquivoPublicacaoService, ClienteFavorecidoRepository, ClienteFavorecidoService, CnabService, DetalheAConfRepository, DetalheAConfService, DetalheARepository, DetalheAService, DetalheBConfRepository, DetalheBConfService, DetalheBRepository, DetalheBService, ExtratoDetalheERepository, ExtratoDetalheEService, ExtratoHeaderArquivoRepository, ExtratoHeaderArquivoService, ExtratoHeaderLoteRepository, ExtratoHeaderLoteService, HeaderArquivoConfRepository, HeaderArquivoConfService, HeaderArquivoRepository, HeaderArquivoService, HeaderLoteConfRepository, HeaderLoteConfService, HeaderLoteRepository, HeaderLoteService, ItemTransacaoAgrupadoRepository, ItemTransacaoAgrupadoService, ItemTransacaoRepository, ItemTransacaoService, OcorrenciaRepository, OcorrenciaService, PagadorRepository, PagadorService, PagamentosPendentesRepository, PagamentosPendentesService, RemessaRetornoService, TransacaoAgrupadoRepository, TransacaoAgrupadoService, TransacaoRepository, TransacaoService], - exports: [CnabService, HeaderArquivoRepository, HeaderArquivoService, HeaderLoteRepository, HeaderLoteService, DetalheARepository, DetalheAService, DetalheBRepository, DetalheBService, HeaderArquivoConfRepository, HeaderArquivoConfService, HeaderLoteConfRepository, HeaderLoteConfService, DetalheAConfRepository, DetalheAConfService, DetalheBConfRepository, DetalheBConfService, PagamentosPendentesRepository, PagamentosPendentesService, ClienteFavorecidoRepository, ClienteFavorecidoService, PagadorRepository, PagadorService, ArquivoPublicacaoRepository, ArquivoPublicacaoService, TransacaoRepository, TransacaoService, ItemTransacaoRepository, ItemTransacaoService, ExtratoHeaderArquivoRepository, ExtratoHeaderArquivoService, ExtratoHeaderLoteRepository, ExtratoHeaderLoteService, ExtratoDetalheERepository, ExtratoDetalheEService, RemessaRetornoService, OcorrenciaService], + imports: [ + BanksModule, // + BigqueryModule, + LancamentoModule, + SettingsModule, + SftpModule, + TransacaoViewModule, + UsersModule, + ClienteFavorecidoModule, + TypeOrmModule.forFeature([ + HeaderArquivoConf, // + HeaderLoteConf, + DetalheAConf, + DetalheBConf, + HeaderArquivo, + HeaderLote, + DetalheA, + DetalheB, + PagamentosPendentes, + ArquivoPublicacao, + Transacao, + TransacaoAgrupado, + Ocorrencia, + ItemTransacao, + ItemTransacaoAgrupado, + Pagador, + ExtratoHeaderArquivo, + ExtratoHeaderLote, + ExtratoDetalheE, + ]), + ], + providers: [ + ArquivoPublicacaoRepository, // + ArquivoPublicacaoService, + CnabService, + DetalheAConfRepository, + DetalheAConfService, + DetalheARepository, + DetalheAService, + DetalheBConfRepository, + DetalheBConfService, + DetalheBRepository, + DetalheBService, + ExtratoDetalheERepository, + ExtratoDetalheEService, + ExtratoHeaderArquivoRepository, + ExtratoHeaderArquivoService, + ExtratoHeaderLoteRepository, + ExtratoHeaderLoteService, + HeaderArquivoConfRepository, + HeaderArquivoConfService, + HeaderArquivoRepository, + HeaderArquivoService, + HeaderLoteConfRepository, + HeaderLoteConfService, + HeaderLoteRepository, + HeaderLoteService, + ItemTransacaoAgrupadoRepository, + ItemTransacaoAgrupadoService, + ItemTransacaoRepository, + ItemTransacaoService, + OcorrenciaRepository, + OcorrenciaService, + PagadorRepository, + PagadorService, + PagamentosPendentesRepository, + PagamentosPendentesService, + RemessaRetornoService, + TransacaoAgrupadoRepository, + TransacaoAgrupadoService, + TransacaoRepository, + TransacaoService, + ], + exports: [ + CnabService, // + HeaderArquivoRepository, + HeaderArquivoService, + HeaderLoteRepository, + HeaderLoteService, + DetalheARepository, + DetalheAService, + DetalheBRepository, + DetalheBService, + HeaderArquivoConfRepository, + HeaderArquivoConfService, + HeaderLoteConfRepository, + HeaderLoteConfService, + DetalheAConfRepository, + DetalheAConfService, + DetalheBConfRepository, + DetalheBConfService, + PagamentosPendentesRepository, + PagamentosPendentesService, + PagadorRepository, + PagadorService, + ArquivoPublicacaoRepository, + ArquivoPublicacaoService, + TransacaoRepository, + TransacaoService, + ItemTransacaoRepository, + ItemTransacaoService, + ExtratoHeaderArquivoRepository, + ExtratoHeaderArquivoService, + ExtratoHeaderLoteRepository, + ExtratoHeaderLoteService, + ExtratoDetalheERepository, + ExtratoDetalheEService, + RemessaRetornoService, + OcorrenciaService, + ], controllers: [CnabController], }) export class CnabModule {} diff --git a/src/cnab/cnab.service.ts b/src/cnab/cnab.service.ts index 58b1b6a6..6b399a7d 100644 --- a/src/cnab/cnab.service.ts +++ b/src/cnab/cnab.service.ts @@ -3,7 +3,7 @@ import { endOfDay, isFriday, nextFriday, nextThursday, startOfDay, subDays } fro import { BigqueryOrdemPagamentoDTO } from 'src/bigquery/dtos/bigquery-ordem-pagamento.dto'; import { BigqueryOrdemPagamentoService } from 'src/bigquery/services/bigquery-ordem-pagamento.service'; import { BigqueryTransacaoService } from 'src/bigquery/services/bigquery-transacao.service'; -import { LancamentoEntity } from 'src/lancamento/lancamento.entity'; +import { Lancamento } from 'src/lancamento/lancamento.entity'; import { LancamentoService } from 'src/lancamento/lancamento.service'; import { SftpBackupFolder } from 'src/sftp/enums/sftp-backup-folder.enum'; import { SftpService } from 'src/sftp/sftp.service'; @@ -262,7 +262,7 @@ export class CnabService { if (existing.length > 1) { await queryRunner.manager.getRepository(TransacaoView).remove(existing.slice(1)); } - if (existing[0].valorPago == 0 && tr.valorPago != existing[0].valorPago) { + if (!existing[0].valorPago && tr.valorPago != existing[0].valorPago) { await queryRunner.manager.getRepository(TransacaoView).update({ idTransacao: tr.idTransacao }, { valorPago: tr.valorPago }); } } @@ -479,11 +479,11 @@ export class CnabService { public async saveTransacoesLancamento() { await this.updateAllFavorecidosFromUsers(); const newLancamentos = await this.lancamentoService.findToPayWeek(); - const favorecidos = newLancamentos.map((i) => i.id_cliente_favorecido); + const favorecidos = newLancamentos.map((i) => i.clienteFavorecido); const pagador = (await this.pagadorService.getAllPagador()).contaBilhetagem; const transacaoDTO = this.transacaoService.generateDTOForLancamento(pagador, newLancamentos); const savedTransacao = await this.transacaoService.saveForLancamento(transacaoDTO); - const updatedLancamentos = savedTransacao.lancamentos as LancamentoEntity[]; + const updatedLancamentos = savedTransacao.lancamentos as Lancamento[]; const itemTransacaoDTOs = this.itemTransacaoService.generateDTOsFromLancamentos(updatedLancamentos, favorecidos); await this.itemTransacaoService.saveMany(itemTransacaoDTOs); } diff --git a/src/cnab/dto/pagamento/transacao.dto.ts b/src/cnab/dto/pagamento/transacao.dto.ts index 80f72c61..d3a82c65 100644 --- a/src/cnab/dto/pagamento/transacao.dto.ts +++ b/src/cnab/dto/pagamento/transacao.dto.ts @@ -1,6 +1,6 @@ import { IsNotEmpty, ValidateIf } from 'class-validator'; import { Ocorrencia } from 'src/cnab/entity/pagamento/ocorrencia.entity'; -import { LancamentoEntity } from 'src/lancamento/lancamento.entity'; +import { Lancamento } from 'src/lancamento/lancamento.entity'; import { DeepPartial } from 'typeorm'; import { Pagador } from '../../entity/pagamento/pagador.entity'; @@ -31,5 +31,5 @@ export class TransacaoDTO { @ValidateIf((obj) => isCreate(obj) && obj.idOrdemPagamento === undefined) @IsNotEmpty() - lancamentos?: LancamentoEntity[] | null; + lancamentos?: Lancamento[] | null; } diff --git a/src/cnab/entity/cliente-favorecido.entity.ts b/src/cnab/entity/cliente-favorecido.entity.ts index 2699440b..882a77c1 100644 --- a/src/cnab/entity/cliente-favorecido.entity.ts +++ b/src/cnab/entity/cliente-favorecido.entity.ts @@ -1,6 +1,7 @@ import { EntityHelper } from 'src/utils/entity-helper'; import { BeforeInsert, + BeforeUpdate, Column, DeepPartial, Entity, @@ -71,6 +72,7 @@ export class ClienteFavorecido extends EntityHelper { tipo: string | null; @BeforeInsert() + @BeforeUpdate() setWriteValues() { if (typeof this.codigoBanco === 'string') { this.codigoBanco = this.codigoBanco.trim().padStart(3, '0'); diff --git a/src/cnab/entity/pagamento/transacao-agrupado.entity.ts b/src/cnab/entity/pagamento/transacao-agrupado.entity.ts index 4b4ef738..96101349 100644 --- a/src/cnab/entity/pagamento/transacao-agrupado.entity.ts +++ b/src/cnab/entity/pagamento/transacao-agrupado.entity.ts @@ -1,16 +1,6 @@ -import { LancamentoEntity } from 'src/lancamento/lancamento.entity'; +import { Lancamento } from 'src/lancamento/lancamento.entity'; import { EntityHelper } from 'src/utils/entity-helper'; -import { - Column, - CreateDateColumn, - DeepPartial, - Entity, - JoinColumn, - ManyToOne, - OneToMany, - PrimaryGeneratedColumn, - UpdateDateColumn, -} from 'typeorm'; +import { Column, CreateDateColumn, DeepPartial, Entity, JoinColumn, ManyToOne, OneToMany, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'; import { ItemTransacaoAgrupado } from './item-transacao-agrupado.entity'; import { Pagador } from './pagador.entity'; import { TransacaoStatus } from './transacao-status.entity'; @@ -62,13 +52,13 @@ export class TransacaoAgrupado extends EntityHelper { status: TransacaoStatus; /** Not a physical column */ - @OneToMany(() => LancamentoEntity, (lancamento) => lancamento.transacao, { + @OneToMany(() => Lancamento, (lancamento) => lancamento.transacao, { nullable: true, }) @JoinColumn({ foreignKeyConstraintName: 'FK_TransacaoAgrupado_lancamentos_OneToMany', }) - lancamentos: LancamentoEntity[] | null; + lancamentos: Lancamento[] | null; /** Not a physical column */ @OneToMany(() => ItemTransacaoAgrupado, (item) => item.transacaoAgrupado, { diff --git a/src/cnab/entity/pagamento/transacao.entity.ts b/src/cnab/entity/pagamento/transacao.entity.ts index d1b55a13..625bce61 100644 --- a/src/cnab/entity/pagamento/transacao.entity.ts +++ b/src/cnab/entity/pagamento/transacao.entity.ts @@ -1,16 +1,6 @@ -import { LancamentoEntity } from 'src/lancamento/lancamento.entity'; +import { Lancamento } from 'src/lancamento/lancamento.entity'; import { EntityHelper } from 'src/utils/entity-helper'; -import { - Column, - CreateDateColumn, - DeepPartial, - Entity, - JoinColumn, - ManyToOne, - OneToMany, - PrimaryGeneratedColumn, - UpdateDateColumn, -} from 'typeorm'; +import { Column, CreateDateColumn, DeepPartial, Entity, JoinColumn, ManyToOne, OneToMany, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'; import { ItemTransacao } from './item-transacao.entity'; import { Pagador } from './pagador.entity'; import { TransacaoAgrupado } from './transacao-agrupado.entity'; @@ -68,13 +58,13 @@ export class Transacao extends EntityHelper { transacaoAgrupado: TransacaoAgrupado; /** Not a physical column */ - @OneToMany(() => LancamentoEntity, (lancamento) => lancamento.transacao, { + @OneToMany(() => Lancamento, (lancamento) => lancamento.transacao, { nullable: true, }) @JoinColumn({ foreignKeyConstraintName: 'FK_Transacao_lancamentos_OneToMany', }) - lancamentos: LancamentoEntity[] | null; + lancamentos: Lancamento[] | null; /** Not a physical column */ @OneToMany(() => ItemTransacao, (item) => item.transacao, { eager: false }) diff --git a/src/cnab/service/cliente-favorecido.service.ts b/src/cnab/service/cliente-favorecido.service.ts index 748da522..4632be0c 100644 --- a/src/cnab/service/cliente-favorecido.service.ts +++ b/src/cnab/service/cliente-favorecido.service.ts @@ -1,6 +1,6 @@ import { HttpStatus, Injectable, Logger } from '@nestjs/common'; import { BigqueryOrdemPagamentoDTO } from 'src/bigquery/dtos/bigquery-ordem-pagamento.dto'; -import { LancamentoEntity } from 'src/lancamento/lancamento.entity'; +import { Lancamento } from 'src/lancamento/lancamento.entity'; import { TipoFavorecidoEnum } from 'src/tipo-favorecido/tipo-favorecido.enum'; import { User } from 'src/users/entities/user.entity'; import { CommonHttpException } from 'src/utils/http-exception/common-http-exception'; @@ -16,9 +16,7 @@ import { IClienteFavorecidoFindBy } from '../interfaces/cliente-favorecido-find- @Injectable() export class ClienteFavorecidoService { - private logger: Logger = new Logger('ClienteFavorecidoService', { - timestamp: true, - }); + private logger: Logger = new Logger('ClienteFavorecidoService', { timestamp: true }); constructor(private clienteFavorecidoRepository: ClienteFavorecidoRepository) {} @@ -51,8 +49,8 @@ export class ClienteFavorecidoService { }); } - public async findManyFromLancamentos(lancamentos: LancamentoEntity[]): Promise { - const ids = [...new Set(lancamentos.map((i) => i.id_cliente_favorecido))]; + public async findManyFromLancamentos(lancamentos: Lancamento[]): Promise { + const ids = [...new Set(lancamentos.map((i) => i.clienteFavorecido))]; return await this.clienteFavorecidoRepository.findMany({ where: { id: In(ids), diff --git a/src/cnab/service/pagamento/item-transacao.service.ts b/src/cnab/service/pagamento/item-transacao.service.ts index 03b58137..f088e6c4 100644 --- a/src/cnab/service/pagamento/item-transacao.service.ts +++ b/src/cnab/service/pagamento/item-transacao.service.ts @@ -4,19 +4,12 @@ import { ClienteFavorecido } from 'src/cnab/entity/cliente-favorecido.entity'; import { ItemTransacao } from 'src/cnab/entity/pagamento/item-transacao.entity'; import { Transacao } from 'src/cnab/entity/pagamento/transacao.entity'; import { ItemTransacaoRepository } from 'src/cnab/repository/pagamento/item-transacao.repository'; -import { LancamentoEntity } from 'src/lancamento/lancamento.entity'; +import { Lancamento } from 'src/lancamento/lancamento.entity'; import { CustomLogger } from 'src/utils/custom-logger'; import { logDebug } from 'src/utils/log-utils'; import { asObject } from 'src/utils/pipe-utils'; import { SaveIfNotExists } from 'src/utils/types/save-if-not-exists.type'; -import { - DeepPartial, - FindManyOptions, - FindOptionsWhere, - In, - Not, - QueryRunner, -} from 'typeorm'; +import { DeepPartial, FindManyOptions, FindOptionsWhere, In, Not, QueryRunner } from 'typeorm'; @Injectable() export class ItemTransacaoService { @@ -35,19 +28,15 @@ export class ItemTransacaoService { /** * @param publicacoes Ready to save or saved Entity. Must contain valid Transacao */ - public generateDTOsFromLancamentos( - lancamentos: LancamentoEntity[], - favorecidos: ClienteFavorecido[], - ): ItemTransacao[] { + public generateDTOsFromLancamentos(lancamentos: Lancamento[], favorecidos: ClienteFavorecido[]): ItemTransacao[] { /** Key: id ClienteFavorecido. Eficient way to find favorecido. */ - const favorecidosMap: Record = - favorecidos.reduce((map, i) => ({ ...map, [i.id]: i }), {}); + const favorecidosMap: Record = favorecidos.reduce((map, i) => ({ ...map, [i.id]: i }), {}); const itens: ItemTransacao[] = []; // Mount DTOs for (const lancamento of lancamentos) { - const favorecido = favorecidosMap[lancamento.id_cliente_favorecido.id]; + const favorecido = favorecidosMap[lancamento.clienteFavorecido.id]; itens.push(this.generateDTOFromLancamento(lancamento, favorecido)); } return itens; @@ -58,10 +47,7 @@ export class ItemTransacaoService { * * **status** is Created. */ - public generateDTOFromLancamento( - lancamento: LancamentoEntity, - favorecido: ClienteFavorecido, - ): ItemTransacao { + public generateDTOFromLancamento(lancamento: Lancamento, favorecido: ClienteFavorecido): ItemTransacao { const transacao = asObject(lancamento.transacao); /** detalheA = null, isRegistered = false */ const itemTransacao = new ItemTransacao({ @@ -90,9 +76,7 @@ export class ItemTransacaoService { return newItens; } - public async findManyByIdTransacao( - id_transacao: number, - ): Promise { + public async findManyByIdTransacao(id_transacao: number): Promise { return await this.itemTransacaoRepository.findMany({ where: { transacao: { @@ -111,16 +95,14 @@ export class ItemTransacaoService { return many.pop() || null; } - public async save(dto: DeepPartial,queryRunner:QueryRunner): Promise { + public async save(dto: DeepPartial, queryRunner: QueryRunner): Promise { return await queryRunner.manager.getRepository(ItemTransacao).save(dto); } /** * Save if composite unique columns not exist. Otherwise, update. */ - public async saveManyIfNotExistsJae( - dtos: DeepPartial[], - ): Promise { + public async saveManyIfNotExistsJae(dtos: DeepPartial[]): Promise { // Existing const existing = await this.itemTransacaoRepository.findMany({ where: dtos.reduce( @@ -135,34 +117,22 @@ export class ItemTransacaoService { [], ), }); - const existingMap: Record = existing.reduce( - (m, i) => ({ ...m, [ItemTransacao.getUniqueIdJae(i)]: i }), - {}, - ); + const existingMap: Record = existing.reduce((m, i) => ({ ...m, [ItemTransacao.getUniqueIdJae(i)]: i }), {}); // Check if (existing.length === dtos.length) { - this.logger.warn( - `${existing.length}/${dtos.length} ItemTransacoes já existem, nada a fazer...`, - ); + this.logger.warn(`${existing.length}/${dtos.length} ItemTransacoes já existem, nada a fazer...`); } else if (existing.length) { - this.logger.warn( - `${existing.length}/${dtos.length} ItemTransacoes já existem, ignorando...`, - ); + this.logger.warn(`${existing.length}/${dtos.length} ItemTransacoes já existem, ignorando...`); return []; } // Save new - const newItems = dtos.filter( - (i) => !existingMap[ItemTransacao.getUniqueIdJae(i)], - ); + const newItems = dtos.filter((i) => !existingMap[ItemTransacao.getUniqueIdJae(i)]); const insert = await this.itemTransacaoRepository.insert(newItems); // 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 savedItems = await this.itemTransacaoRepository.findMany({ where: { id: In(insertIds) }, }); @@ -172,10 +142,7 @@ export class ItemTransacaoService { /** * Save if composite unique columns not exist. Otherwise, update. */ - public async saveIfNotExists( - dto: ItemTransacaoDTO, - updateIfExists?: boolean, - ): Promise> { + public async saveIfNotExists(dto: ItemTransacaoDTO, updateIfExists?: boolean): Promise> { // Find by composite unique columns const item = await this.itemTransacaoRepository.findOne({ where: { @@ -247,10 +214,6 @@ export class ItemTransacaoService { // Log const allIds = allFailed.reduce((l, i) => [...l, i.id], []).join(','); - logDebug( - this.logger, - `ItemTr. #${allIds} movidos para Transacao #${transacaoDest.id}`, - METHOD, - ); + logDebug(this.logger, `ItemTr. #${allIds} movidos para Transacao #${transacaoDest.id}`, METHOD); } } diff --git a/src/cnab/service/pagamento/transacao-agrupado.service.ts b/src/cnab/service/pagamento/transacao-agrupado.service.ts index 254809f7..a6c602ac 100644 --- a/src/cnab/service/pagamento/transacao-agrupado.service.ts +++ b/src/cnab/service/pagamento/transacao-agrupado.service.ts @@ -6,7 +6,7 @@ import { Pagador } from 'src/cnab/entity/pagamento/pagador.entity'; import { TransacaoAgrupado } from 'src/cnab/entity/pagamento/transacao-agrupado.entity'; import { PagadorContaEnum } from 'src/cnab/enums/pagamento/pagador.enum'; import { TransacaoAgrupadoRepository } from 'src/cnab/repository/pagamento/transacao-agrupado.repository'; -import { LancamentoEntity } from 'src/lancamento/lancamento.entity'; +import { Lancamento } from 'src/lancamento/lancamento.entity'; import { asNumber } from 'src/utils/pipe-utils'; import { EntityCondition } from 'src/utils/types/entity-condition.type'; import { DeepPartial, UpdateResult } from 'typeorm'; @@ -36,10 +36,7 @@ export class TransacaoAgrupadoService { * * @param newLancamentos It must have at least 1 unused Lancamento */ - public generateDTOForLancamento( - pagador: Pagador, - newLancamentos: LancamentoEntity[], - ): Transacao { + public generateDTOForLancamento(pagador: Pagador, newLancamentos: Lancamento[]): Transacao { const today = new Date(); const friday = isFriday(today) ? today : nextFriday(today); const transacao = new Transacao({ @@ -58,22 +55,15 @@ export class TransacaoAgrupadoService { /** * Use first Transacao as set to update and all Transacoes to get ids. */ - public updateMany( - ids: number[], - set: DeepPartial, - ): Promise { + public updateMany(ids: number[], set: DeepPartial): Promise { return this.transacaoAgRepository.updateMany(ids, set); } - public saveManyIfNotExists( - transacoes: DeepPartial[], - ): Promise { + public saveManyIfNotExists(transacoes: DeepPartial[]): Promise { return this.transacaoAgRepository.saveManyIfNotExists(transacoes); } - public async saveMany( - transacoes: DeepPartial[], - ): Promise { + public async saveMany(transacoes: DeepPartial[]): Promise { const insertResult = await this.transacaoAgRepository.insert(transacoes); return await this.transacaoAgRepository.findMany({ where: insertResult.identifiers, @@ -83,9 +73,7 @@ export class TransacaoAgrupadoService { /** * Save Transacao if NSA not exists */ - public async save( - dto: DeepPartial, - ): Promise { + public async save(dto: DeepPartial): Promise { return await this.transacaoAgRepository.save(dto); } @@ -96,9 +84,7 @@ export class TransacaoAgrupadoService { /** * Get all transacao where id not exists in headerArquivo yet (new CNABS) */ - public async findAllNewTransacao( - tipo: PagadorContaEnum, - ): Promise { + public async findAllNewTransacao(tipo: PagadorContaEnum): Promise { return await this.transacaoAgRepository.findAllNewTransacao(tipo); } } diff --git a/src/cnab/service/pagamento/transacao.service.ts b/src/cnab/service/pagamento/transacao.service.ts index 6db86035..924af91f 100644 --- a/src/cnab/service/pagamento/transacao.service.ts +++ b/src/cnab/service/pagamento/transacao.service.ts @@ -6,7 +6,7 @@ import { TransacaoRepository } from '../../repository/pagamento/transacao.reposi import { isFriday, nextFriday } from 'date-fns'; import { Pagador } from 'src/cnab/entity/pagamento/pagador.entity'; import { PagadorContaEnum } from 'src/cnab/enums/pagamento/pagador.enum'; -import { LancamentoEntity } from 'src/lancamento/lancamento.entity'; +import { Lancamento } from 'src/lancamento/lancamento.entity'; import { asNumber, asString } from 'src/utils/pipe-utils'; import { EntityCondition } from 'src/utils/types/entity-condition.type'; import { SaveIfNotExists } from 'src/utils/types/save-if-not-exists.type'; @@ -36,10 +36,7 @@ export class TransacaoService { * * @param newLancamentos It must have at least 1 unused Lancamento */ - public generateDTOForLancamento( - pagador: Pagador, - newLancamentos: LancamentoEntity[], - ): Transacao { + public generateDTOForLancamento(pagador: Pagador, newLancamentos: Lancamento[]): Transacao { const today = new Date(); const friday = isFriday(today) ? today : nextFriday(today); const transacao = new Transacao({ @@ -100,9 +97,7 @@ export class TransacaoService { /** * Use first Transacao as set to update and all Transacoes to get ids. */ - public updateMany( - transacoes: DeepPartial[], - ): Promise { + public updateMany(transacoes: DeepPartial[]): Promise { const ids = transacoes.reduce((l, i) => [...l, asNumber(i.id)], []); const set = transacoes[0]; if ('id' in set) { @@ -111,15 +106,11 @@ export class TransacaoService { return this.transacaoRepository.updateMany(ids, set); } - public saveManyIfNotExists( - transacoes: DeepPartial[], - ): Promise { + public saveManyIfNotExists(transacoes: DeepPartial[]): Promise { return this.transacaoRepository.saveManyIfNotExists(transacoes); } - public async saveMany( - transacoes: DeepPartial[], - ): Promise { + public async saveMany(transacoes: DeepPartial[]): Promise { const insertResult = await this.transacaoRepository.insert(transacoes); return await this.transacaoRepository.findMany({ where: insertResult.identifiers, @@ -150,9 +141,7 @@ export class TransacaoService { /** * Save Transacao if Jae unique column not exists */ - public async saveForJaeIfNotExists( - dto: TransacaoDTO, - ): Promise> { + public async saveForJaeIfNotExists(dto: TransacaoDTO): Promise> { await validateDTO(TransacaoDTO, dto); const transacao = await this.transacaoRepository.findOne({ idOrdemPagamento: asString(dto.idOrdemPagamento), @@ -173,7 +162,7 @@ export class TransacaoService { /** * Save Transacao if NSA not exists */ - public async save(dto: TransacaoDTO,queryRunner: QueryRunner): Promise { + public async save(dto: TransacaoDTO, queryRunner: QueryRunner): Promise { await validateDTO(TransacaoDTO, dto); return await queryRunner.manager.getRepository(Transacao).save(dto); } @@ -185,9 +174,7 @@ export class TransacaoService { /** * Get all transacao where id not exists in headerArquivo yet (new CNABS) */ - public async findAllNewTransacao( - tipo: PagadorContaEnum, - ): Promise { + public async findAllNewTransacao(tipo: PagadorContaEnum): Promise { return await this.transacaoRepository.findAllNewTransacao(tipo); } } diff --git a/src/database/migrations/1724957667914-Lancamento.ts b/src/database/migrations/1724957667914-Lancamento.ts new file mode 100644 index 00000000..682edf93 --- /dev/null +++ b/src/database/migrations/1724957667914-Lancamento.ts @@ -0,0 +1,26 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class Lancamento1724957667914 implements MigrationInterface { + name = 'Lancamento1724957667914' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "lancamento" DROP COLUMN "auth_usersIds"`); + await queryRunner.query(`ALTER TABLE "lancamento" ADD "autorizado_por" character varying`); + await queryRunner.query(`ALTER TABLE "lancamento" DROP CONSTRAINT "FK_Lancamento_user_ManyToOne"`); + await queryRunner.query(`ALTER TABLE "lancamento" DROP COLUMN "algoritmo"`); + await queryRunner.query(`ALTER TABLE "lancamento" ADD "algoritmo" numeric NOT NULL`); + await queryRunner.query(`ALTER TABLE "lancamento" ALTER COLUMN "userId" DROP NOT NULL`); + await queryRunner.query(`ALTER TABLE "lancamento" ADD CONSTRAINT "FK_Lancamento_user_ManyToOne" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "lancamento" DROP CONSTRAINT "FK_Lancamento_user_ManyToOne"`); + await queryRunner.query(`ALTER TABLE "lancamento" ALTER COLUMN "userId" SET NOT NULL`); + await queryRunner.query(`ALTER TABLE "lancamento" DROP COLUMN "algoritmo"`); + await queryRunner.query(`ALTER TABLE "lancamento" ADD "algoritmo" character varying NOT NULL`); + await queryRunner.query(`ALTER TABLE "lancamento" ADD CONSTRAINT "FK_Lancamento_user_ManyToOne" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "lancamento" DROP COLUMN "autorizado_por"`); + await queryRunner.query(`ALTER TABLE "lancamento" ADD "auth_usersIds" character varying`); + } + +} diff --git a/src/database/seeds/lancamento/lancamento-seed-data.service.ts b/src/database/seeds/lancamento/lancamento-seed-data.service.ts index 210a119a..95f5c08b 100644 --- a/src/database/seeds/lancamento/lancamento-seed-data.service.ts +++ b/src/database/seeds/lancamento/lancamento-seed-data.service.ts @@ -10,7 +10,6 @@ import { Repository } from 'typeorm'; @Injectable() export class LancamentoSeedDataService { - nodeEnv = (): string => ''; constructor( @@ -20,28 +19,25 @@ export class LancamentoSeedDataService { @InjectRepository(ClienteFavorecido) private favorecidosRepository: Repository, ) { - this.nodeEnv = () => - this.configService.getOrThrow('app.nodeEnv', { infer: true }); + this.nodeEnv = () => this.configService.getOrThrow('app.nodeEnv', { infer: true }); } async getData() { // Get user_ids const users = await this.usersRepository.find({ - take: 3 + take: 3, }); if (users.length < 3) { - throw new Exception(`Expected 3 users but got ${users.length}.` + - 'Did you seed this before seeding Users?'); + throw new Exception(`Expected 3 users but got ${users.length}.` + 'Did you seed this before seeding Users?'); } const userIds = users.reduce((l, i) => [...l, i.id], []); // Get id_favorecidos const favorecidos = await this.favorecidosRepository.find({ - take: 2 + take: 2, }); if (users.length < 2) { - throw new Exception(`Expected 2 Favorecidos but got ${favorecidos.length}.` + - 'Did you seed this before seeding ClienteFavorecidos?'); + throw new Exception(`Expected 2 Favorecidos but got ${favorecidos.length}.` + 'Did you seed this before seeding ClienteFavorecidos?'); } const today = getBRTFromUTC(new Date()); const seedTag = '[SEED] '; @@ -53,7 +49,7 @@ export class LancamentoSeedDataService { { algoritmo: '1', descricao: seedTag + 'Unapproved', - id_cliente_favorecido: { id: favorecidos[0].id }, + clienteFavorecido: { id: favorecidos[0].id }, data_lancamento: today, data_ordem: today, data_pgto: today, @@ -62,14 +58,13 @@ export class LancamentoSeedDataService { recurso: 1, valor: 110, valor_a_pagar: 110, - userId: users[0].id, - user: { id: users[0].id }, + autor: { id: users[0].id }, anexo: 1, }, { algoritmo: '2', descricao: seedTag + 'Approved 1', - id_cliente_favorecido: { id: favorecidos[0].id }, + clienteFavorecido: { id: favorecidos[0].id }, data_lancamento: today, data_ordem: today, data_pgto: today, @@ -78,15 +73,14 @@ export class LancamentoSeedDataService { recurso: 2, valor: 951, valor_a_pagar: 951, - userId: users[0].id, - user: { id: users[0].id }, - auth_usersIds: userIds.slice(0, 1).join(','), + autor: { id: users[0].id }, + autorizado_por: userIds.slice(0, 1).join(','), anexo: 1, }, { algoritmo: 2, descricao: seedTag + 'Approved 2', - id_cliente_favorecido: { id: favorecidos[0].id }, + clienteFavorecido: { id: favorecidos[0].id }, data_lancamento: today, data_ordem: today, data_pgto: today, @@ -95,15 +89,14 @@ export class LancamentoSeedDataService { recurso: 2, valor: 951.12, valor_a_pagar: 951.12, - userId: users[0].id, - user: { id: users[0].id }, - auth_usersIds: userIds.slice(0, 2).join(','), + autor: { id: users[0].id }, + autorizado_por: userIds.slice(0, 2).join(','), anexo: 1, }, { algoritmo: 3, descricao: seedTag + 'Approved 2 again', - id_cliente_favorecido: { id: favorecidos[0].id }, + clienteFavorecido: { id: favorecidos[0].id }, data_lancamento: today, data_ordem: today, data_pgto: today, @@ -112,9 +105,8 @@ export class LancamentoSeedDataService { recurso: 2, valor: 1034.01, valor_a_pagar: 1034.01, - userId: users[0].id, - user: { id: users[0].id }, - auth_usersIds: userIds.slice(0, 2).join(','), + autor: { id: users[0].id }, + autorizado_por: userIds.slice(0, 2).join(','), anexo: 1, }, ] as LancamentoSeedData[]) diff --git a/src/database/seeds/lancamento/lancamento-seed.module.ts b/src/database/seeds/lancamento/lancamento-seed.module.ts index 6b1f1926..f6ea489c 100644 --- a/src/database/seeds/lancamento/lancamento-seed.module.ts +++ b/src/database/seeds/lancamento/lancamento-seed.module.ts @@ -1,15 +1,15 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { ClienteFavorecido } from 'src/cnab/entity/cliente-favorecido.entity'; -import { LancamentoEntity } from 'src/lancamento/lancamento.entity'; +import { Lancamento } from 'src/lancamento/lancamento.entity'; import { User } from 'src/users/entities/user.entity'; import { LancamentoSeedDataService } from './lancamento-seed-data.service'; import { LancamentoSeedService } from './lancamento-seed.service'; import { ConfigModule } from '@nestjs/config'; @Module({ - imports: [TypeOrmModule.forFeature([User, ClienteFavorecido, LancamentoEntity]), ConfigModule], + imports: [TypeOrmModule.forFeature([User, ClienteFavorecido, Lancamento]), ConfigModule], providers: [LancamentoSeedService, LancamentoSeedDataService], exports: [LancamentoSeedService, LancamentoSeedDataService], }) -export class LancamentoSeedModule { } +export class LancamentoSeedModule {} diff --git a/src/database/seeds/lancamento/lancamento-seed.service.ts b/src/database/seeds/lancamento/lancamento-seed.service.ts index 05970903..37753e5a 100644 --- a/src/database/seeds/lancamento/lancamento-seed.service.ts +++ b/src/database/seeds/lancamento/lancamento-seed.service.ts @@ -1,16 +1,16 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { LancamentoEntity } from 'src/lancamento/lancamento.entity'; +import { Lancamento } from 'src/lancamento/lancamento.entity'; import { In, Repository } from 'typeorm'; import { LancamentoSeedDataService } from './lancamento-seed-data.service'; @Injectable() export class LancamentoSeedService { constructor( - @InjectRepository(LancamentoEntity) - private lancamentoRepository: Repository, + @InjectRepository(Lancamento) + private lancamentoRepository: Repository, private lancamentoSeedDataService: LancamentoSeedDataService, - ) { } + ) {} async validateRun() { return Promise.resolve(global.force); @@ -18,12 +18,12 @@ export class LancamentoSeedService { async run() { const { fixtures } = await this.lancamentoSeedDataService.getData(); - const descricoes = fixtures.map(i => i.descricao); + const descricoes = fixtures.map((i) => i.descricao); // Remove existing seeds await this.lancamentoRepository.delete({ descricao: In(descricoes) }); - const newItems: LancamentoEntity[] = []; + const newItems: Lancamento[] = []; for (const fixture of fixtures) { - newItems.push(new LancamentoEntity(fixture)); + newItems.push(new Lancamento(fixture)); } await this.lancamentoRepository.upsert(newItems, { conflictPaths: { id: true } }); } diff --git a/src/lancamento/dtos/lancamento-input.dto.ts b/src/lancamento/dtos/lancamento-input.dto.ts new file mode 100644 index 00000000..a4b6206a --- /dev/null +++ b/src/lancamento/dtos/lancamento-input.dto.ts @@ -0,0 +1,67 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsDateString, IsNotEmpty, IsNumber, IsString, Min } from 'class-validator'; +import { User } from 'src/users/entities/user.entity'; +import { DeepPartial } from 'typeorm'; + +export class LancamentoInputDto { + constructor(dto?: DeepPartial) { + if (dto) { + Object.assign(this, dto); + } + } + + @ApiProperty() + @IsNotEmpty() + @IsString() + descricao: string; + + @ApiProperty({ type: Number, example: 1.99 }) + @IsNumber() + valor: number; + + @ApiProperty({ type: 'DATE', example: '2024-08-01T19:54:56.299Z' }) + @IsDateString() + data_ordem: Date; + + @ApiProperty({ type: 'DATE', example: '2024-08-01T19:54:56.299Z' }) + @IsDateString() + data_pgto: Date; + + @ApiProperty() + @ApiProperty({ type: 'DATE', example: '2024-08-01T19:54:56.299Z' }) + @IsDateString() + data_lancamento: Date; + + @ApiProperty({ example: 10 }) + @IsNumber() + algoritmo: number; + + @ApiProperty({ type: Number, description: 'Valor de pagamento bloqueado.', example: 10.99 }) + @IsNumber() + glosa: number; + + @ApiProperty({ type: Number, example: 10.99 }) + @IsNumber() + recurso: number; + + @ApiProperty() + @IsNotEmpty() + @IsNumber() + anexo: number; + + @ApiProperty({ type: Number, example: 1.99 }) + @IsNumber() + valor_a_pagar: number; + + @ApiProperty({ description: 'Não sabemos se será número, ser terá zeros a esquerda ou terá letras' }) + @IsNotEmpty() + @IsString() + numero_processo: string; + + @ApiProperty({ type: Number, example: 2 }) + @Min(1) + id_cliente_favorecido: number; + + /** userId - Used internally */ + author: DeepPartial; +} diff --git a/src/lancamento/dtos/lancamentoDto.ts b/src/lancamento/dtos/lancamentoDto.ts deleted file mode 100644 index 03182a4e..00000000 --- a/src/lancamento/dtos/lancamentoDto.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { Transform } from 'class-transformer'; -import { - IsDate, - IsNotEmpty, - IsNumber, - IsNumberString, - IsString, - isNumber -} from 'class-validator'; -import { ClienteFavorecido } from 'src/cnab/entity/cliente-favorecido.entity'; -import { ValidateValue } from 'src/utils/validators/validate-value.validator'; -import { DeepPartial } from 'typeorm'; - -export class LancamentoDto { - @ApiProperty() - @IsNotEmpty() - @IsString() - descricao: string; - - @ApiProperty({ type: Number, example: 1.99 }) - @IsNumber() - valor: number; - - @ApiProperty() - @Transform((p) => new Date(p.value)) - @IsDate() - data_ordem: Date; - - @ApiProperty() - @Transform((p) => new Date(p.value)) - @IsDate() - data_pgto: Date; - - @ApiProperty() - @Transform((p) => new Date(p.value)) - @IsDate() - data_lancamento: Date; - - @ApiProperty({ example: 1 }) - @IsNumberString() - algoritmo: string; - - @ApiProperty({ type: Number, description: 'Valor de pagamento bloqueado.', example: 1 }) - @IsNumber() - glosa: number; - - @ApiProperty({ type: Number, example: 1 }) - @IsNumber() - recurso: number; - - @ApiProperty() - @IsNotEmpty() - @IsNumber() - anexo: number; - - @ApiProperty({ type: Number, example: 1.99 }) - @IsNumber() - valor_a_pagar: number; - - @ApiProperty() - @IsNotEmpty() - @IsString() - numero_processo: string; - - @ApiProperty({ type: Number, example: 2 }) - @Transform((params) => ({ id: params.value })) - @ValidateValue((v) => { - return isNumber(v.id) && v.id > 0 - }, { message: (v) => `Is not a number > 0 but ${v.value?.id} (type ${typeof v.value?.id})` })id_cliente_favorecido: DeepPartial; -} diff --git a/src/lancamento/interfaces/lancamento-seed-data.interface.ts b/src/lancamento/interfaces/lancamento-seed-data.interface.ts index e2377639..55a9dc29 100644 --- a/src/lancamento/interfaces/lancamento-seed-data.interface.ts +++ b/src/lancamento/interfaces/lancamento-seed-data.interface.ts @@ -1,6 +1,6 @@ -import { ClienteFavorecido } from "src/cnab/entity/cliente-favorecido.entity"; -import { User } from "src/users/entities/user.entity"; -import { DeepPartial } from "typeorm"; +import { ClienteFavorecido } from 'src/cnab/entity/cliente-favorecido.entity'; +import { User } from 'src/users/entities/user.entity'; +import { DeepPartial } from 'typeorm'; export interface LancamentoSeedData { id?: number; @@ -9,16 +9,14 @@ export interface LancamentoSeedData { data_lancamento: Date; data_ordem: Date; data_pgto: Date; - algoritmo: string; + algoritmo: number; glosa: number; recurso: number; valor_a_pagar: number; numero_processo: string; - id_cliente_favorecido: DeepPartial; - userId: number; - /** Probably we dont need this field or userId. */ - user: DeepPartial; + clienteFavorecido: DeepPartial; + autor: DeepPartial; /** @example `1,2,3` */ - auth_usersIds?: string; + autorizado_por?: string; anexo: number; } diff --git a/src/lancamento/interfaces/lancamento.interface.ts b/src/lancamento/interfaces/lancamento.interface.ts deleted file mode 100644 index 5a98a04c..00000000 --- a/src/lancamento/interfaces/lancamento.interface.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { ClienteFavorecido } from "src/cnab/entity/cliente-favorecido.entity"; -import { DeepPartial } from "typeorm"; - -/** - * Used as output data to frontend - */ -export interface ItfLancamento { - id: number; - descricao: string; - valor: number; - data_ordem: Date; - data_pgto: Date; - data_lancamento: Date; - algoritmo: string; - glosa: number; - recurso: number; - anexo: number; - valor_a_pagar: number; - numero_processo: string; - id_cliente_favorecido: DeepPartial; - // Added fields - auth_usersIds: number[]; - autorizadopor: number[]; -} diff --git a/src/lancamento/lancamento.controller.ts b/src/lancamento/lancamento.controller.ts index 668464b2..14a0dee3 100644 --- a/src/lancamento/lancamento.controller.ts +++ b/src/lancamento/lancamento.controller.ts @@ -1,15 +1,16 @@ import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put, Query, Request, UseGuards } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; -import { ApiBearerAuth, ApiBody, ApiQuery, ApiTags } from '@nestjs/swagger'; +import { ApiBearerAuth, ApiBody, ApiOperation, ApiQuery, ApiTags } from '@nestjs/swagger'; import { Roles } from 'src/roles/roles.decorator'; import { RoleEnum } from 'src/roles/roles.enum'; import { RolesGuard } from 'src/roles/roles.guard'; -import { LancamentoDto } from './dtos/lancamentoDto'; -import { ItfLancamento } from './interfaces/lancamento.interface'; -import { LancamentoService } from './lancamento.service'; -import { AutorizaLancamentoDto } from './dtos/AutorizaLancamentoDto'; import { ApiDescription } from 'src/utils/api-param/description-api-param'; +import { ParseBooleanPipe } from 'src/utils/pipes/parse-boolean.pipe'; import { ParseNumberPipe } from 'src/utils/pipes/parse-number.pipe'; +import { AutorizaLancamentoDto } from './dtos/AutorizaLancamentoDto'; +import { LancamentoInputDto } from './dtos/lancamento-input.dto'; +import { Lancamento } from './lancamento.entity'; +import { LancamentoService } from './lancamento.service'; @ApiTags('Lancamento') @Controller({ @@ -31,16 +32,16 @@ export class LancamentoController { @ApiQuery({ name: 'mes', type: Number, required: false, description: 'Mês do lançamento' }) @ApiQuery({ name: 'periodo', type: Number, required: false, description: ApiDescription({ _: 'Período do lançamento. Primeira quinzena ou segunda quinzena.', min: 1, max: 2 }) }) @ApiQuery({ name: 'ano', type: Number, required: false, description: 'Ano do lançamento.' }) - @ApiQuery({ name: 'autorizado', type: Number, required: false, description: 'use 1 ou 0 (ou deixe vazio) para filtrar por autorizado ou não autorizado.' }) + @ApiQuery({ name: 'autorizado', type: Boolean, required: false, description: 'Fitra se foi autorizado ou não.' }) @HttpCode(HttpStatus.OK) async getLancamento( @Request() request, // @Query('mes') mes: number, @Query('periodo', new ParseNumberPipe({ min: 1, max: 2, optional: true })) periodo: number | undefined, @Query('ano') ano: number, - @Query('autorizado') authorized: number, - ): Promise { - return await this.lancamentoService.findByPeriod(mes, periodo, ano, authorized); + @Query('autorizado') autorizado: boolean | undefined, + ): Promise { + return await this.lancamentoService.findByPeriod({ mes, periodo, ano, autorizado }); } @ApiBearerAuth() @@ -52,13 +53,14 @@ export class LancamentoController { RoleEnum.aprovador_financeiro, ) @Get('/getbystatus') - @ApiQuery({ name: 'status', required: false, description: 'use 1 ou 0 para autorizado ou não autorizado.' }) + @ApiQuery({ name: 'autorizado', type: Boolean, required: true, description: 'Fitra se foi autorizado ou não.' }) @HttpCode(HttpStatus.OK) async getByStatus( @Request() request, // - @Query('status') status: number, - ): Promise { - return await this.lancamentoService.findByStatus(status); + @Query('autorizado', new ParseBooleanPipe()) autorizado: boolean | undefined, + ): Promise { + const _autorizado = autorizado as boolean; + return await this.lancamentoService.findByStatus(_autorizado); } @ApiBearerAuth() @@ -91,29 +93,30 @@ export class LancamentoController { RoleEnum.lancador_financeiro, RoleEnum.aprovador_financeiro, ) - @ApiBody({ type: LancamentoDto }) + @ApiBody({ type: LancamentoInputDto }) @HttpCode(HttpStatus.CREATED) @Post('/create') async postCreateLancamento( @Request() req: any, // - @Body() lancamentoData: LancamentoDto, - ): Promise { - const userId = req.user.id; - const createdLancamento = await this.lancamentoService.create(lancamentoData, userId); - return createdLancamento.toItfLancamento(); + @Body() lancamentoDto: LancamentoInputDto, + ): Promise { + lancamentoDto.author = { id: req.user.id }; + const createdLancamento = await this.lancamentoService.create(lancamentoDto); + return createdLancamento; } - @ApiBearerAuth() + @Put('/authorize') + @HttpCode(HttpStatus.OK) @UseGuards(AuthGuard('jwt'), RolesGuard) @Roles( RoleEnum.master, // RoleEnum.admin_finan, RoleEnum.aprovador_financeiro, ) - @Put('/authorize') + @ApiOperation({ description: `Inclui uma autorização do usuário autenticado para o Lançamento.` }) + @ApiBearerAuth() @ApiQuery({ name: 'lancamentoId', required: true, description: 'Id do lançamento' }) @ApiBody({ type: AutorizaLancamentoDto }) - @HttpCode(HttpStatus.OK) async putAutorizarPagamento( @Request() req, // @Body() autorizaLancamentoDto: AutorizaLancamentoDto, @@ -123,7 +126,8 @@ export class LancamentoController { return await this.lancamentoService.autorizarPagamento(userId, lancamentoId, autorizaLancamentoDto); } - @ApiBearerAuth() + @Put('/') + @HttpCode(HttpStatus.OK) @UseGuards(AuthGuard('jwt'), RolesGuard) @Roles( RoleEnum.master, // @@ -131,17 +135,16 @@ export class LancamentoController { RoleEnum.lancador_financeiro, RoleEnum.aprovador_financeiro, ) - @Put('/') - @ApiBody({ type: LancamentoDto }) - @HttpCode(HttpStatus.OK) + @ApiBearerAuth() + @ApiBody({ type: LancamentoInputDto }) @ApiQuery({ name: 'lancamentoId', required: true, description: 'Id do lançamento' }) async atualizaLancamento( @Request() req, - @Body() lancamentoData: LancamentoDto, // It was ItfLancamento + @Query('lancamentoId', new ParseNumberPipe({ min: 1 })) lancamentoId: number, + @Body() lancamentoDto: LancamentoInputDto, // It was ItfLancamento ) { - const id = req.query.lancamentoId; - const userId = req.user.id; - return await this.lancamentoService.update(id, lancamentoData, userId); + lancamentoDto.author = { id: req.user.id }; + return await this.lancamentoService.update(lancamentoId, lancamentoDto); } @ApiBearerAuth() diff --git a/src/lancamento/lancamento.entity.ts b/src/lancamento/lancamento.entity.ts index 2c0d0296..78f03d1d 100644 --- a/src/lancamento/lancamento.entity.ts +++ b/src/lancamento/lancamento.entity.ts @@ -1,30 +1,55 @@ +import { Exclude, Expose, Transform } from 'class-transformer'; import { ClienteFavorecido } from 'src/cnab/entity/cliente-favorecido.entity'; import { Transacao } from 'src/cnab/entity/pagamento/transacao.entity'; import { User } from 'src/users/entities/user.entity'; import { EntityHelper } from 'src/utils/entity-helper'; -import { - AfterLoad, - Column, - CreateDateColumn, - DeepPartial, - Entity, - JoinColumn, - ManyToOne, - PrimaryGeneratedColumn, - UpdateDateColumn, -} from 'typeorm'; -import { ItfLancamento } from './interfaces/lancamento.interface'; import { asStringOrNumber } from 'src/utils/pipe-utils'; +import { AfterLoad, BeforeInsert, BeforeUpdate, Column, CreateDateColumn, DeepPartial, Entity, JoinColumn, ManyToOne, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'; +import { LancamentoInputDto } from './dtos/lancamento-input.dto'; @Entity('lancamento') -export class LancamentoEntity extends EntityHelper { - constructor(lancamento?: DeepPartial) { +export class Lancamento extends EntityHelper { + constructor(lancamento?: DeepPartial) { super(); if (lancamento !== undefined) { Object.assign(this, lancamento); } } + public static fromInputDto(dto: LancamentoInputDto) { + return new Lancamento({ + descricao: dto.descricao, + valor: dto.valor, + data_ordem: new Date(dto.data_ordem), + data_pgto: new Date(dto.data_pgto), + data_lancamento: new Date(dto.data_lancamento), + algoritmo: dto.algoritmo, + glosa: dto.glosa, + recurso: dto.recurso, + anexo: dto.anexo, + valor_a_pagar: dto.valor_a_pagar, + numero_processo: dto.numero_processo, + clienteFavorecido: { id: dto.id_cliente_favorecido }, + autor: dto.author, + }); + } + + updateFromInputDto(dto: LancamentoInputDto) { + this.descricao = dto.descricao; + this.valor = dto.valor; + this.data_ordem = dto.data_ordem; + this.data_pgto = dto.data_pgto; + this.data_lancamento = dto.data_lancamento; + this.algoritmo = dto.algoritmo; + this.glosa = dto.glosa; + this.recurso = dto.recurso; + this.anexo = dto.anexo; + this.valor_a_pagar = dto.valor_a_pagar; + this.numero_processo = dto.numero_processo; + this.clienteFavorecido = new ClienteFavorecido({ id: dto.id_cliente_favorecido }); + this.autor = new User(dto.author); + } + @PrimaryGeneratedColumn({ primaryKeyConstraintName: 'PK_Lancamento_id' }) id: number; @@ -50,30 +75,28 @@ export class LancamentoEntity extends EntityHelper { @JoinColumn({ foreignKeyConstraintName: 'FK_Lancamento_transacao_ManyToOne' }) transacao: Transacao; - /** @example `1,2,3` */ - @Column({ type: 'varchar', nullable: true }) - auth_usersIds?: string; + /** + * Coluna - lista de User.id + * @example `1,2,3` + */ + @Exclude() + @Column({ name: 'autorizado_por', type: 'varchar', nullable: true }) + autorizado_por: string | null; - @Column({ type: 'int', nullable: false }) - userId?: number; - - /** Is userId the same as user? Why do we have two of them? */ @ManyToOne(() => User) - @JoinColumn({ - name: 'userId', - foreignKeyConstraintName: 'FK_Lancamento_user_ManyToOne', - }) - user: User; + @JoinColumn({ name: 'userId', foreignKeyConstraintName: 'FK_Lancamento_user_ManyToOne' }) + @Expose({ name: 'userId' }) + @Transform(({ value }) => (value as User).id) + autor: User; @ManyToOne(() => ClienteFavorecido, { eager: true }) - @JoinColumn({ - name: 'id_cliente_favorecido', - foreignKeyConstraintName: 'FK_Lancamento_idClienteFavorecido_ManyToOne', - }) - id_cliente_favorecido: ClienteFavorecido; + @JoinColumn({ name: 'id_cliente_favorecido', foreignKeyConstraintName: 'FK_Lancamento_idClienteFavorecido_ManyToOne' }) + @Expose({ name: 'id_cliente_favorecido' }) + @Transform(({ value }) => (value as ClienteFavorecido).id) + clienteFavorecido: ClienteFavorecido; - @Column({ type: 'varchar', nullable: false }) - algoritmo: string; + @Column({ type: 'numeric', nullable: false }) + algoritmo: number; @Column({ type: 'numeric', nullable: false }) glosa: number; @@ -96,31 +119,23 @@ export class LancamentoEntity extends EntityHelper { @UpdateDateColumn() updatedAt: Date; - toItfLancamento(): ItfLancamento { - return { - id: this.id, - descricao: this.descricao, - valor: this.valor, - data_ordem: this.data_ordem, - data_pgto: this.data_pgto, - data_lancamento: this.data_lancamento, - algoritmo: this.algoritmo, - glosa: this.glosa, - recurso: this.recurso, - anexo: this.anexo, - valor_a_pagar: this.valor_a_pagar, - numero_processo: this.numero_processo, - id_cliente_favorecido: this.id_cliente_favorecido, - auth_usersIds: this.auth_usersIds?.split(',')?.map(Number) || [], - autorizadopor: [], - }; - } - @AfterLoad() setReadValues() { this.glosa = asStringOrNumber(this.glosa); + this.algoritmo = asStringOrNumber(this.algoritmo); this.recurso = asStringOrNumber(this.recurso); this.anexo = asStringOrNumber(this.anexo); this.valor_a_pagar = asStringOrNumber(this.valor_a_pagar); } + + getIsAutorizado() { + const list = (this.autorizado_por || '').split(','); + return list.length >= 2; + } + + pushAutorizado(userId: number) { + const list = (this.autorizado_por || '').split(','); + list.push('' + userId); + this.autorizado_por = list.join(','); + } } diff --git a/src/lancamento/lancamento.module.ts b/src/lancamento/lancamento.module.ts index 09c174b5..423994d8 100644 --- a/src/lancamento/lancamento.module.ts +++ b/src/lancamento/lancamento.module.ts @@ -2,16 +2,19 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { UsersModule } from 'src/users/users.module'; import { LancamentoController } from './lancamento.controller'; -import { LancamentoEntity } from './lancamento.entity'; +import { Lancamento } from './lancamento.entity'; import { LancamentoService } from './lancamento.service'; import { LancamentoRepository } from './lancamento.repository'; +import { ClienteFavorecidoModule } from 'src/cnab/cliente-favorecido.module'; @Module({ - imports: [TypeOrmModule.forFeature([LancamentoEntity]), + imports: [ UsersModule, + ClienteFavorecidoModule, + TypeOrmModule.forFeature([Lancamento]), // ], controllers: [LancamentoController], providers: [LancamentoService, LancamentoRepository], exports: [LancamentoService, LancamentoRepository], }) -export class LancamentoModule { } +export class LancamentoModule {} diff --git a/src/lancamento/lancamento.repository.ts b/src/lancamento/lancamento.repository.ts index b75109cd..939e370f 100644 --- a/src/lancamento/lancamento.repository.ts +++ b/src/lancamento/lancamento.repository.ts @@ -1,46 +1,32 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { - DeepPartial, - DeleteResult, - FindManyOptions, - FindOneOptions, - Repository, - SaveOptions, -} from 'typeorm'; -import { LancamentoEntity } from './lancamento.entity'; +import { DeepPartial, DeleteResult, FindManyOptions, FindOneOptions, Repository, SaveOptions } from 'typeorm'; +import { Lancamento } from './lancamento.entity'; @Injectable() export class LancamentoRepository { constructor( - @InjectRepository(LancamentoEntity) - private readonly lancamentoRepository: Repository, + @InjectRepository(Lancamento) + private readonly lancamentoRepository: Repository, ) {} - create(entityLike: DeepPartial): LancamentoEntity { + create(entityLike: DeepPartial): Lancamento { return this.lancamentoRepository.create(entityLike); } - save( - entity: DeepPartial, - options?: SaveOptions, - ): Promise { + save(entity: DeepPartial, options?: SaveOptions): Promise { return this.lancamentoRepository.save(entity, options); } - findOne( - options: FindOneOptions, - ): Promise { + findOne(options: FindOneOptions): Promise { return this.findOne(options); } - findMany( - options?: FindManyOptions | undefined, - ): Promise { + findMany(options?: FindManyOptions | undefined): Promise { return this.lancamentoRepository.find(options); } - getAll(): Promise { + getAll(): Promise { return this.lancamentoRepository.find(); } diff --git a/src/lancamento/lancamento.service.ts b/src/lancamento/lancamento.service.ts index d6e81d77..ad293dba 100644 --- a/src/lancamento/lancamento.service.ts +++ b/src/lancamento/lancamento.service.ts @@ -1,29 +1,24 @@ -import { - HttpException, - HttpStatus, - Injectable, - NotFoundException, -} from '@nestjs/common'; +import { HttpException, HttpStatus, Injectable, NotFoundException } from '@nestjs/common'; import * as bcrypt from 'bcryptjs'; import { endOfDay, isFriday, nextFriday, startOfDay, subDays } from 'date-fns'; +import { ClienteFavorecidoService } from 'src/cnab/service/cliente-favorecido.service'; import { UsersService } from 'src/users/users.service'; import { CustomLogger } from 'src/utils/custom-logger'; -import { Between, In, IsNull, Like } from 'typeorm'; +import { CommonHttpException } from 'src/utils/http-exception/common-http-exception'; +import { Between, IsNull, Like, Not } from 'typeorm'; import { AutorizaLancamentoDto } from './dtos/AutorizaLancamentoDto'; -import { LancamentoDto } from './dtos/lancamentoDto'; -import { ItfLancamento } from './interfaces/lancamento.interface'; -import { LancamentoEntity } from './lancamento.entity'; +import { LancamentoInputDto } from './dtos/lancamento-input.dto'; +import { Lancamento } from './lancamento.entity'; import { LancamentoRepository } from './lancamento.repository'; @Injectable() export class LancamentoService { - private readonly logger = new CustomLogger(LancamentoService.name, { - timestamp: true, - }); + private readonly logger = new CustomLogger(LancamentoService.name, { timestamp: true }); constructor( - private readonly lancamentoRepository: LancamentoRepository, + private readonly lancamentoRepository: LancamentoRepository, // private readonly usersService: UsersService, + private readonly clienteFavorecidoService: ClienteFavorecidoService, ) {} // /** @@ -42,266 +37,125 @@ export class LancamentoService { /** * Get unused data (no Transacao Id) from current payment week (sex-qui). */ - findToPayWeek(): Promise { + async findToPayWeek(): Promise { const today = new Date(); const friday = isFriday(today) ? today : nextFriday(today); const sex = startOfDay(subDays(friday, 7)); const qui = endOfDay(subDays(friday, 1)); - return this.lancamentoRepository.findMany({ + return await this.lancamentoRepository.findMany({ where: { data_lancamento: Between(sex, qui), transacao: IsNull(), - auth_usersIds: Like('%,%'), // At least 2 approved (1 comma) + autorizado_por: Like('%,%'), // Ao menos 2 aprovados (1 vírgula) }, }); } - async findByPeriod( - month?: number, - period?: number, - year?: number, - authorized?: number, - ): Promise { - let startDate: Date | undefined; - let endDate: Date | undefined; - - this.logger.debug(String(`Find Lancamento by period: ${period}`)); - if ( - month && period && year && - !isNaN(month) && - !isNaN(period) && - !isNaN(year) && - typeof month === 'number' && - typeof period === 'number' && - typeof year === 'number' - ) { - [startDate, endDate] = this.getMonthDateRange(year, month, period); - this.logger.debug(String(`${startDate}, ${endDate}`)); - } - - const whereOptions: any = {}; - if (startDate && endDate) { - whereOptions.data_lancamento = Between(startDate, endDate); + async findByPeriod(args?: { mes?: number; periodo?: number; ano?: number; autorizado?: boolean }): Promise { + /** [startDate, endDate] */ + let dateRange: [Date, Date] | null = null; + if (args?.mes && args?.periodo && args?.ano) { + dateRange = this.getMonthDateRange(args.ano, args.mes, args.periodo); } const lancamentos = await this.lancamentoRepository.findMany({ - where: whereOptions, + where: { + ...(dateRange ? { data_lancamento: Between(...dateRange) } : {}), + ...(args?.autorizado !== undefined ? (args.autorizado === true ? { autorizado_por: Like('%,%') } : { autorizado_por: Not(Like('%,%')) }) : {}), + }, relations: ['user'], }); - const allUserIds = new Set(); - lancamentos.forEach((lancamento) => { - if (lancamento.auth_usersIds) { - lancamento.auth_usersIds - .split(',') - .forEach((id) => allUserIds.add(Number(id))); - } - }); - - let usersMap = new Map(); - if (allUserIds.size > 0) { - const users = await this.usersService.findMany({ - where: { - id: In([...allUserIds]), - }, - }); - usersMap = new Map(users.map((user) => [user.id, user])); - } - - const lancamentosComUsuarios = lancamentos.map((lancamento) => { - const userIds = lancamento.auth_usersIds - ? lancamento.auth_usersIds.split(',').map(Number) - : []; - const autorizadopor: number[] = userIds - .map((id) => usersMap.get(id)) - .filter((user) => user !== undefined); - return { - ...lancamento.toItfLancamento(), - autorizadopor: autorizadopor, - } as ItfLancamento; - }); - - if (authorized === 1) { - this.logger.debug('AUTHORIZED 1'); - return lancamentosComUsuarios.filter( - (lancamento) => lancamento.autorizadopor.length === 2, - ); - } - - if (authorized === 0) { - this.logger.debug('AUTHORIZED 0'); - return lancamentosComUsuarios.filter( - (lancamento) => lancamento.autorizadopor.length < 2, - ); - } - - return lancamentosComUsuarios; + return lancamentos; } - async findByStatus(status: number | null = null): Promise { - const lancamentos = await this.lancamentoRepository.findMany({ + async findByStatus(status: boolean): Promise { + let lancamentos = await this.lancamentoRepository.findMany({ + where: { + ...(status === true ? { autorizado_por: Like('%,%') } : { autorizado_por: Not(Like('%,%')) }), + }, relations: ['user'], }); - const allUserIds = new Set(); - lancamentos.forEach((lancamento) => { - lancamento.auth_usersIds - ?.split(',') - .forEach((id) => allUserIds.add(Number(id))); - }); - - let usersMap = new Map(); - if (allUserIds.size > 0) { - const users = await this.usersService.findMany({ - where: { - id: In([...allUserIds]), - }, - }); - usersMap = new Map(users.map((user) => [user.id, user])); - } - - const lancamentosComUsuarios = lancamentos.map((lancamento) => { - const userIds = lancamento.auth_usersIds - ? lancamento.auth_usersIds.split(',').map(Number) - : []; - const autorizadopor = userIds - .map((id) => usersMap.get(id)) - .filter((user) => user); - return { - ...lancamento.toItfLancamento(), - autorizadopor, - } as ItfLancamento; - }); - - if (status === 1) { - return lancamentosComUsuarios.filter( - (lancamento) => lancamento.autorizadopor.length === 2, - ); - } else { - return lancamentosComUsuarios.filter( - (lancamento) => lancamento.autorizadopor.length !== 2, - ); - } + return lancamentos; } - async getValorAutorizado( - month: number, - period: number, - year: number, - ): Promise { + async getValorAutorizado(month: number, period: number, year: number) { const [startDate, endDate] = this.getMonthDateRange(year, month, period); - const response = await this.lancamentoRepository.findMany({ + const autorizados = await this.lancamentoRepository.findMany({ where: { data_lancamento: Between(startDate, endDate), + autorizado_por: Like('%,%'), // Ao menos 2 aprovados (1 vírgula) }, }); - - const filteredResponse = response.filter( - (item) => item.auth_usersIds && item.auth_usersIds.split(',').length >= 2, - ); - this.logger.debug(`filteredResponse: ${filteredResponse}`); - - const sumOfValues = filteredResponse.reduce( - (acc, curr) => acc + curr.valor, - 0, - ); - - const formattedSum = sumOfValues.toLocaleString('pt-BR', { - style: 'currency', - currency: 'BRL', - }); - + const autorizadoSum = autorizados.reduce((sum, lancamento) => sum + lancamento.valor, 0); const resp = { - valor_autorizado: String(formattedSum), + valor_autorizado: autorizadoSum, }; - return resp; } - async create( - lancamentoData: LancamentoDto, - userId: number, - ): Promise { - const newLancamento = this.lancamentoRepository.create(lancamentoData); - newLancamento.userId = userId; - return await this.lancamentoRepository.save(newLancamento); + async create(dto: LancamentoInputDto): Promise { + const lancamento = await this.validateLancamentoDto(dto); + const created = this.lancamentoRepository.create(lancamento); + return await this.lancamentoRepository.save(created); } - async autorizarPagamento( - userId: number, - lancamentoId: string, - AutorizaLancamentoDto: AutorizaLancamentoDto, - ): Promise { - const user = await this.usersService.findOne({ - id: userId, - }); - - if (!user) - throw new HttpException( - 'Usuário não encontrado', - HttpStatus.UNAUTHORIZED, - ); + async validateLancamentoDto(dto: LancamentoInputDto): Promise { + const favorecido = await this.clienteFavorecidoService.findOne({ where: { id: dto.id_cliente_favorecido } }); + if (!favorecido) { + throw CommonHttpException.message(`id_cliente_favorecido: Favorecido não encontrado no sistema`); + } + const lancamento = Lancamento.fromInputDto(dto); + lancamento.clienteFavorecido = favorecido; + return lancamento; + } - const isValidPassword = await bcrypt.compare( - AutorizaLancamentoDto.password, - user.password, - ); + async autorizarPagamento(userId: number, lancamentoId: string, AutorizaLancamentoDto: AutorizaLancamentoDto): Promise { + const user = await this.usersService.findOne({ id: userId }); + if (!user) { + throw new HttpException('Usuário não encontrado', HttpStatus.UNAUTHORIZED); + } - if (!isValidPassword) + const isValidPassword = await bcrypt.compare(AutorizaLancamentoDto.password, user.password); + if (!isValidPassword) { throw new HttpException('Senha inválida', HttpStatus.UNAUTHORIZED); + } - const lancamento = await this.lancamentoRepository.findOne({ - where: { id: parseInt(lancamentoId) }, - }); - + const lancamento = await this.lancamentoRepository.findOne({ where: { id: parseInt(lancamentoId) } }); if (!lancamento) { throw new Error('Lançamento não encontrado.'); } - const userIds = new Set( - lancamento.auth_usersIds - ? lancamento.auth_usersIds.split(',').map(Number) - : [], - ); - userIds.add(userId); + lancamento.pushAutorizado(userId); - lancamento.auth_usersIds = Array.from(userIds).join(','); - return (await this.lancamentoRepository.save(lancamento)).toItfLancamento(); + return await this.lancamentoRepository.save(lancamento); } - async update( - id: number, - updatedData: LancamentoDto, - userId: number, - ): Promise { - let lancamento = await this.lancamentoRepository.findOne({ where: { id } }); + async update(id: number, updateDto: LancamentoInputDto): Promise { + const lancamento = await this.lancamentoRepository.findOne({ where: { id } }); if (!lancamento) { throw new NotFoundException(`Lançamento com ID ${id} não encontrado.`); } - const { id_cliente_favorecido, ...restUpdatedData } = updatedData; - lancamento = new LancamentoEntity({ - ...lancamento, - ...restUpdatedData, - userId, - auth_usersIds: '', - }); + const update = Lancamento.fromInputDto(updateDto); + update.updateFromInputDto(updateDto); - await this.lancamentoRepository.save(lancamento); - this.logger.log(`Lancamento ${id_cliente_favorecido} atualizado`); + const updated = await this.lancamentoRepository.save(lancamento); + this.logger.log(`Lancamento #${updated.id} atualizado por ${updated.clienteFavorecido.nome}.`); - return lancamento.toItfLancamento(); + return lancamento; } - async getById(id: number): Promise { + async getById(id: number): Promise { const lancamento = await this.lancamentoRepository.findOne({ where: { id }, }); if (!lancamento) { throw new NotFoundException(`Lançamento com ID ${id} não encontrado.`); } - return lancamento.toItfLancamento(); + return lancamento; } async delete(id: number): Promise { diff --git a/src/users/entities/user.entity.ts b/src/users/entities/user.entity.ts index b7fc39d8..bb42c78f 100644 --- a/src/users/entities/user.entity.ts +++ b/src/users/entities/user.entity.ts @@ -17,6 +17,7 @@ import { Entity, Index, JoinColumn, + ManyToMany, ManyToOne, OneToMany, PrimaryGeneratedColumn, @@ -26,6 +27,7 @@ import { FileEntity } from '../../files/entities/file.entity'; import { Role } from '../../roles/entities/role.entity'; import { Status } from '../../statuses/entities/status.entity'; import { UserHttpException } from 'src/utils/http-exception/user-http-exception'; +import { Lancamento } from 'src/lancamento/lancamento.entity'; /** uniqueConstraintName: `UQ_User_email` */ @Entity() @@ -200,10 +202,7 @@ export class User extends EntityHelper { 'bankAccountDigit', ]; - return requiredFields.filter( - (field) => - !(typeof this[field] === 'boolean' || Boolean(this[field]) === true), - ); + return requiredFields.filter((field) => !(typeof this[field] === 'boolean' || Boolean(this[field]) === true)); } @OneToMany(() => MailHistory, (mailHistory) => mailHistory.user.id, { @@ -269,10 +268,7 @@ export class User extends EntityHelper { * Get field validated * @throws `HttpException` */ - getBankAgency(args?: { - errorMessage?: string; - httpStatusCode?: HttpStatus; - }): string { + getBankAgency(args?: { errorMessage?: string; httpStatusCode?: HttpStatus }): string { if (!this.bankAgency) { throw UserHttpException.invalidField('bankAgency', { errorMessage: args?.errorMessage, @@ -286,10 +282,7 @@ export class User extends EntityHelper { * Get field validated * @throws `HttpException` */ - getBankAgencyWithoutDigit(args?: { - errorMessage?: string; - httpStatusCode?: HttpStatus; - }): string { + getBankAgencyWithoutDigit(args?: { errorMessage?: string; httpStatusCode?: HttpStatus }): string { const agency = this.getBankAgency(args); return agency.substring(0, agency.length - 1); } @@ -298,10 +291,7 @@ export class User extends EntityHelper { * Get field validated * @throws `HttpException` */ - getBankAgencyDigit(args?: { - errorMessage?: string; - httpStatusCode?: HttpStatus; - }): string { + getBankAgencyDigit(args?: { errorMessage?: string; httpStatusCode?: HttpStatus }): string { const agency = this.getBankAgency(args); return agency.substring(agency.length - 1); } @@ -310,10 +300,7 @@ export class User extends EntityHelper { * Get field validated * @throws `HttpException` */ - getBankAccount(args?: { - errorMessage?: string; - httpStatusCode?: HttpStatus; - }): string { + getBankAccount(args?: { errorMessage?: string; httpStatusCode?: HttpStatus }): string { if (!this.bankAccount) { throw UserHttpException.invalidField('bankAgency', { errorMessage: args?.errorMessage, @@ -327,10 +314,7 @@ export class User extends EntityHelper { * Get field validated * @throws `HttpException` */ - getBankAccountDigit(args?: { - errorMessage?: string; - httpStatusCode?: HttpStatus; - }): string { + getBankAccountDigit(args?: { errorMessage?: string; httpStatusCode?: HttpStatus }): string { if (!this.bankAccountDigit) { throw UserHttpException.invalidField('bankAgency', { errorMessage: args?.errorMessage, @@ -344,10 +328,7 @@ export class User extends EntityHelper { * Get field validated * @throws `HttpException` */ - getBankCode(args?: { - errorMessage?: string; - httpStatusCode?: HttpStatus; - }): number { + getBankCode(args?: { errorMessage?: string; httpStatusCode?: HttpStatus }): number { if (!this.bankCode) { throw UserHttpException.invalidField('bankAgency', { errorMessage: args?.errorMessage, @@ -361,10 +342,7 @@ export class User extends EntityHelper { * Get field validated * @throws `HttpException` */ - getFullName(args?: { - errorMessage?: string; - httpStatusCode?: HttpStatus; - }): string { + getFullName(args?: { errorMessage?: string; httpStatusCode?: HttpStatus }): string { if (!this.fullName) { throw UserHttpException.invalidField('bankAgency', { errorMessage: args?.errorMessage, @@ -378,10 +356,7 @@ export class User extends EntityHelper { * Get field validated * @throws `HttpException` */ - getCpfCnpj(args?: { - errorMessage?: string; - httpStatusCode?: HttpStatus; - }): string { + getCpfCnpj(args?: { errorMessage?: string; httpStatusCode?: HttpStatus }): string { if (!this.cpfCnpj) { throw UserHttpException.invalidField('cpfCnpj', { errorMessage: args?.errorMessage, @@ -395,10 +370,7 @@ export class User extends EntityHelper { * Get field validated * @throws `HttpException` */ - getPermitCode(args?: { - errorMessage?: string; - httpStatusCode?: HttpStatus; - }): string { + getPermitCode(args?: { errorMessage?: string; httpStatusCode?: HttpStatus }): string { if (!this.permitCode) { throw UserHttpException.invalidField('permitCode', { errorMessage: args?.errorMessage, diff --git a/src/utils/all-exteptions-filter/filters/all-exceptions.filter.ts b/src/utils/all-exteptions-filter/filters/all-exceptions.filter.ts index 721c315a..ddbc1521 100644 --- a/src/utils/all-exteptions-filter/filters/all-exceptions.filter.ts +++ b/src/utils/all-exteptions-filter/filters/all-exceptions.filter.ts @@ -31,13 +31,12 @@ export class AllExceptionsFilter implements ExceptionFilter { const error = exception instanceof Error ? (exception as Error) : undefined; this.logger.error(this.getErrorLogSummary(responseData), error?.stack); - response.status(responseData.statusCode).json( - responseData?.response || { - status: responseData.statusCode, - ...responseData.clientMessage, - timestamp: responseData.timestamp, - }, - ); + const responseJson = responseData?.response || { + status: responseData.statusCode, + ...responseData.clientMessage, + timestamp: responseData.timestamp, + }; + response.status(responseData.statusCode).json(responseJson); } private getResponseData(kwargs: { From fff84649778a602085d41cece30942a79ec3afd1 Mon Sep 17 00:00:00 2001 From: Raphael Rivas Date: Thu, 29 Aug 2024 17:44:52 -0300 Subject: [PATCH 04/29] fix: lancamento optional create params --- .../pagamento/item-transacao.service.ts | 2 +- .../migrations/1724964086793-Lancamento_2v.ts | 34 +++++++++++ .../lancamento-seed-data.service.ts | 12 ++-- .../lancamento/lancamento-seed.service.ts | 4 +- src/lancamento/dtos/lancamento-input.dto.ts | 33 +++++------ .../lancamento-seed-data.interface.ts | 1 - src/lancamento/lancamento.entity.ts | 56 ++++++++++--------- src/lancamento/lancamento.service.ts | 8 ++- 8 files changed, 89 insertions(+), 61 deletions(-) create mode 100644 src/database/migrations/1724964086793-Lancamento_2v.ts diff --git a/src/cnab/service/pagamento/item-transacao.service.ts b/src/cnab/service/pagamento/item-transacao.service.ts index f088e6c4..8126a28b 100644 --- a/src/cnab/service/pagamento/item-transacao.service.ts +++ b/src/cnab/service/pagamento/item-transacao.service.ts @@ -53,7 +53,7 @@ export class ItemTransacaoService { const itemTransacao = new ItemTransacao({ clienteFavorecido: { id: favorecido.id }, transacao: { id: transacao.id }, - valor: lancamento.valor_a_pagar, + valor: lancamento.valor, dataOrdem: lancamento.data_ordem, }); return itemTransacao; diff --git a/src/database/migrations/1724964086793-Lancamento_2v.ts b/src/database/migrations/1724964086793-Lancamento_2v.ts new file mode 100644 index 00000000..dbcc1836 --- /dev/null +++ b/src/database/migrations/1724964086793-Lancamento_2v.ts @@ -0,0 +1,34 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class Lancamento2v1724964086793 implements MigrationInterface { + name = 'Lancamento2v1724964086793' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "lancamento" DROP CONSTRAINT "FK_Lancamento_user_ManyToOne"`); + await queryRunner.query(`ALTER TABLE "lancamento" DROP COLUMN "userId"`); + await queryRunner.query(`ALTER TABLE "lancamento" DROP COLUMN "valor_a_pagar"`); + await queryRunner.query(`ALTER TABLE "lancamento" DROP COLUMN "descricao"`); + await queryRunner.query(`ALTER TABLE "lancamento" ADD "autorId" integer`); + await queryRunner.query(`ALTER TABLE "lancamento" ALTER COLUMN "data_pgto" DROP NOT NULL`); + await queryRunner.query(`ALTER TABLE "lancamento" ALTER COLUMN "data_pgto" DROP DEFAULT`); + await queryRunner.query(`ALTER TABLE "lancamento" ALTER COLUMN "glosa" SET DEFAULT '0'`); + await queryRunner.query(`ALTER TABLE "lancamento" ALTER COLUMN "recurso" SET DEFAULT '0'`); + await queryRunner.query(`ALTER TABLE "lancamento" ALTER COLUMN "anexo" SET DEFAULT '0'`); + await queryRunner.query(`ALTER TABLE "lancamento" ADD CONSTRAINT "FK_Lancamento_autor_ManyToOne" FOREIGN KEY ("autorId") REFERENCES "user"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "lancamento" DROP CONSTRAINT "FK_Lancamento_autor_ManyToOne"`); + await queryRunner.query(`ALTER TABLE "lancamento" ALTER COLUMN "anexo" DROP DEFAULT`); + await queryRunner.query(`ALTER TABLE "lancamento" ALTER COLUMN "recurso" DROP DEFAULT`); + await queryRunner.query(`ALTER TABLE "lancamento" ALTER COLUMN "glosa" DROP DEFAULT`); + await queryRunner.query(`ALTER TABLE "lancamento" ALTER COLUMN "data_pgto" SET DEFAULT now()`); + await queryRunner.query(`ALTER TABLE "lancamento" ALTER COLUMN "data_pgto" SET NOT NULL`); + await queryRunner.query(`ALTER TABLE "lancamento" DROP COLUMN "autorId"`); + await queryRunner.query(`ALTER TABLE "lancamento" ADD "descricao" character varying NOT NULL`); + await queryRunner.query(`ALTER TABLE "lancamento" ADD "valor_a_pagar" numeric NOT NULL`); + await queryRunner.query(`ALTER TABLE "lancamento" ADD "userId" integer`); + await queryRunner.query(`ALTER TABLE "lancamento" ADD CONSTRAINT "FK_Lancamento_user_ManyToOne" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); + } + +} diff --git a/src/database/seeds/lancamento/lancamento-seed-data.service.ts b/src/database/seeds/lancamento/lancamento-seed-data.service.ts index 95f5c08b..11a8ec45 100644 --- a/src/database/seeds/lancamento/lancamento-seed-data.service.ts +++ b/src/database/seeds/lancamento/lancamento-seed-data.service.ts @@ -48,13 +48,12 @@ export class LancamentoSeedDataService { ? ([ { algoritmo: '1', - descricao: seedTag + 'Unapproved', clienteFavorecido: { id: favorecidos[0].id }, data_lancamento: today, data_ordem: today, data_pgto: today, glosa: 1, - numero_processo: '1', + numero_processo: seedTag + 'Unapproved', recurso: 1, valor: 110, valor_a_pagar: 110, @@ -63,13 +62,12 @@ export class LancamentoSeedDataService { }, { algoritmo: '2', - descricao: seedTag + 'Approved 1', clienteFavorecido: { id: favorecidos[0].id }, data_lancamento: today, data_ordem: today, data_pgto: today, glosa: 2, - numero_processo: '2', + numero_processo: seedTag + 'Approved 1', recurso: 2, valor: 951, valor_a_pagar: 951, @@ -79,13 +77,12 @@ export class LancamentoSeedDataService { }, { algoritmo: 2, - descricao: seedTag + 'Approved 2', clienteFavorecido: { id: favorecidos[0].id }, data_lancamento: today, data_ordem: today, data_pgto: today, glosa: 2, - numero_processo: '2', + numero_processo: seedTag + 'Approved 2', recurso: 2, valor: 951.12, valor_a_pagar: 951.12, @@ -95,13 +92,12 @@ export class LancamentoSeedDataService { }, { algoritmo: 3, - descricao: seedTag + 'Approved 2 again', clienteFavorecido: { id: favorecidos[0].id }, data_lancamento: today, data_ordem: today, data_pgto: today, glosa: 2, - numero_processo: '2', + numero_processo: seedTag + 'Approved 2 again', recurso: 2, valor: 1034.01, valor_a_pagar: 1034.01, diff --git a/src/database/seeds/lancamento/lancamento-seed.service.ts b/src/database/seeds/lancamento/lancamento-seed.service.ts index 37753e5a..8dacec1e 100644 --- a/src/database/seeds/lancamento/lancamento-seed.service.ts +++ b/src/database/seeds/lancamento/lancamento-seed.service.ts @@ -18,9 +18,9 @@ export class LancamentoSeedService { async run() { const { fixtures } = await this.lancamentoSeedDataService.getData(); - const descricoes = fixtures.map((i) => i.descricao); + const descricoes = fixtures.map((i) => i.numero_processo); // Remove existing seeds - await this.lancamentoRepository.delete({ descricao: In(descricoes) }); + await this.lancamentoRepository.delete({ numero_processo: In(descricoes) }); const newItems: Lancamento[] = []; for (const fixture of fixtures) { newItems.push(new Lancamento(fixture)); diff --git a/src/lancamento/dtos/lancamento-input.dto.ts b/src/lancamento/dtos/lancamento-input.dto.ts index a4b6206a..fb416a15 100644 --- a/src/lancamento/dtos/lancamento-input.dto.ts +++ b/src/lancamento/dtos/lancamento-input.dto.ts @@ -1,5 +1,5 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsDateString, IsNotEmpty, IsNumber, IsString, Min } from 'class-validator'; +import { IsDateString, IsNotEmpty, IsNumber, IsOptional, IsString, Min } from 'class-validator'; import { User } from 'src/users/entities/user.entity'; import { DeepPartial } from 'typeorm'; @@ -10,12 +10,8 @@ export class LancamentoInputDto { } } - @ApiProperty() - @IsNotEmpty() - @IsString() - descricao: string; - @ApiProperty({ type: Number, example: 1.99 }) + @IsOptional() @IsNumber() valor: number; @@ -23,10 +19,6 @@ export class LancamentoInputDto { @IsDateString() data_ordem: Date; - @ApiProperty({ type: 'DATE', example: '2024-08-01T19:54:56.299Z' }) - @IsDateString() - data_pgto: Date; - @ApiProperty() @ApiProperty({ type: 'DATE', example: '2024-08-01T19:54:56.299Z' }) @IsDateString() @@ -36,22 +28,25 @@ export class LancamentoInputDto { @IsNumber() algoritmo: number; - @ApiProperty({ type: Number, description: 'Valor de pagamento bloqueado.', example: 10.99 }) + @ApiProperty({ type: Number, required: false, description: 'Valor de pagamento bloqueado.', example: 10.99 }) + @IsOptional() @IsNumber() - glosa: number; + glosa?: number; - @ApiProperty({ type: Number, example: 10.99 }) + @ApiProperty({ type: Number, required: false, example: 10.99 }) + @IsOptional() @IsNumber() - recurso: number; + recurso?: number; @ApiProperty() - @IsNotEmpty() + @IsOptional() @IsNumber() - anexo: number; + anexo?: number; - @ApiProperty({ type: Number, example: 1.99 }) - @IsNumber() - valor_a_pagar: number; + // TODO: Caso não precisemos disso, vamos deletar + // @ApiProperty({ type: Number, required: false, example: 1.99 }) + // @IsNumber() + // valor_a_pagar: number; @ApiProperty({ description: 'Não sabemos se será número, ser terá zeros a esquerda ou terá letras' }) @IsNotEmpty() diff --git a/src/lancamento/interfaces/lancamento-seed-data.interface.ts b/src/lancamento/interfaces/lancamento-seed-data.interface.ts index 55a9dc29..c08a1c21 100644 --- a/src/lancamento/interfaces/lancamento-seed-data.interface.ts +++ b/src/lancamento/interfaces/lancamento-seed-data.interface.ts @@ -4,7 +4,6 @@ import { DeepPartial } from 'typeorm'; export interface LancamentoSeedData { id?: number; - descricao: string; valor: number; data_lancamento: Date; data_ordem: Date; diff --git a/src/lancamento/lancamento.entity.ts b/src/lancamento/lancamento.entity.ts index 78f03d1d..28aec4c0 100644 --- a/src/lancamento/lancamento.entity.ts +++ b/src/lancamento/lancamento.entity.ts @@ -1,10 +1,11 @@ +import { ApiProperty } from '@nestjs/swagger'; import { Exclude, Expose, Transform } from 'class-transformer'; import { ClienteFavorecido } from 'src/cnab/entity/cliente-favorecido.entity'; import { Transacao } from 'src/cnab/entity/pagamento/transacao.entity'; import { User } from 'src/users/entities/user.entity'; import { EntityHelper } from 'src/utils/entity-helper'; import { asStringOrNumber } from 'src/utils/pipe-utils'; -import { AfterLoad, BeforeInsert, BeforeUpdate, Column, CreateDateColumn, DeepPartial, Entity, JoinColumn, ManyToOne, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'; +import { AfterLoad, Column, CreateDateColumn, DeepPartial, Entity, JoinColumn, ManyToOne, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'; import { LancamentoInputDto } from './dtos/lancamento-input.dto'; @Entity('lancamento') @@ -18,16 +19,13 @@ export class Lancamento extends EntityHelper { public static fromInputDto(dto: LancamentoInputDto) { return new Lancamento({ - descricao: dto.descricao, valor: dto.valor, - data_ordem: new Date(dto.data_ordem), - data_pgto: new Date(dto.data_pgto), - data_lancamento: new Date(dto.data_lancamento), + data_ordem: dto.data_ordem, + data_lancamento: dto.data_lancamento, algoritmo: dto.algoritmo, glosa: dto.glosa, recurso: dto.recurso, anexo: dto.anexo, - valor_a_pagar: dto.valor_a_pagar, numero_processo: dto.numero_processo, clienteFavorecido: { id: dto.id_cliente_favorecido }, autor: dto.author, @@ -35,16 +33,20 @@ export class Lancamento extends EntityHelper { } updateFromInputDto(dto: LancamentoInputDto) { - this.descricao = dto.descricao; this.valor = dto.valor; this.data_ordem = dto.data_ordem; - this.data_pgto = dto.data_pgto; this.data_lancamento = dto.data_lancamento; this.algoritmo = dto.algoritmo; - this.glosa = dto.glosa; - this.recurso = dto.recurso; - this.anexo = dto.anexo; - this.valor_a_pagar = dto.valor_a_pagar; + if (dto.glosa !== undefined) { + this.glosa = dto.glosa; + } + if (dto.recurso !== undefined) { + this.recurso = dto.recurso; + } + if (dto.anexo !== undefined) { + this.anexo = dto.anexo; + } + this.valor = dto.valor; this.numero_processo = dto.numero_processo; this.clienteFavorecido = new ClienteFavorecido({ id: dto.id_cliente_favorecido }); this.autor = new User(dto.author); @@ -53,24 +55,22 @@ export class Lancamento extends EntityHelper { @PrimaryGeneratedColumn({ primaryKeyConstraintName: 'PK_Lancamento_id' }) id: number; - @Column({ type: 'varchar' }) - descricao: string; - - /** Valor pago */ + @ApiProperty({ description: 'Valor a pagar' }) @Column({ type: 'numeric' }) valor: number; @Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' }) data_ordem: Date; - @Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' }) - data_pgto: Date; + @Column({ type: 'timestamp', nullable: true }) + data_pgto: Date | null; /** createdAt */ @Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' }) data_lancamento: Date; /** Remessa */ + @ApiProperty({ description: 'Transação do CNAB remessa associado a este Lançamento' }) @ManyToOne(() => Transacao, { nullable: true }) @JoinColumn({ foreignKeyConstraintName: 'FK_Lancamento_transacao_ManyToOne' }) transacao: Transacao; @@ -84,7 +84,7 @@ export class Lancamento extends EntityHelper { autorizado_por: string | null; @ManyToOne(() => User) - @JoinColumn({ name: 'userId', foreignKeyConstraintName: 'FK_Lancamento_user_ManyToOne' }) + @JoinColumn({ foreignKeyConstraintName: 'FK_Lancamento_autor_ManyToOne' }) @Expose({ name: 'userId' }) @Transform(({ value }) => (value as User).id) autor: User; @@ -94,28 +94,30 @@ export class Lancamento extends EntityHelper { @Expose({ name: 'id_cliente_favorecido' }) @Transform(({ value }) => (value as ClienteFavorecido).id) clienteFavorecido: ClienteFavorecido; - + @Column({ type: 'numeric', nullable: false }) algoritmo: number; - - @Column({ type: 'numeric', nullable: false }) + + @Column({ type: 'numeric', nullable: false, default: 0 }) glosa: number; - @Column({ type: 'numeric', nullable: false }) + @Column({ type: 'numeric', nullable: false, default: 0 }) recurso: number; - @Column({ type: 'numeric', nullable: false }) + @Column({ type: 'numeric', nullable: false, default: 0 }) anexo: number; - @Column({ type: 'numeric', nullable: false }) - valor_a_pagar: number; + // @Column({ type: 'numeric', nullable: false }) + // valor_a_pagar: number; @Column({ type: 'varchar', nullable: false }) numero_processo: string; + @Exclude() @CreateDateColumn() createdAt: Date; + @Exclude() @UpdateDateColumn() updatedAt: Date; @@ -125,7 +127,7 @@ export class Lancamento extends EntityHelper { this.algoritmo = asStringOrNumber(this.algoritmo); this.recurso = asStringOrNumber(this.recurso); this.anexo = asStringOrNumber(this.anexo); - this.valor_a_pagar = asStringOrNumber(this.valor_a_pagar); + this.valor = asStringOrNumber(this.valor); } getIsAutorizado() { diff --git a/src/lancamento/lancamento.service.ts b/src/lancamento/lancamento.service.ts index ad293dba..bd669d08 100644 --- a/src/lancamento/lancamento.service.ts +++ b/src/lancamento/lancamento.service.ts @@ -10,6 +10,7 @@ import { AutorizaLancamentoDto } from './dtos/AutorizaLancamentoDto'; import { LancamentoInputDto } from './dtos/lancamento-input.dto'; import { Lancamento } from './lancamento.entity'; import { LancamentoRepository } from './lancamento.repository'; +import { ClienteFavorecido } from 'src/cnab/entity/cliente-favorecido.entity'; @Injectable() export class LancamentoService { @@ -98,8 +99,9 @@ export class LancamentoService { async create(dto: LancamentoInputDto): Promise { const lancamento = await this.validateLancamentoDto(dto); - const created = this.lancamentoRepository.create(lancamento); - return await this.lancamentoRepository.save(created); + const created = await this.lancamentoRepository.save(this.lancamentoRepository.create(lancamento)); + created.setReadValues(); + return created; } async validateLancamentoDto(dto: LancamentoInputDto): Promise { @@ -108,7 +110,7 @@ export class LancamentoService { throw CommonHttpException.message(`id_cliente_favorecido: Favorecido não encontrado no sistema`); } const lancamento = Lancamento.fromInputDto(dto); - lancamento.clienteFavorecido = favorecido; + lancamento.clienteFavorecido = new ClienteFavorecido({ id: favorecido.id }); return lancamento; } From c6117d32fdcb163cea25f4f423a16a8dcd1d23fc Mon Sep 17 00:00:00 2001 From: Raphael Rivas Date: Thu, 29 Aug 2024 17:57:24 -0300 Subject: [PATCH 05/29] feat: clienteFavorecido id and name --- src/lancamento/lancamento.entity.ts | 12 ++++++++---- src/lancamento/lancamento.repository.ts | 8 ++++++-- src/lancamento/lancamento.service.ts | 10 +++------- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/lancamento/lancamento.entity.ts b/src/lancamento/lancamento.entity.ts index 28aec4c0..f9427620 100644 --- a/src/lancamento/lancamento.entity.ts +++ b/src/lancamento/lancamento.entity.ts @@ -52,6 +52,7 @@ export class Lancamento extends EntityHelper { this.autor = new User(dto.author); } + @Expose() @PrimaryGeneratedColumn({ primaryKeyConstraintName: 'PK_Lancamento_id' }) id: number; @@ -83,7 +84,7 @@ export class Lancamento extends EntityHelper { @Column({ name: 'autorizado_por', type: 'varchar', nullable: true }) autorizado_por: string | null; - @ManyToOne(() => User) + @ManyToOne(() => User, { eager: true }) @JoinColumn({ foreignKeyConstraintName: 'FK_Lancamento_autor_ManyToOne' }) @Expose({ name: 'userId' }) @Transform(({ value }) => (value as User).id) @@ -92,12 +93,15 @@ export class Lancamento extends EntityHelper { @ManyToOne(() => ClienteFavorecido, { eager: true }) @JoinColumn({ name: 'id_cliente_favorecido', foreignKeyConstraintName: 'FK_Lancamento_idClienteFavorecido_ManyToOne' }) @Expose({ name: 'id_cliente_favorecido' }) - @Transform(({ value }) => (value as ClienteFavorecido).id) + @Transform(({ value }) => ({ + id: (value as ClienteFavorecido).id, + nome: (value as ClienteFavorecido).nome, + })) clienteFavorecido: ClienteFavorecido; - + @Column({ type: 'numeric', nullable: false }) algoritmo: number; - + @Column({ type: 'numeric', nullable: false, default: 0 }) glosa: number; diff --git a/src/lancamento/lancamento.repository.ts b/src/lancamento/lancamento.repository.ts index 939e370f..0649a744 100644 --- a/src/lancamento/lancamento.repository.ts +++ b/src/lancamento/lancamento.repository.ts @@ -1,6 +1,6 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable, NotFoundException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { DeepPartial, DeleteResult, FindManyOptions, FindOneOptions, Repository, SaveOptions } from 'typeorm'; +import { DeepPartial, DeleteResult, FindManyOptions, FindOneOptions, FindOptionsWhere, Repository, SaveOptions } from 'typeorm'; import { Lancamento } from './lancamento.entity'; @Injectable() @@ -22,6 +22,10 @@ export class LancamentoRepository { return this.findOne(options); } + async getOne(where: FindOptionsWhere): Promise { + return await this.lancamentoRepository.findOneByOrFail(where); + } + findMany(options?: FindManyOptions | undefined): Promise { return this.lancamentoRepository.find(options); } diff --git a/src/lancamento/lancamento.service.ts b/src/lancamento/lancamento.service.ts index bd669d08..14b9a82f 100644 --- a/src/lancamento/lancamento.service.ts +++ b/src/lancamento/lancamento.service.ts @@ -100,8 +100,8 @@ export class LancamentoService { async create(dto: LancamentoInputDto): Promise { const lancamento = await this.validateLancamentoDto(dto); const created = await this.lancamentoRepository.save(this.lancamentoRepository.create(lancamento)); - created.setReadValues(); - return created; + const getCreated = await this.lancamentoRepository.getOne({ clienteFavorecido: { id: created.clienteFavorecido.id } }); + return getCreated; } async validateLancamentoDto(dto: LancamentoInputDto): Promise { @@ -140,13 +140,9 @@ export class LancamentoService { if (!lancamento) { throw new NotFoundException(`Lançamento com ID ${id} não encontrado.`); } - - const update = Lancamento.fromInputDto(updateDto); - update.updateFromInputDto(updateDto); - + lancamento.updateFromInputDto(updateDto); const updated = await this.lancamentoRepository.save(lancamento); this.logger.log(`Lancamento #${updated.id} atualizado por ${updated.clienteFavorecido.nome}.`); - return lancamento; } From 713d82ff9b15250fd57fd203557b2279677897a1 Mon Sep 17 00:00:00 2001 From: Raphael Rivas Date: Fri, 30 Aug 2024 11:19:17 -0300 Subject: [PATCH 06/29] refactor: remove imports --- src/cnab/cnab.controller.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/cnab/cnab.controller.ts b/src/cnab/cnab.controller.ts index 85665b0d..960e8732 100644 --- a/src/cnab/cnab.controller.ts +++ b/src/cnab/cnab.controller.ts @@ -7,16 +7,14 @@ import { RolesGuard } from 'src/roles/roles.guard'; import { ApiDescription } from 'src/utils/api-param/description-api-param'; import { CustomLogger } from 'src/utils/custom-logger'; import { ParseDatePipe } from 'src/utils/pipes/parse-date.pipe'; -import { ParseListPipe } from 'src/utils/pipes/parse-list.pipe'; import { ParseNumberPipe } from 'src/utils/pipes/parse-number.pipe'; -import { CnabService } from './cnab.service'; +import { ParseEnumPipe } from 'src/utils/pipes/validate-enum.pipe'; import { ClienteFavorecido } from './entity/cliente-favorecido.entity'; +import { GetClienteFavorecidoConsorcioEnum } from './enums/get-cliente-favorecido-consorcio.enum'; import { ArquivoPublicacaoService } from './service/arquivo-publicacao.service'; import { ClienteFavorecidoService } from './service/cliente-favorecido.service'; import { ExtratoDto } from './service/dto/extrato.dto'; import { ExtratoHeaderArquivoService } from './service/extrato/extrato-header-arquivo.service'; -import { GetClienteFavorecidoConsorcioEnum } from './enums/get-cliente-favorecido-consorcio.enum'; -import { ParseEnumPipe } from 'src/utils/pipes/validate-enum.pipe'; @ApiTags('Cnab') @Controller({ From 745e477005eec1bfac17ccdfdf57762fd4165d00 Mon Sep 17 00:00:00 2001 From: Raphael Rivas Date: Fri, 30 Aug 2024 14:30:19 -0300 Subject: [PATCH 07/29] feat: lancamento endponits create, update, authorize, getById, getByStatus --- src/cnab/cnab.service.ts | 2 +- src/cnab/dto/pagamento/transacao.dto.ts | 2 +- .../pagamento/transacao-agrupado.entity.ts | 2 +- src/cnab/entity/pagamento/transacao.entity.ts | 2 +- .../service/cliente-favorecido.service.ts | 2 +- .../pagamento/item-transacao.service.ts | 2 +- .../pagamento/transacao-agrupado.service.ts | 2 +- .../service/pagamento/transacao.service.ts | 2 +- .../1725038149559-LancamentoAutorizacao.ts | 28 +++++++ .../1714005101337-UpdateLancamento.ts | 0 .../1714151527859-UpdateLancamento2v.ts | 0 .../1714246457271-UpdateLancamento3v.ts | 0 .../1714409896493-UpdatePublicacao.ts | 0 .../1714662784331-UpdatePublicacao2v.ts | 0 .../1714686484049-CreateAgrupado.ts | 0 .../1714688980016-CreateAgrupado2v.ts | 0 .../1714764255864-CreateOcorrencia.ts | 0 .../1715087636547-UpdateRetorno.ts | 0 .../1715114151403-OcorrenciaCode.ts | 0 .../1715114882554-ExcludeDetalheARef.ts | 0 .../1715188684545-UpdateItemTransacao.ts | 0 .../1715266251395-FixPublicacao.ts | 0 .../1715693228210-CreatedAtLancamento.ts | 0 .../1715711970831-PublicacaoFKOcorrencia.ts | 0 ...1715882993008-RemoveHeaderArquivoStatus.ts | 0 .../1715887343778-DetalheAFKAgrupado.ts | 0 .../1715954451933-TransacaoNoUniqueIdOrdem.ts | 0 .../1716386948012-CreateTransacaoView.ts | 0 .../1716406551459-TransacaoViewCpfCnpj.ts | 0 .../1716472421112-TransacaoViewNoUnique.ts | 0 .../1716576955307-OcorrenciasDetalheAOnly.ts | 0 .../1717014260013-RemoveCnabStatus.ts | 0 ...7442570102-TransacaoViewCreateUpdatedAt.ts | 0 .../1718128591022-TransacaoViewValorPago.ts | 0 ...1719431856725-HeaderLoteFormaLancamento.ts | 0 .../1719949229037-conference.ts | 0 .../1720214573088-transacaoView.ts | 0 .../1721056266092-PagamentoPendente.ts | 0 .../1721170246216-RefactorMigration.ts | 0 ...icionColunaIdTransacaoViewItemTransacao.ts | 0 ...unaItemTransacaoAgrupadoIdTransacaoView.ts | 0 .../1722348299991-Lancamento.ts | 0 .../1724436146343-DecouplePublicacao.ts | 0 .../lancamento/lancamento-seed.module.ts | 2 +- .../lancamento/lancamento-seed.service.ts | 2 +- src/lancamento/dtos/lancamento-input.dto.ts | 2 +- .../entities/lancamento-autorizacao.entity.ts | 53 +++++++++++++ .../{ => entities}/lancamento.entity.ts | 78 ++++++++++++++----- src/lancamento/lancamento.controller.ts | 12 +-- src/lancamento/lancamento.module.ts | 2 +- src/lancamento/lancamento.repository.ts | 16 ++-- src/lancamento/lancamento.service.ts | 60 ++++++-------- src/users/entities/user.entity.ts | 23 ++---- .../custom-validation-options.ts | 63 +++++++-------- .../filters/all-exceptions.filter.ts | 38 +++------ 55 files changed, 237 insertions(+), 158 deletions(-) create mode 100644 src/database/migrations/1725038149559-LancamentoAutorizacao.ts rename src/database/migrations/{ => 2-cnab-jae}/1714005101337-UpdateLancamento.ts (100%) rename src/database/migrations/{ => 2-cnab-jae}/1714151527859-UpdateLancamento2v.ts (100%) rename src/database/migrations/{ => 2-cnab-jae}/1714246457271-UpdateLancamento3v.ts (100%) rename src/database/migrations/{ => 3-cnab-refactor}/1714409896493-UpdatePublicacao.ts (100%) rename src/database/migrations/{ => 3-cnab-refactor}/1714662784331-UpdatePublicacao2v.ts (100%) rename src/database/migrations/{ => 3-cnab-refactor}/1714686484049-CreateAgrupado.ts (100%) rename src/database/migrations/{ => 3-cnab-refactor}/1714688980016-CreateAgrupado2v.ts (100%) rename src/database/migrations/{ => 3-cnab-refactor}/1714764255864-CreateOcorrencia.ts (100%) rename src/database/migrations/{ => 3-cnab-refactor}/1715087636547-UpdateRetorno.ts (100%) rename src/database/migrations/{ => 3-cnab-refactor}/1715114151403-OcorrenciaCode.ts (100%) rename src/database/migrations/{ => 3-cnab-refactor}/1715114882554-ExcludeDetalheARef.ts (100%) rename src/database/migrations/{ => 3-cnab-refactor}/1715188684545-UpdateItemTransacao.ts (100%) rename src/database/migrations/{ => 3-cnab-refactor}/1715266251395-FixPublicacao.ts (100%) rename src/database/migrations/{ => 3-cnab-refactor}/1715693228210-CreatedAtLancamento.ts (100%) rename src/database/migrations/{ => 3-cnab-refactor}/1715711970831-PublicacaoFKOcorrencia.ts (100%) rename src/database/migrations/{ => 3-cnab-refactor}/1715882993008-RemoveHeaderArquivoStatus.ts (100%) rename src/database/migrations/{ => 3-cnab-refactor}/1715887343778-DetalheAFKAgrupado.ts (100%) rename src/database/migrations/{ => 3-cnab-refactor}/1715954451933-TransacaoNoUniqueIdOrdem.ts (100%) rename src/database/migrations/{ => 3-cnab-refactor}/1716386948012-CreateTransacaoView.ts (100%) rename src/database/migrations/{ => 3-cnab-refactor}/1716406551459-TransacaoViewCpfCnpj.ts (100%) rename src/database/migrations/{ => 3-cnab-refactor}/1716472421112-TransacaoViewNoUnique.ts (100%) rename src/database/migrations/{ => 3-cnab-refactor}/1716576955307-OcorrenciasDetalheAOnly.ts (100%) rename src/database/migrations/{ => 3-cnab-refactor}/1717014260013-RemoveCnabStatus.ts (100%) rename src/database/migrations/{ => 3-cnab-refactor}/1717442570102-TransacaoViewCreateUpdatedAt.ts (100%) rename src/database/migrations/{ => 3-cnab-refactor}/1718128591022-TransacaoViewValorPago.ts (100%) rename src/database/migrations/{ => 3-cnab-refactor}/1719431856725-HeaderLoteFormaLancamento.ts (100%) rename src/database/migrations/{ => 3-cnab-refactor}/1719949229037-conference.ts (100%) rename src/database/migrations/{ => 3-cnab-refactor}/1720214573088-transacaoView.ts (100%) rename src/database/migrations/{ => 3-cnab-refactor}/1721056266092-PagamentoPendente.ts (100%) rename src/database/migrations/{ => 3-cnab-refactor}/1721170246216-RefactorMigration.ts (100%) rename src/database/migrations/{ => 3-cnab-refactor}/1721848566340-AdicionColunaIdTransacaoViewItemTransacao.ts (100%) rename src/database/migrations/{ => 3-cnab-refactor}/1721853197274-AdicionColunaItemTransacaoAgrupadoIdTransacaoView.ts (100%) rename src/database/migrations/{ => 3-cnab-refactor}/1722348299991-Lancamento.ts (100%) rename src/database/migrations/{ => 3-cnab-refactor}/1724436146343-DecouplePublicacao.ts (100%) create mode 100644 src/lancamento/entities/lancamento-autorizacao.entity.ts rename src/lancamento/{ => entities}/lancamento.entity.ts (66%) diff --git a/src/cnab/cnab.service.ts b/src/cnab/cnab.service.ts index 787f00cc..a18d98aa 100644 --- a/src/cnab/cnab.service.ts +++ b/src/cnab/cnab.service.ts @@ -3,7 +3,7 @@ import { endOfDay, isFriday, nextFriday, nextThursday, startOfDay, subDays } fro import { BigqueryOrdemPagamentoDTO } from 'src/bigquery/dtos/bigquery-ordem-pagamento.dto'; import { BigqueryOrdemPagamentoService } from 'src/bigquery/services/bigquery-ordem-pagamento.service'; import { BigqueryTransacaoService } from 'src/bigquery/services/bigquery-transacao.service'; -import { Lancamento } from 'src/lancamento/lancamento.entity'; +import { Lancamento } from 'src/lancamento/entities/lancamento.entity'; import { LancamentoService } from 'src/lancamento/lancamento.service'; import { SftpBackupFolder } from 'src/sftp/enums/sftp-backup-folder.enum'; import { SftpService } from 'src/sftp/sftp.service'; diff --git a/src/cnab/dto/pagamento/transacao.dto.ts b/src/cnab/dto/pagamento/transacao.dto.ts index d3a82c65..62ce12f7 100644 --- a/src/cnab/dto/pagamento/transacao.dto.ts +++ b/src/cnab/dto/pagamento/transacao.dto.ts @@ -1,6 +1,6 @@ import { IsNotEmpty, ValidateIf } from 'class-validator'; import { Ocorrencia } from 'src/cnab/entity/pagamento/ocorrencia.entity'; -import { Lancamento } from 'src/lancamento/lancamento.entity'; +import { Lancamento } from 'src/lancamento/entities/lancamento.entity'; import { DeepPartial } from 'typeorm'; import { Pagador } from '../../entity/pagamento/pagador.entity'; diff --git a/src/cnab/entity/pagamento/transacao-agrupado.entity.ts b/src/cnab/entity/pagamento/transacao-agrupado.entity.ts index 96101349..f1508d1d 100644 --- a/src/cnab/entity/pagamento/transacao-agrupado.entity.ts +++ b/src/cnab/entity/pagamento/transacao-agrupado.entity.ts @@ -1,4 +1,4 @@ -import { Lancamento } from 'src/lancamento/lancamento.entity'; +import { Lancamento } from 'src/lancamento/entities/lancamento.entity'; import { EntityHelper } from 'src/utils/entity-helper'; import { Column, CreateDateColumn, DeepPartial, Entity, JoinColumn, ManyToOne, OneToMany, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'; import { ItemTransacaoAgrupado } from './item-transacao-agrupado.entity'; diff --git a/src/cnab/entity/pagamento/transacao.entity.ts b/src/cnab/entity/pagamento/transacao.entity.ts index 625bce61..82c97a5e 100644 --- a/src/cnab/entity/pagamento/transacao.entity.ts +++ b/src/cnab/entity/pagamento/transacao.entity.ts @@ -1,4 +1,4 @@ -import { Lancamento } from 'src/lancamento/lancamento.entity'; +import { Lancamento } from 'src/lancamento/entities/lancamento.entity'; import { EntityHelper } from 'src/utils/entity-helper'; import { Column, CreateDateColumn, DeepPartial, Entity, JoinColumn, ManyToOne, OneToMany, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'; import { ItemTransacao } from './item-transacao.entity'; diff --git a/src/cnab/service/cliente-favorecido.service.ts b/src/cnab/service/cliente-favorecido.service.ts index 4632be0c..d9bcb95e 100644 --- a/src/cnab/service/cliente-favorecido.service.ts +++ b/src/cnab/service/cliente-favorecido.service.ts @@ -1,6 +1,6 @@ import { HttpStatus, Injectable, Logger } from '@nestjs/common'; import { BigqueryOrdemPagamentoDTO } from 'src/bigquery/dtos/bigquery-ordem-pagamento.dto'; -import { Lancamento } from 'src/lancamento/lancamento.entity'; +import { Lancamento } from 'src/lancamento/entities/lancamento.entity'; import { TipoFavorecidoEnum } from 'src/tipo-favorecido/tipo-favorecido.enum'; import { User } from 'src/users/entities/user.entity'; import { CommonHttpException } from 'src/utils/http-exception/common-http-exception'; diff --git a/src/cnab/service/pagamento/item-transacao.service.ts b/src/cnab/service/pagamento/item-transacao.service.ts index 8126a28b..241b1b29 100644 --- a/src/cnab/service/pagamento/item-transacao.service.ts +++ b/src/cnab/service/pagamento/item-transacao.service.ts @@ -4,7 +4,7 @@ import { ClienteFavorecido } from 'src/cnab/entity/cliente-favorecido.entity'; import { ItemTransacao } from 'src/cnab/entity/pagamento/item-transacao.entity'; import { Transacao } from 'src/cnab/entity/pagamento/transacao.entity'; import { ItemTransacaoRepository } from 'src/cnab/repository/pagamento/item-transacao.repository'; -import { Lancamento } from 'src/lancamento/lancamento.entity'; +import { Lancamento } from 'src/lancamento/entities/lancamento.entity'; import { CustomLogger } from 'src/utils/custom-logger'; import { logDebug } from 'src/utils/log-utils'; import { asObject } from 'src/utils/pipe-utils'; diff --git a/src/cnab/service/pagamento/transacao-agrupado.service.ts b/src/cnab/service/pagamento/transacao-agrupado.service.ts index a6c602ac..426d6a97 100644 --- a/src/cnab/service/pagamento/transacao-agrupado.service.ts +++ b/src/cnab/service/pagamento/transacao-agrupado.service.ts @@ -6,7 +6,7 @@ import { Pagador } from 'src/cnab/entity/pagamento/pagador.entity'; import { TransacaoAgrupado } from 'src/cnab/entity/pagamento/transacao-agrupado.entity'; import { PagadorContaEnum } from 'src/cnab/enums/pagamento/pagador.enum'; import { TransacaoAgrupadoRepository } from 'src/cnab/repository/pagamento/transacao-agrupado.repository'; -import { Lancamento } from 'src/lancamento/lancamento.entity'; +import { Lancamento } from 'src/lancamento/entities/lancamento.entity'; import { asNumber } from 'src/utils/pipe-utils'; import { EntityCondition } from 'src/utils/types/entity-condition.type'; import { DeepPartial, UpdateResult } from 'typeorm'; diff --git a/src/cnab/service/pagamento/transacao.service.ts b/src/cnab/service/pagamento/transacao.service.ts index 924af91f..4384fcd2 100644 --- a/src/cnab/service/pagamento/transacao.service.ts +++ b/src/cnab/service/pagamento/transacao.service.ts @@ -6,7 +6,7 @@ import { TransacaoRepository } from '../../repository/pagamento/transacao.reposi import { isFriday, nextFriday } from 'date-fns'; import { Pagador } from 'src/cnab/entity/pagamento/pagador.entity'; import { PagadorContaEnum } from 'src/cnab/enums/pagamento/pagador.enum'; -import { Lancamento } from 'src/lancamento/lancamento.entity'; +import { Lancamento } from 'src/lancamento/entities/lancamento.entity'; import { asNumber, asString } from 'src/utils/pipe-utils'; import { EntityCondition } from 'src/utils/types/entity-condition.type'; import { SaveIfNotExists } from 'src/utils/types/save-if-not-exists.type'; diff --git a/src/database/migrations/1725038149559-LancamentoAutorizacao.ts b/src/database/migrations/1725038149559-LancamentoAutorizacao.ts new file mode 100644 index 00000000..a8a2256a --- /dev/null +++ b/src/database/migrations/1725038149559-LancamentoAutorizacao.ts @@ -0,0 +1,28 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class LancamentoAutorizacao1725038149559 implements MigrationInterface { + name = 'LancamentoAutorizacao1725038149559' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "lancamento" RENAME COLUMN "autorizado_por" TO "is_autorizado"`); + await queryRunner.query(`CREATE TABLE "lancamento_autorizacao" ("id" SERIAL NOT NULL, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "lancamentoId" integer, "userId" integer, CONSTRAINT "PK_LancamentoAutorizacao_id" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE INDEX "IDX_LancamentoAutorizacao_lancamento" ON "lancamento_autorizacao" ("lancamentoId") `); + await queryRunner.query(`CREATE INDEX "IDX_LancamentoAutorizacao_user" ON "lancamento_autorizacao" ("userId") `); + await queryRunner.query(`ALTER TABLE "lancamento" DROP COLUMN "is_autorizado"`); + await queryRunner.query(`ALTER TABLE "lancamento" ADD "is_autorizado" boolean NOT NULL DEFAULT false`); + await queryRunner.query(`ALTER TABLE "lancamento_autorizacao" ADD CONSTRAINT "FK_LancamentoAutorizacao_lancamento" FOREIGN KEY ("lancamentoId") REFERENCES "lancamento"("id") ON DELETE CASCADE ON UPDATE CASCADE`); + await queryRunner.query(`ALTER TABLE "lancamento_autorizacao" ADD CONSTRAINT "FK_LancamentoAutorizacao_user" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE CASCADE`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "lancamento_autorizacao" DROP CONSTRAINT "FK_LancamentoAutorizacao_user"`); + await queryRunner.query(`ALTER TABLE "lancamento_autorizacao" DROP CONSTRAINT "FK_LancamentoAutorizacao_lancamento"`); + await queryRunner.query(`ALTER TABLE "lancamento" DROP COLUMN "is_autorizado"`); + await queryRunner.query(`ALTER TABLE "lancamento" ADD "is_autorizado" character varying`); + await queryRunner.query(`DROP INDEX "public"."IDX_LancamentoAutorizacao_user"`); + await queryRunner.query(`DROP INDEX "public"."IDX_LancamentoAutorizacao_lancamento"`); + await queryRunner.query(`DROP TABLE "lancamento_autorizacao"`); + await queryRunner.query(`ALTER TABLE "lancamento" RENAME COLUMN "is_autorizado" TO "autorizado_por"`); + } + +} diff --git a/src/database/migrations/1714005101337-UpdateLancamento.ts b/src/database/migrations/2-cnab-jae/1714005101337-UpdateLancamento.ts similarity index 100% rename from src/database/migrations/1714005101337-UpdateLancamento.ts rename to src/database/migrations/2-cnab-jae/1714005101337-UpdateLancamento.ts diff --git a/src/database/migrations/1714151527859-UpdateLancamento2v.ts b/src/database/migrations/2-cnab-jae/1714151527859-UpdateLancamento2v.ts similarity index 100% rename from src/database/migrations/1714151527859-UpdateLancamento2v.ts rename to src/database/migrations/2-cnab-jae/1714151527859-UpdateLancamento2v.ts diff --git a/src/database/migrations/1714246457271-UpdateLancamento3v.ts b/src/database/migrations/2-cnab-jae/1714246457271-UpdateLancamento3v.ts similarity index 100% rename from src/database/migrations/1714246457271-UpdateLancamento3v.ts rename to src/database/migrations/2-cnab-jae/1714246457271-UpdateLancamento3v.ts diff --git a/src/database/migrations/1714409896493-UpdatePublicacao.ts b/src/database/migrations/3-cnab-refactor/1714409896493-UpdatePublicacao.ts similarity index 100% rename from src/database/migrations/1714409896493-UpdatePublicacao.ts rename to src/database/migrations/3-cnab-refactor/1714409896493-UpdatePublicacao.ts diff --git a/src/database/migrations/1714662784331-UpdatePublicacao2v.ts b/src/database/migrations/3-cnab-refactor/1714662784331-UpdatePublicacao2v.ts similarity index 100% rename from src/database/migrations/1714662784331-UpdatePublicacao2v.ts rename to src/database/migrations/3-cnab-refactor/1714662784331-UpdatePublicacao2v.ts diff --git a/src/database/migrations/1714686484049-CreateAgrupado.ts b/src/database/migrations/3-cnab-refactor/1714686484049-CreateAgrupado.ts similarity index 100% rename from src/database/migrations/1714686484049-CreateAgrupado.ts rename to src/database/migrations/3-cnab-refactor/1714686484049-CreateAgrupado.ts diff --git a/src/database/migrations/1714688980016-CreateAgrupado2v.ts b/src/database/migrations/3-cnab-refactor/1714688980016-CreateAgrupado2v.ts similarity index 100% rename from src/database/migrations/1714688980016-CreateAgrupado2v.ts rename to src/database/migrations/3-cnab-refactor/1714688980016-CreateAgrupado2v.ts diff --git a/src/database/migrations/1714764255864-CreateOcorrencia.ts b/src/database/migrations/3-cnab-refactor/1714764255864-CreateOcorrencia.ts similarity index 100% rename from src/database/migrations/1714764255864-CreateOcorrencia.ts rename to src/database/migrations/3-cnab-refactor/1714764255864-CreateOcorrencia.ts diff --git a/src/database/migrations/1715087636547-UpdateRetorno.ts b/src/database/migrations/3-cnab-refactor/1715087636547-UpdateRetorno.ts similarity index 100% rename from src/database/migrations/1715087636547-UpdateRetorno.ts rename to src/database/migrations/3-cnab-refactor/1715087636547-UpdateRetorno.ts diff --git a/src/database/migrations/1715114151403-OcorrenciaCode.ts b/src/database/migrations/3-cnab-refactor/1715114151403-OcorrenciaCode.ts similarity index 100% rename from src/database/migrations/1715114151403-OcorrenciaCode.ts rename to src/database/migrations/3-cnab-refactor/1715114151403-OcorrenciaCode.ts diff --git a/src/database/migrations/1715114882554-ExcludeDetalheARef.ts b/src/database/migrations/3-cnab-refactor/1715114882554-ExcludeDetalheARef.ts similarity index 100% rename from src/database/migrations/1715114882554-ExcludeDetalheARef.ts rename to src/database/migrations/3-cnab-refactor/1715114882554-ExcludeDetalheARef.ts diff --git a/src/database/migrations/1715188684545-UpdateItemTransacao.ts b/src/database/migrations/3-cnab-refactor/1715188684545-UpdateItemTransacao.ts similarity index 100% rename from src/database/migrations/1715188684545-UpdateItemTransacao.ts rename to src/database/migrations/3-cnab-refactor/1715188684545-UpdateItemTransacao.ts diff --git a/src/database/migrations/1715266251395-FixPublicacao.ts b/src/database/migrations/3-cnab-refactor/1715266251395-FixPublicacao.ts similarity index 100% rename from src/database/migrations/1715266251395-FixPublicacao.ts rename to src/database/migrations/3-cnab-refactor/1715266251395-FixPublicacao.ts diff --git a/src/database/migrations/1715693228210-CreatedAtLancamento.ts b/src/database/migrations/3-cnab-refactor/1715693228210-CreatedAtLancamento.ts similarity index 100% rename from src/database/migrations/1715693228210-CreatedAtLancamento.ts rename to src/database/migrations/3-cnab-refactor/1715693228210-CreatedAtLancamento.ts diff --git a/src/database/migrations/1715711970831-PublicacaoFKOcorrencia.ts b/src/database/migrations/3-cnab-refactor/1715711970831-PublicacaoFKOcorrencia.ts similarity index 100% rename from src/database/migrations/1715711970831-PublicacaoFKOcorrencia.ts rename to src/database/migrations/3-cnab-refactor/1715711970831-PublicacaoFKOcorrencia.ts diff --git a/src/database/migrations/1715882993008-RemoveHeaderArquivoStatus.ts b/src/database/migrations/3-cnab-refactor/1715882993008-RemoveHeaderArquivoStatus.ts similarity index 100% rename from src/database/migrations/1715882993008-RemoveHeaderArquivoStatus.ts rename to src/database/migrations/3-cnab-refactor/1715882993008-RemoveHeaderArquivoStatus.ts diff --git a/src/database/migrations/1715887343778-DetalheAFKAgrupado.ts b/src/database/migrations/3-cnab-refactor/1715887343778-DetalheAFKAgrupado.ts similarity index 100% rename from src/database/migrations/1715887343778-DetalheAFKAgrupado.ts rename to src/database/migrations/3-cnab-refactor/1715887343778-DetalheAFKAgrupado.ts diff --git a/src/database/migrations/1715954451933-TransacaoNoUniqueIdOrdem.ts b/src/database/migrations/3-cnab-refactor/1715954451933-TransacaoNoUniqueIdOrdem.ts similarity index 100% rename from src/database/migrations/1715954451933-TransacaoNoUniqueIdOrdem.ts rename to src/database/migrations/3-cnab-refactor/1715954451933-TransacaoNoUniqueIdOrdem.ts diff --git a/src/database/migrations/1716386948012-CreateTransacaoView.ts b/src/database/migrations/3-cnab-refactor/1716386948012-CreateTransacaoView.ts similarity index 100% rename from src/database/migrations/1716386948012-CreateTransacaoView.ts rename to src/database/migrations/3-cnab-refactor/1716386948012-CreateTransacaoView.ts diff --git a/src/database/migrations/1716406551459-TransacaoViewCpfCnpj.ts b/src/database/migrations/3-cnab-refactor/1716406551459-TransacaoViewCpfCnpj.ts similarity index 100% rename from src/database/migrations/1716406551459-TransacaoViewCpfCnpj.ts rename to src/database/migrations/3-cnab-refactor/1716406551459-TransacaoViewCpfCnpj.ts diff --git a/src/database/migrations/1716472421112-TransacaoViewNoUnique.ts b/src/database/migrations/3-cnab-refactor/1716472421112-TransacaoViewNoUnique.ts similarity index 100% rename from src/database/migrations/1716472421112-TransacaoViewNoUnique.ts rename to src/database/migrations/3-cnab-refactor/1716472421112-TransacaoViewNoUnique.ts diff --git a/src/database/migrations/1716576955307-OcorrenciasDetalheAOnly.ts b/src/database/migrations/3-cnab-refactor/1716576955307-OcorrenciasDetalheAOnly.ts similarity index 100% rename from src/database/migrations/1716576955307-OcorrenciasDetalheAOnly.ts rename to src/database/migrations/3-cnab-refactor/1716576955307-OcorrenciasDetalheAOnly.ts diff --git a/src/database/migrations/1717014260013-RemoveCnabStatus.ts b/src/database/migrations/3-cnab-refactor/1717014260013-RemoveCnabStatus.ts similarity index 100% rename from src/database/migrations/1717014260013-RemoveCnabStatus.ts rename to src/database/migrations/3-cnab-refactor/1717014260013-RemoveCnabStatus.ts diff --git a/src/database/migrations/1717442570102-TransacaoViewCreateUpdatedAt.ts b/src/database/migrations/3-cnab-refactor/1717442570102-TransacaoViewCreateUpdatedAt.ts similarity index 100% rename from src/database/migrations/1717442570102-TransacaoViewCreateUpdatedAt.ts rename to src/database/migrations/3-cnab-refactor/1717442570102-TransacaoViewCreateUpdatedAt.ts diff --git a/src/database/migrations/1718128591022-TransacaoViewValorPago.ts b/src/database/migrations/3-cnab-refactor/1718128591022-TransacaoViewValorPago.ts similarity index 100% rename from src/database/migrations/1718128591022-TransacaoViewValorPago.ts rename to src/database/migrations/3-cnab-refactor/1718128591022-TransacaoViewValorPago.ts diff --git a/src/database/migrations/1719431856725-HeaderLoteFormaLancamento.ts b/src/database/migrations/3-cnab-refactor/1719431856725-HeaderLoteFormaLancamento.ts similarity index 100% rename from src/database/migrations/1719431856725-HeaderLoteFormaLancamento.ts rename to src/database/migrations/3-cnab-refactor/1719431856725-HeaderLoteFormaLancamento.ts diff --git a/src/database/migrations/1719949229037-conference.ts b/src/database/migrations/3-cnab-refactor/1719949229037-conference.ts similarity index 100% rename from src/database/migrations/1719949229037-conference.ts rename to src/database/migrations/3-cnab-refactor/1719949229037-conference.ts diff --git a/src/database/migrations/1720214573088-transacaoView.ts b/src/database/migrations/3-cnab-refactor/1720214573088-transacaoView.ts similarity index 100% rename from src/database/migrations/1720214573088-transacaoView.ts rename to src/database/migrations/3-cnab-refactor/1720214573088-transacaoView.ts diff --git a/src/database/migrations/1721056266092-PagamentoPendente.ts b/src/database/migrations/3-cnab-refactor/1721056266092-PagamentoPendente.ts similarity index 100% rename from src/database/migrations/1721056266092-PagamentoPendente.ts rename to src/database/migrations/3-cnab-refactor/1721056266092-PagamentoPendente.ts diff --git a/src/database/migrations/1721170246216-RefactorMigration.ts b/src/database/migrations/3-cnab-refactor/1721170246216-RefactorMigration.ts similarity index 100% rename from src/database/migrations/1721170246216-RefactorMigration.ts rename to src/database/migrations/3-cnab-refactor/1721170246216-RefactorMigration.ts diff --git a/src/database/migrations/1721848566340-AdicionColunaIdTransacaoViewItemTransacao.ts b/src/database/migrations/3-cnab-refactor/1721848566340-AdicionColunaIdTransacaoViewItemTransacao.ts similarity index 100% rename from src/database/migrations/1721848566340-AdicionColunaIdTransacaoViewItemTransacao.ts rename to src/database/migrations/3-cnab-refactor/1721848566340-AdicionColunaIdTransacaoViewItemTransacao.ts diff --git a/src/database/migrations/1721853197274-AdicionColunaItemTransacaoAgrupadoIdTransacaoView.ts b/src/database/migrations/3-cnab-refactor/1721853197274-AdicionColunaItemTransacaoAgrupadoIdTransacaoView.ts similarity index 100% rename from src/database/migrations/1721853197274-AdicionColunaItemTransacaoAgrupadoIdTransacaoView.ts rename to src/database/migrations/3-cnab-refactor/1721853197274-AdicionColunaItemTransacaoAgrupadoIdTransacaoView.ts diff --git a/src/database/migrations/1722348299991-Lancamento.ts b/src/database/migrations/3-cnab-refactor/1722348299991-Lancamento.ts similarity index 100% rename from src/database/migrations/1722348299991-Lancamento.ts rename to src/database/migrations/3-cnab-refactor/1722348299991-Lancamento.ts diff --git a/src/database/migrations/1724436146343-DecouplePublicacao.ts b/src/database/migrations/3-cnab-refactor/1724436146343-DecouplePublicacao.ts similarity index 100% rename from src/database/migrations/1724436146343-DecouplePublicacao.ts rename to src/database/migrations/3-cnab-refactor/1724436146343-DecouplePublicacao.ts diff --git a/src/database/seeds/lancamento/lancamento-seed.module.ts b/src/database/seeds/lancamento/lancamento-seed.module.ts index f6ea489c..16b61c7b 100644 --- a/src/database/seeds/lancamento/lancamento-seed.module.ts +++ b/src/database/seeds/lancamento/lancamento-seed.module.ts @@ -1,7 +1,7 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { ClienteFavorecido } from 'src/cnab/entity/cliente-favorecido.entity'; -import { Lancamento } from 'src/lancamento/lancamento.entity'; +import { Lancamento } from 'src/lancamento/entities/lancamento.entity'; import { User } from 'src/users/entities/user.entity'; import { LancamentoSeedDataService } from './lancamento-seed-data.service'; import { LancamentoSeedService } from './lancamento-seed.service'; diff --git a/src/database/seeds/lancamento/lancamento-seed.service.ts b/src/database/seeds/lancamento/lancamento-seed.service.ts index 8dacec1e..09524fdf 100644 --- a/src/database/seeds/lancamento/lancamento-seed.service.ts +++ b/src/database/seeds/lancamento/lancamento-seed.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { Lancamento } from 'src/lancamento/lancamento.entity'; +import { Lancamento } from 'src/lancamento/entities/lancamento.entity'; import { In, Repository } from 'typeorm'; import { LancamentoSeedDataService } from './lancamento-seed-data.service'; diff --git a/src/lancamento/dtos/lancamento-input.dto.ts b/src/lancamento/dtos/lancamento-input.dto.ts index fb416a15..46a32a18 100644 --- a/src/lancamento/dtos/lancamento-input.dto.ts +++ b/src/lancamento/dtos/lancamento-input.dto.ts @@ -38,7 +38,7 @@ export class LancamentoInputDto { @IsNumber() recurso?: number; - @ApiProperty() + @ApiProperty({ required: false }) @IsOptional() @IsNumber() anexo?: number; diff --git a/src/lancamento/entities/lancamento-autorizacao.entity.ts b/src/lancamento/entities/lancamento-autorizacao.entity.ts new file mode 100644 index 00000000..c537ae87 --- /dev/null +++ b/src/lancamento/entities/lancamento-autorizacao.entity.ts @@ -0,0 +1,53 @@ +import { Expose } from 'class-transformer'; +import { User } from 'src/users/entities/user.entity'; +import { EntityHelper } from 'src/utils/entity-helper'; +import { CreateDateColumn, DeepPartial, Entity, Index, JoinColumn, ManyToOne, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'; +import { Lancamento } from './lancamento.entity'; + +export interface ILancamentoAutorizacao { + id: number; + lancamento: Lancamento; + user: User; + createdAt: Date; + updatedAt: Date; +} + +@Entity('lancamento_autorizacao') +export class LancamentoAutorizacao extends EntityHelper implements ILancamentoAutorizacao { + constructor(autorizado?: DeepPartial) { + super(); + if (autorizado !== undefined) { + Object.assign(this, autorizado); + } + } + + public static tableName = 'lancamento_autorizacao'; + + public static sqlFields: Record = { + id: `"id"`, + lancamento: `"lancamentoId"`, + user: `"userId"`, + createdAt: `"createdAt"`, + updatedAt: `"updatedAt"`, + }; + + @Expose() + @PrimaryGeneratedColumn({ primaryKeyConstraintName: 'PK_LancamentoAutorizacao_id' }) + id: number; + + @ManyToOne(() => Lancamento, (lancamento) => lancamento.autorizacoes, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) + @JoinColumn({ foreignKeyConstraintName: 'FK_LancamentoAutorizacao_lancamento' }) + @Index('IDX_LancamentoAutorizacao_lancamento') + lancamento: Lancamento; + + @ManyToOne(() => User, (user) => user.lancamentos, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) + @JoinColumn({ foreignKeyConstraintName: 'FK_LancamentoAutorizacao_user' }) + @Index('IDX_LancamentoAutorizacao_user') + user: User; + + @CreateDateColumn() + createdAt: Date; + + @UpdateDateColumn() + updatedAt: Date; +} diff --git a/src/lancamento/lancamento.entity.ts b/src/lancamento/entities/lancamento.entity.ts similarity index 66% rename from src/lancamento/lancamento.entity.ts rename to src/lancamento/entities/lancamento.entity.ts index f9427620..61f6df19 100644 --- a/src/lancamento/lancamento.entity.ts +++ b/src/lancamento/entities/lancamento.entity.ts @@ -5,11 +5,31 @@ import { Transacao } from 'src/cnab/entity/pagamento/transacao.entity'; import { User } from 'src/users/entities/user.entity'; import { EntityHelper } from 'src/utils/entity-helper'; import { asStringOrNumber } from 'src/utils/pipe-utils'; -import { AfterLoad, Column, CreateDateColumn, DeepPartial, Entity, JoinColumn, ManyToOne, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'; -import { LancamentoInputDto } from './dtos/lancamento-input.dto'; +import { AfterLoad, Column, CreateDateColumn, DeepPartial, Entity, JoinColumn, JoinTable, ManyToMany, ManyToOne, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'; +import { LancamentoInputDto } from '../dtos/lancamento-input.dto'; +import { LancamentoAutorizacao } from './lancamento-autorizacao.entity'; + +export interface ILancamento { + id: number; + valor: number; + data_ordem: Date; + data_pgto: Date | null; + data_lancamento: Date; + transacao: Transacao; + autorizacoes: User[]; + autor: User; + clienteFavorecido: ClienteFavorecido; + algoritmo: number; + glosa: number; + recurso: number; + anexo: number; + numero_processo: string; + createdAt: Date; + updatedAt: Date; +} @Entity('lancamento') -export class Lancamento extends EntityHelper { +export class Lancamento extends EntityHelper implements ILancamento { constructor(lancamento?: DeepPartial) { super(); if (lancamento !== undefined) { @@ -76,14 +96,23 @@ export class Lancamento extends EntityHelper { @JoinColumn({ foreignKeyConstraintName: 'FK_Lancamento_transacao_ManyToOne' }) transacao: Transacao; - /** - * Coluna - lista de User.id - * @example `1,2,3` - */ - @Exclude() - @Column({ name: 'autorizado_por', type: 'varchar', nullable: true }) - autorizado_por: string | null; - + @ManyToMany(() => User, (user) => user) + @JoinTable({ + name: LancamentoAutorizacao.tableName, + joinColumn: { name: 'lancamentoId', referencedColumnName: 'id' }, + inverseJoinColumn: { name: 'userId', referencedColumnName: 'id' }, + synchronize: false, // We use LancamentoAutorizacao to generate migration + }) + @Expose({ name: 'autorizado_por' }) + @Transform(({ value }) => + (value as User[]).map((u) => ({ + id: u.id, + nome: u.fullName, + })), + ) + autorizacoes: User[]; + + /** O autor mais recente da criação/modificação/autorização do Lançamento */ @ManyToOne(() => User, { eager: true }) @JoinColumn({ foreignKeyConstraintName: 'FK_Lancamento_autor_ManyToOne' }) @Expose({ name: 'userId' }) @@ -92,7 +121,7 @@ export class Lancamento extends EntityHelper { @ManyToOne(() => ClienteFavorecido, { eager: true }) @JoinColumn({ name: 'id_cliente_favorecido', foreignKeyConstraintName: 'FK_Lancamento_idClienteFavorecido_ManyToOne' }) - @Expose({ name: 'id_cliente_favorecido' }) + // @Expose({ name: 'id_cliente_favorecido' }) @Transform(({ value }) => ({ id: (value as ClienteFavorecido).id, nome: (value as ClienteFavorecido).nome, @@ -117,6 +146,9 @@ export class Lancamento extends EntityHelper { @Column({ type: 'varchar', nullable: false }) numero_processo: string; + @Column({ type: Boolean, nullable: false, default: false }) + is_autorizado: boolean; + @Exclude() @CreateDateColumn() createdAt: Date; @@ -132,16 +164,24 @@ export class Lancamento extends EntityHelper { this.recurso = asStringOrNumber(this.recurso); this.anexo = asStringOrNumber(this.anexo); this.valor = asStringOrNumber(this.valor); + this.autorizacoes = this.autorizacoes || []; + } + + getIsAutorizado(): boolean { + return this.is_autorizado; + } + + addAutorizado(userId: number) { + this.autorizacoes.push({ id: userId } as User); + this.is_autorizado = this.autorizacoes.length >= 2; } - getIsAutorizado() { - const list = (this.autorizado_por || '').split(','); - return list.length >= 2; + removeAutorizado(userId: number) { + this.autorizacoes = this.autorizacoes.filter((u) => u.id != userId); + this.is_autorizado = this.autorizacoes.length >= 2; } - pushAutorizado(userId: number) { - const list = (this.autorizado_por || '').split(','); - list.push('' + userId); - this.autorizado_por = list.join(','); + hasAutorizadoPor(userId: number): boolean { + return Boolean(this.autorizacoes.find((u) => u.id == userId)); } } diff --git a/src/lancamento/lancamento.controller.ts b/src/lancamento/lancamento.controller.ts index 14a0dee3..1346aa52 100644 --- a/src/lancamento/lancamento.controller.ts +++ b/src/lancamento/lancamento.controller.ts @@ -9,7 +9,7 @@ import { ParseBooleanPipe } from 'src/utils/pipes/parse-boolean.pipe'; import { ParseNumberPipe } from 'src/utils/pipes/parse-number.pipe'; import { AutorizaLancamentoDto } from './dtos/AutorizaLancamentoDto'; import { LancamentoInputDto } from './dtos/lancamento-input.dto'; -import { Lancamento } from './lancamento.entity'; +import { Lancamento } from './entities/lancamento.entity'; import { LancamentoService } from './lancamento.service'; @ApiTags('Lancamento') @@ -34,14 +34,14 @@ export class LancamentoController { @ApiQuery({ name: 'ano', type: Number, required: false, description: 'Ano do lançamento.' }) @ApiQuery({ name: 'autorizado', type: Boolean, required: false, description: 'Fitra se foi autorizado ou não.' }) @HttpCode(HttpStatus.OK) - async getLancamento( + async get( @Request() request, // @Query('mes') mes: number, @Query('periodo', new ParseNumberPipe({ min: 1, max: 2, optional: true })) periodo: number | undefined, @Query('ano') ano: number, @Query('autorizado') autorizado: boolean | undefined, ): Promise { - return await this.lancamentoService.findByPeriod({ mes, periodo, ano, autorizado }); + return await this.lancamentoService.find({ mes, periodo, ano, autorizado }); } @ApiBearerAuth() @@ -138,7 +138,7 @@ export class LancamentoController { @ApiBearerAuth() @ApiBody({ type: LancamentoInputDto }) @ApiQuery({ name: 'lancamentoId', required: true, description: 'Id do lançamento' }) - async atualizaLancamento( + async putLancamento( @Request() req, @Query('lancamentoId', new ParseNumberPipe({ min: 1 })) lancamentoId: number, @Body() lancamentoDto: LancamentoInputDto, // It was ItfLancamento @@ -157,13 +157,13 @@ export class LancamentoController { ) @Get('/:id') @HttpCode(HttpStatus.OK) - async getById(@Param('id') id: number) { + async getId(@Param('id') id: number) { return await this.lancamentoService.getById(id); } @Delete('/:id') @HttpCode(HttpStatus.NO_CONTENT) - async deleteById(@Param('id') id: number) { + async deleteId(@Param('id') id: number) { return await this.lancamentoService.delete(id); } } diff --git a/src/lancamento/lancamento.module.ts b/src/lancamento/lancamento.module.ts index 423994d8..dca16181 100644 --- a/src/lancamento/lancamento.module.ts +++ b/src/lancamento/lancamento.module.ts @@ -2,7 +2,7 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { UsersModule } from 'src/users/users.module'; import { LancamentoController } from './lancamento.controller'; -import { Lancamento } from './lancamento.entity'; +import { Lancamento } from './entities/lancamento.entity'; import { LancamentoService } from './lancamento.service'; import { LancamentoRepository } from './lancamento.repository'; import { ClienteFavorecidoModule } from 'src/cnab/cliente-favorecido.module'; diff --git a/src/lancamento/lancamento.repository.ts b/src/lancamento/lancamento.repository.ts index 0649a744..3f51d9e7 100644 --- a/src/lancamento/lancamento.repository.ts +++ b/src/lancamento/lancamento.repository.ts @@ -1,7 +1,7 @@ -import { Injectable, NotFoundException } from '@nestjs/common'; +import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { DeepPartial, DeleteResult, FindManyOptions, FindOneOptions, FindOptionsWhere, Repository, SaveOptions } from 'typeorm'; -import { Lancamento } from './lancamento.entity'; +import { DeepPartial, DeleteResult, FindManyOptions, FindOneOptions, Repository, SaveOptions } from 'typeorm'; +import { Lancamento } from './entities/lancamento.entity'; @Injectable() export class LancamentoRepository { @@ -19,11 +19,15 @@ export class LancamentoRepository { } findOne(options: FindOneOptions): Promise { - return this.findOne(options); + return this.lancamentoRepository.findOne(options); } - async getOne(where: FindOptionsWhere): Promise { - return await this.lancamentoRepository.findOneByOrFail(where); + async getOne(where: FindOneOptions): Promise { + const found = await this.lancamentoRepository.findOne(where); + if (!found) { + throw new HttpException('Lancamento não encontrado', HttpStatus.NOT_FOUND); + } + return found; } findMany(options?: FindManyOptions | undefined): Promise { diff --git a/src/lancamento/lancamento.service.ts b/src/lancamento/lancamento.service.ts index 14b9a82f..ff322063 100644 --- a/src/lancamento/lancamento.service.ts +++ b/src/lancamento/lancamento.service.ts @@ -8,9 +8,10 @@ import { CommonHttpException } from 'src/utils/http-exception/common-http-except import { Between, IsNull, Like, Not } from 'typeorm'; import { AutorizaLancamentoDto } from './dtos/AutorizaLancamentoDto'; import { LancamentoInputDto } from './dtos/lancamento-input.dto'; -import { Lancamento } from './lancamento.entity'; +import { ILancamento, Lancamento } from './entities/lancamento.entity'; import { LancamentoRepository } from './lancamento.repository'; import { ClienteFavorecido } from 'src/cnab/entity/cliente-favorecido.entity'; +import { LancamentoAutorizacao } from './entities/lancamento-autorizacao.entity'; @Injectable() export class LancamentoService { @@ -47,12 +48,12 @@ export class LancamentoService { where: { data_lancamento: Between(sex, qui), transacao: IsNull(), - autorizado_por: Like('%,%'), // Ao menos 2 aprovados (1 vírgula) + is_autorizado: true, }, }); } - async findByPeriod(args?: { mes?: number; periodo?: number; ano?: number; autorizado?: boolean }): Promise { + async find(args?: { mes?: number; periodo?: number; ano?: number; autorizado?: boolean }): Promise { /** [startDate, endDate] */ let dateRange: [Date, Date] | null = null; if (args?.mes && args?.periodo && args?.ano) { @@ -62,7 +63,7 @@ export class LancamentoService { const lancamentos = await this.lancamentoRepository.findMany({ where: { ...(dateRange ? { data_lancamento: Between(...dateRange) } : {}), - ...(args?.autorizado !== undefined ? (args.autorizado === true ? { autorizado_por: Like('%,%') } : { autorizado_por: Not(Like('%,%')) }) : {}), + ...(args?.autorizado !== undefined ? { is_autorizado: args.autorizado } : {}), }, relations: ['user'], }); @@ -70,37 +71,23 @@ export class LancamentoService { return lancamentos; } - async findByStatus(status: boolean): Promise { - let lancamentos = await this.lancamentoRepository.findMany({ - where: { - ...(status === true ? { autorizado_por: Like('%,%') } : { autorizado_por: Not(Like('%,%')) }), - }, - relations: ['user'], - }); - + async findByStatus(isAutorizado: boolean): Promise { + const lancamentos = await this.lancamentoRepository.findMany({ where: { is_autorizado: isAutorizado }, relations: ['autorizacoes'] as (keyof ILancamento)[] }); return lancamentos; } async getValorAutorizado(month: number, period: number, year: number) { const [startDate, endDate] = this.getMonthDateRange(year, month, period); - - const autorizados = await this.lancamentoRepository.findMany({ - where: { - data_lancamento: Between(startDate, endDate), - autorizado_por: Like('%,%'), // Ao menos 2 aprovados (1 vírgula) - }, - }); + const autorizados = await this.lancamentoRepository.findMany({ where: { data_lancamento: Between(startDate, endDate) }, relations: ['autorizacoes'] as (keyof ILancamento)[] }); const autorizadoSum = autorizados.reduce((sum, lancamento) => sum + lancamento.valor, 0); - const resp = { - valor_autorizado: autorizadoSum, - }; + const resp = { valor_autorizado: autorizadoSum }; return resp; } async create(dto: LancamentoInputDto): Promise { const lancamento = await this.validateLancamentoDto(dto); const created = await this.lancamentoRepository.save(this.lancamentoRepository.create(lancamento)); - const getCreated = await this.lancamentoRepository.getOne({ clienteFavorecido: { id: created.clienteFavorecido.id } }); + const getCreated = await this.lancamentoRepository.getOne({ where: { clienteFavorecido: { id: created.clienteFavorecido.id } }, relations: ['autorizacoes'] as (keyof ILancamento)[] }); return getCreated; } @@ -115,41 +102,44 @@ export class LancamentoService { } async autorizarPagamento(userId: number, lancamentoId: string, AutorizaLancamentoDto: AutorizaLancamentoDto): Promise { + const lancamento = await this.lancamentoRepository.findOne({ where: { id: parseInt(lancamentoId) }, relations: ['autorizacoes'] as (keyof ILancamento)[] }); + if (!lancamento) { + throw new HttpException('Lançamento não encontrado.', HttpStatus.NOT_FOUND); + } + const user = await this.usersService.findOne({ id: userId }); if (!user) { throw new HttpException('Usuário não encontrado', HttpStatus.UNAUTHORIZED); } + if (lancamento.hasAutorizadoPor(user.id)) { + throw new HttpException('Usuário já autorizou este Lançamento', HttpStatus.PRECONDITION_FAILED); + } + const isValidPassword = await bcrypt.compare(AutorizaLancamentoDto.password, user.password); if (!isValidPassword) { throw new HttpException('Senha inválida', HttpStatus.UNAUTHORIZED); } - const lancamento = await this.lancamentoRepository.findOne({ where: { id: parseInt(lancamentoId) } }); - if (!lancamento) { - throw new Error('Lançamento não encontrado.'); - } - - lancamento.pushAutorizado(userId); + lancamento.addAutorizado(userId); return await this.lancamentoRepository.save(lancamento); } async update(id: number, updateDto: LancamentoInputDto): Promise { - const lancamento = await this.lancamentoRepository.findOne({ where: { id } }); + const lancamento = await this.lancamentoRepository.findOne({ where: { id }, relations: ['autorizacoes'] as (keyof ILancamento)[] }); if (!lancamento) { throw new NotFoundException(`Lançamento com ID ${id} não encontrado.`); } lancamento.updateFromInputDto(updateDto); - const updated = await this.lancamentoRepository.save(lancamento); + await this.lancamentoRepository.save(lancamento); + const updated = await this.lancamentoRepository.getOne({ where: { id: lancamento.id }, relations: ['autorizacoes'] as (keyof ILancamento)[] }); this.logger.log(`Lancamento #${updated.id} atualizado por ${updated.clienteFavorecido.nome}.`); - return lancamento; + return updated; } async getById(id: number): Promise { - const lancamento = await this.lancamentoRepository.findOne({ - where: { id }, - }); + const lancamento = await this.lancamentoRepository.findOne({ where: { id }, relations: ['autorizacoes'] as (keyof ILancamento)[] }); if (!lancamento) { throw new NotFoundException(`Lançamento com ID ${id} não encontrado.`); } diff --git a/src/users/entities/user.entity.ts b/src/users/entities/user.entity.ts index bb42c78f..9e2303c0 100644 --- a/src/users/entities/user.entity.ts +++ b/src/users/entities/user.entity.ts @@ -6,28 +6,12 @@ import { Bank } from 'src/banks/entities/bank.entity'; import { InviteStatus } from 'src/mail-history-statuses/entities/mail-history-status.entity'; import { MailHistory } from 'src/mail-history/entities/mail-history.entity'; import { EntityHelper } from 'src/utils/entity-helper'; -import { - AfterLoad, - BeforeInsert, - BeforeUpdate, - Column, - CreateDateColumn, - DeepPartial, - DeleteDateColumn, - Entity, - Index, - JoinColumn, - ManyToMany, - ManyToOne, - OneToMany, - PrimaryGeneratedColumn, - UpdateDateColumn, -} from 'typeorm'; +import { AfterLoad, BeforeInsert, BeforeUpdate, Column, CreateDateColumn, DeepPartial, DeleteDateColumn, Entity, Index, JoinColumn, ManyToMany, ManyToOne, OneToMany, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'; import { FileEntity } from '../../files/entities/file.entity'; import { Role } from '../../roles/entities/role.entity'; import { Status } from '../../statuses/entities/status.entity'; import { UserHttpException } from 'src/utils/http-exception/user-http-exception'; -import { Lancamento } from 'src/lancamento/lancamento.entity'; +import { Lancamento } from 'src/lancamento/entities/lancamento.entity'; /** uniqueConstraintName: `UQ_User_email` */ @Entity() @@ -170,6 +154,9 @@ export class User extends EntityHelper { @Column({ type: String, nullable: true }) passValidatorId?: string; + @ManyToMany(() => Lancamento, (lancamento) => lancamento) + lancamentos: Lancamento[]; + @Expose({ name: 'aux_isRegistrationComplete' }) aux_isRegistrationComplete(): boolean { return ( diff --git a/src/utils/all-exteptions-filter/custom-validation-options.ts b/src/utils/all-exteptions-filter/custom-validation-options.ts index dff8e802..3caa3d92 100644 --- a/src/utils/all-exteptions-filter/custom-validation-options.ts +++ b/src/utils/all-exteptions-filter/custom-validation-options.ts @@ -28,9 +28,7 @@ interface DecodedList { * A helper to pass extra error parameters from class-validator to a global errorFilter or similar. * @returns ValidationOptions with JSON encoding `message` and extra fields in `message` field. */ -export function customValidationOptions( - validationOptions: CustomValidationOptions, -): ValidationOptions { +export function customValidationOptions(validationOptions: CustomValidationOptions): ValidationOptions { const extraFields: CustomValidationOptions = { ...(validationOptions?.statusCode && { statusCode: validationOptions.statusCode, @@ -58,15 +56,15 @@ interface BehaviorOptions { * @returns The same HttpException but with decoded data for customValidation */ export function getCustomValidationOptions( - errorResponse: HttpExceptionResponse, + errorResponse: HttpExceptionResponse | string, behavior: BehaviorOptions = { setLowestStatus: true, }, ): Partial | null { - if (!errorResponse?.errors) { + if (typeof errorResponse !== 'string' && !errorResponse?.errors) { return null; } - const newResponse: Partial = { ...errorResponse }; + const newResponse: Partial = { ...(typeof errorResponse === 'string' ? { message: errorResponse } : errorResponse) }; newResponse.errors = {}; newResponse.details = {}; newResponse.statusCodes = {}; @@ -76,35 +74,34 @@ export function getCustomValidationOptions( statusCodes: [], }; let decodedLowestStatus: SerializedValidationOptions | undefined; - for (const [errorKey, errorValue] of Object.entries(errorResponse.errors)) { - const substrings = asJSONStrOrObj(errorValue) - .split(/,(?![^{}]*})/) - .map((substring) => substring.trim()); - const list: DecodedList = { - details: [], - errors: [], - statusCodes: [], - }; - for (const substring of substrings) { - try { - const decodedJson: SerializedValidationOptions = JSON.parse(substring); - const { statusCode, error, details } = decodedJson; - error && list.errors.push(error); - details && list.details.push(details); - statusCode && list.statusCodes.push(statusCode); - if ( - statusCode && - (!decodedLowestStatus || decodedLowestStatus.statusCode > statusCode) - ) { - decodedLowestStatus = decodedJson; + if (typeof errorResponse !== 'string' && errorResponse?.errors) { + for (const [errorKey, errorValue] of Object.entries(errorResponse.errors)) { + const substrings = asJSONStrOrObj(errorValue) + .split(/,(?![^{}]*})/) + .map((substring) => substring.trim()); + const list: DecodedList = { + details: [], + errors: [], + statusCodes: [], + }; + for (const substring of substrings) { + try { + const decodedJson: SerializedValidationOptions = JSON.parse(substring); + const { statusCode, error, details } = decodedJson; + error && list.errors.push(error); + details && list.details.push(details); + statusCode && list.statusCodes.push(statusCode); + if (statusCode && (!decodedLowestStatus || decodedLowestStatus.statusCode > statusCode)) { + decodedLowestStatus = decodedJson; + } + } catch (error) { + list.errors.push(substring); } - } catch (error) { - list.errors.push(substring); } - } - for (const listKey in list) { - if (list[listKey].length) { - newResponse[listKey][errorKey] = list[listKey].join(', '); + for (const listKey in list) { + if (list[listKey].length) { + newResponse[listKey][errorKey] = list[listKey].join(', '); + } } } } diff --git a/src/utils/all-exteptions-filter/filters/all-exceptions.filter.ts b/src/utils/all-exteptions-filter/filters/all-exceptions.filter.ts index ddbc1521..30a85fb2 100644 --- a/src/utils/all-exteptions-filter/filters/all-exceptions.filter.ts +++ b/src/utils/all-exteptions-filter/filters/all-exceptions.filter.ts @@ -1,18 +1,9 @@ -import { - ArgumentsHost, - Catch, - ExceptionFilter, - HttpException, - HttpStatus -} from '@nestjs/common'; +import { ArgumentsHost, Catch, ExceptionFilter, HttpException, HttpStatus } from '@nestjs/common'; import { Request, Response } from 'express'; import { CustomLogger } from 'src/utils/custom-logger'; import { formatError } from 'src/utils/log-utils'; import { getCustomValidationOptions } from '../custom-validation-options'; -import { - CustomHttpExceptionResponse, - HttpExceptionResponse, -} from '../interfaces/http-exception-response.interface'; +import { CustomHttpExceptionResponse, HttpExceptionResponse } from '../interfaces/http-exception-response.interface'; @Catch() export class AllExceptionsFilter implements ExceptionFilter { @@ -39,20 +30,17 @@ export class AllExceptionsFilter implements ExceptionFilter { response.status(responseData.statusCode).json(responseJson); } - private getResponseData(kwargs: { - exception: unknown; - request: Request; - }): CustomHttpExceptionResponse { + private getResponseData(kwargs: { exception: unknown; request: Request }): CustomHttpExceptionResponse { const { exception, request } = kwargs; let responseData: CustomHttpExceptionResponse = { - statusCode: HttpStatus.INTERNAL_SERVER_ERROR, + statusCode: (exception as any)?.status || HttpStatus.INTERNAL_SERVER_ERROR, uri: request.url, method: request.method, timestamp: new Date(), }; if (exception instanceof HttpException) { - const httpResponse = exception.getResponse() as HttpExceptionResponse; + const httpResponse = exception.getResponse() as HttpExceptionResponse | string; const customResponse = getCustomValidationOptions(httpResponse, { setLowestStatus: true, setMainMessage: true, @@ -61,7 +49,7 @@ export class AllExceptionsFilter implements ExceptionFilter { responseData = { ...responseData, statusCode: exception.getStatus(), - ...(customResponse || httpResponse), + ...(customResponse || (typeof httpResponse === 'string' ? { message: httpResponse } : httpResponse)), }; } if (responseData?.message) { @@ -82,18 +70,10 @@ export class AllExceptionsFilter implements ExceptionFilter { return responseData; } - private getErrorLogSummary = ( - errorResponse: CustomHttpExceptionResponse, - exception?: unknown, - ): string => { - const { statusCode, clientMessage, method, uri, internalMessage } = - errorResponse; + private getErrorLogSummary = (errorResponse: CustomHttpExceptionResponse, exception?: unknown): string => { + const { statusCode, clientMessage, method, uri, internalMessage } = errorResponse; - const errorLog = formatError( - `Code: ${statusCode} - ${method}: ${uri}`, - { ...clientMessage, ...internalMessage }, - exception instanceof Error ? (exception as Error) : undefined, - ); + const errorLog = formatError(`Code: ${statusCode} - ${method}: ${uri}`, { ...clientMessage, ...internalMessage }, exception instanceof Error ? (exception as Error) : undefined); return errorLog; }; From f35bfd5834941be9607311ab6919a7e79a135d00 Mon Sep 17 00:00:00 2001 From: Raphael Rivas Date: Fri, 30 Aug 2024 14:52:48 -0300 Subject: [PATCH 08/29] feat: endpoint get --- src/lancamento/lancamento.controller.ts | 12 ++++++------ src/lancamento/lancamento.service.ts | 12 +++++------- src/utils/pipes/parse-boolean.pipe.ts | 14 ++++++++------ 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/lancamento/lancamento.controller.ts b/src/lancamento/lancamento.controller.ts index 1346aa52..bf7e35de 100644 --- a/src/lancamento/lancamento.controller.ts +++ b/src/lancamento/lancamento.controller.ts @@ -29,17 +29,17 @@ export class LancamentoController { RoleEnum.aprovador_financeiro, ) @Get('/') - @ApiQuery({ name: 'mes', type: Number, required: false, description: 'Mês do lançamento' }) - @ApiQuery({ name: 'periodo', type: Number, required: false, description: ApiDescription({ _: 'Período do lançamento. Primeira quinzena ou segunda quinzena.', min: 1, max: 2 }) }) - @ApiQuery({ name: 'ano', type: Number, required: false, description: 'Ano do lançamento.' }) + @ApiQuery({ name: 'periodo', type: Number, required: false, description: ApiDescription({ _: 'Período do lançamento. Primeira quinzena (dia 5) ou segunda quinzena (dia 20).', conditions: "periodo, mes, ano muest be filled. Otherwise it won't filter by date", min: 1, max: 2 }) }) + @ApiQuery({ name: 'mes', type: Number, required: false, description: ApiDescription({ _: 'Mês do lançamento', conditions: "periodo, mes, ano muest be filled. Otherwise it won't filter by date" }) }) + @ApiQuery({ name: 'ano', type: Number, required: false, description: ApiDescription({ _: 'Ano do lançamento.', conditions: "periodo, mes, ano muest be filled. Otherwise it won't filter by date" }) }) @ApiQuery({ name: 'autorizado', type: Boolean, required: false, description: 'Fitra se foi autorizado ou não.' }) @HttpCode(HttpStatus.OK) async get( @Request() request, // - @Query('mes') mes: number, @Query('periodo', new ParseNumberPipe({ min: 1, max: 2, optional: true })) periodo: number | undefined, - @Query('ano') ano: number, - @Query('autorizado') autorizado: boolean | undefined, + @Query('mes', new ParseNumberPipe({ min: 1, max: 12, optional: true })) mes: number | undefined, + @Query('ano') ano: number | undefined, + @Query('autorizado', new ParseBooleanPipe({ optional: true })) autorizado: boolean | undefined, ): Promise { return await this.lancamentoService.find({ mes, periodo, ano, autorizado }); } diff --git a/src/lancamento/lancamento.service.ts b/src/lancamento/lancamento.service.ts index ff322063..0d101c83 100644 --- a/src/lancamento/lancamento.service.ts +++ b/src/lancamento/lancamento.service.ts @@ -1,6 +1,6 @@ import { HttpException, HttpStatus, Injectable, NotFoundException } from '@nestjs/common'; import * as bcrypt from 'bcryptjs'; -import { endOfDay, isFriday, nextFriday, startOfDay, subDays } from 'date-fns'; +import { endOfDay, isFriday, lastDayOfMonth, nextFriday, startOfDay, subDays } from 'date-fns'; import { ClienteFavorecidoService } from 'src/cnab/service/cliente-favorecido.service'; import { UsersService } from 'src/users/users.service'; import { CustomLogger } from 'src/utils/custom-logger'; @@ -57,7 +57,7 @@ export class LancamentoService { /** [startDate, endDate] */ let dateRange: [Date, Date] | null = null; if (args?.mes && args?.periodo && args?.ano) { - dateRange = this.getMonthDateRange(args.ano, args.mes, args.periodo); + dateRange = this.getMonthDateRange(args.ano, args.mes, args?.periodo); } const lancamentos = await this.lancamentoRepository.findMany({ @@ -65,7 +65,7 @@ export class LancamentoService { ...(dateRange ? { data_lancamento: Between(...dateRange) } : {}), ...(args?.autorizado !== undefined ? { is_autorizado: args.autorizado } : {}), }, - relations: ['user'], + relations: ['autorizacoes'] as (keyof ILancamento)[], }); return lancamentos; @@ -156,12 +156,10 @@ export class LancamentoService { if (period === 1) { startDate = new Date(year, month - 1, 1); - endDate = new Date(year, month - 1, 15); - endDate.setHours(23, 59, 59); + endDate = endOfDay(new Date(year, month - 1, 15)); } else if (period === 2) { startDate = new Date(year, month - 1, 16); - endDate = new Date(year, month, 0); - endDate.setHours(23, 59, 59); + endDate = endOfDay(new Date(year, month, 0)); } else { throw new Error('Invalid period. Period should be 1 or 2.'); } diff --git a/src/utils/pipes/parse-boolean.pipe.ts b/src/utils/pipes/parse-boolean.pipe.ts index 56ccc2d3..f07503b6 100644 --- a/src/utils/pipes/parse-boolean.pipe.ts +++ b/src/utils/pipes/parse-boolean.pipe.ts @@ -1,9 +1,4 @@ -import { - BadRequestException, - Injectable, - PipeTransform, - ArgumentMetadata, -} from '@nestjs/common'; +import { BadRequestException, Injectable, PipeTransform, ArgumentMetadata, HttpException, HttpStatus } from '@nestjs/common'; /** * @param required default is `false` @@ -21,6 +16,13 @@ export class ParseBooleanPipe implements PipeTransform { const defaultValue = this?.args?.defaultValue; const field = metadata.data; + if (!['true', 'false', true, false, undefined].includes(value)) { + let _value = value; + if (_value === '') { + _value = 'got empty string'; + } + throw new HttpException(`${field}: Invalid boolean value (${_value})`, HttpStatus.BAD_REQUEST); + } const booleanValue = value == 'true' || value == true; if (value === undefined || field === undefined) { From a903503d5e7370bf42f5f38169e1969521b9e257 Mon Sep 17 00:00:00 2001 From: Raphael Rivas Date: Fri, 30 Aug 2024 14:59:19 -0300 Subject: [PATCH 09/29] fix: delete lancamento with auth roles --- src/lancamento/lancamento.controller.ts | 12 ++++++++++-- src/lancamento/lancamento.service.ts | 4 ++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/lancamento/lancamento.controller.ts b/src/lancamento/lancamento.controller.ts index bf7e35de..04cce2b8 100644 --- a/src/lancamento/lancamento.controller.ts +++ b/src/lancamento/lancamento.controller.ts @@ -29,7 +29,7 @@ export class LancamentoController { RoleEnum.aprovador_financeiro, ) @Get('/') - @ApiQuery({ name: 'periodo', type: Number, required: false, description: ApiDescription({ _: 'Período do lançamento. Primeira quinzena (dia 5) ou segunda quinzena (dia 20).', conditions: "periodo, mes, ano muest be filled. Otherwise it won't filter by date", min: 1, max: 2 }) }) + @ApiQuery({ name: 'periodo', type: Number, required: false, description: ApiDescription({ _: 'Período do lançamento. Primeira quinzena (dias 1-15) ou segunda quinzena (dias 16 até o fim do mês).', conditions: "periodo, mes, ano muest be filled. Otherwise it won't filter by date", min: 1, max: 2 }) }) @ApiQuery({ name: 'mes', type: Number, required: false, description: ApiDescription({ _: 'Mês do lançamento', conditions: "periodo, mes, ano muest be filled. Otherwise it won't filter by date" }) }) @ApiQuery({ name: 'ano', type: Number, required: false, description: ApiDescription({ _: 'Ano do lançamento.', conditions: "periodo, mes, ano muest be filled. Otherwise it won't filter by date" }) }) @ApiQuery({ name: 'autorizado', type: Boolean, required: false, description: 'Fitra se foi autorizado ou não.' }) @@ -73,7 +73,7 @@ export class LancamentoController { ) @Get('/getValorAutorizado') @ApiQuery({ name: 'mes', required: true, description: 'Mês do lançamento' }) - @ApiQuery({ name: 'periodo', required: true, description: 'Período do lançamento. primeira quinzena ou segunda quinzena.' }) + @ApiQuery({ name: 'periodo', required: true, description: 'Período do lançamento. Primeira quinzena (dias 1-15) ou segunda quinzena (dias 16 até o fim do mês).' }) @ApiQuery({ name: 'ano', required: true, description: 'Ano do lançamento.' }) @HttpCode(HttpStatus.OK) async getValorAutorizado( @@ -163,6 +163,14 @@ export class LancamentoController { @Delete('/:id') @HttpCode(HttpStatus.NO_CONTENT) + @UseGuards(AuthGuard('jwt'), RolesGuard) + @Roles( + RoleEnum.master, // + RoleEnum.admin_finan, + RoleEnum.lancador_financeiro, + RoleEnum.aprovador_financeiro, + ) + @ApiBearerAuth() async deleteId(@Param('id') id: number) { return await this.lancamentoService.delete(id); } diff --git a/src/lancamento/lancamento.service.ts b/src/lancamento/lancamento.service.ts index 0d101c83..22330258 100644 --- a/src/lancamento/lancamento.service.ts +++ b/src/lancamento/lancamento.service.ts @@ -1,6 +1,6 @@ import { HttpException, HttpStatus, Injectable, NotFoundException } from '@nestjs/common'; import * as bcrypt from 'bcryptjs'; -import { endOfDay, isFriday, lastDayOfMonth, nextFriday, startOfDay, subDays } from 'date-fns'; +import { endOfDay, endOfMonth, isFriday, lastDayOfMonth, nextFriday, startOfDay, subDays } from 'date-fns'; import { ClienteFavorecidoService } from 'src/cnab/service/cliente-favorecido.service'; import { UsersService } from 'src/users/users.service'; import { CustomLogger } from 'src/utils/custom-logger'; @@ -159,7 +159,7 @@ export class LancamentoService { endDate = endOfDay(new Date(year, month - 1, 15)); } else if (period === 2) { startDate = new Date(year, month - 1, 16); - endDate = endOfDay(new Date(year, month, 0)); + endDate = endOfMonth(new Date(year, month, 0)); } else { throw new Error('Invalid period. Period should be 1 or 2.'); } From a802b49543dcd0447486f495b3e33bb27a71194b Mon Sep 17 00:00:00 2001 From: Raphael Rivas Date: Fri, 30 Aug 2024 15:05:53 -0300 Subject: [PATCH 10/29] feat: getValorAutorizado --- src/lancamento/lancamento.controller.ts | 33 ++++++++++++++----------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/src/lancamento/lancamento.controller.ts b/src/lancamento/lancamento.controller.ts index 04cce2b8..1bae8751 100644 --- a/src/lancamento/lancamento.controller.ts +++ b/src/lancamento/lancamento.controller.ts @@ -20,7 +20,8 @@ import { LancamentoService } from './lancamento.service'; export class LancamentoController { constructor(private readonly lancamentoService: LancamentoService) {} - @ApiBearerAuth() + @Get('/') + @HttpCode(HttpStatus.OK) @UseGuards(AuthGuard('jwt'), RolesGuard) @Roles( RoleEnum.master, // @@ -28,12 +29,11 @@ export class LancamentoController { RoleEnum.lancador_financeiro, RoleEnum.aprovador_financeiro, ) - @Get('/') + @ApiBearerAuth() @ApiQuery({ name: 'periodo', type: Number, required: false, description: ApiDescription({ _: 'Período do lançamento. Primeira quinzena (dias 1-15) ou segunda quinzena (dias 16 até o fim do mês).', conditions: "periodo, mes, ano muest be filled. Otherwise it won't filter by date", min: 1, max: 2 }) }) @ApiQuery({ name: 'mes', type: Number, required: false, description: ApiDescription({ _: 'Mês do lançamento', conditions: "periodo, mes, ano muest be filled. Otherwise it won't filter by date" }) }) @ApiQuery({ name: 'ano', type: Number, required: false, description: ApiDescription({ _: 'Ano do lançamento.', conditions: "periodo, mes, ano muest be filled. Otherwise it won't filter by date" }) }) @ApiQuery({ name: 'autorizado', type: Boolean, required: false, description: 'Fitra se foi autorizado ou não.' }) - @HttpCode(HttpStatus.OK) async get( @Request() request, // @Query('periodo', new ParseNumberPipe({ min: 1, max: 2, optional: true })) periodo: number | undefined, @@ -44,7 +44,8 @@ export class LancamentoController { return await this.lancamentoService.find({ mes, periodo, ano, autorizado }); } - @ApiBearerAuth() + @Get('/getbystatus') + @HttpCode(HttpStatus.OK) @UseGuards(AuthGuard('jwt'), RolesGuard) @Roles( RoleEnum.master, // @@ -52,9 +53,9 @@ export class LancamentoController { RoleEnum.lancador_financeiro, RoleEnum.aprovador_financeiro, ) - @Get('/getbystatus') + @ApiOperation({ description: 'Pesquisar Lançamentos pelo status' }) + @ApiBearerAuth() @ApiQuery({ name: 'autorizado', type: Boolean, required: true, description: 'Fitra se foi autorizado ou não.' }) - @HttpCode(HttpStatus.OK) async getByStatus( @Request() request, // @Query('autorizado', new ParseBooleanPipe()) autorizado: boolean | undefined, @@ -63,7 +64,8 @@ export class LancamentoController { return await this.lancamentoService.findByStatus(_autorizado); } - @ApiBearerAuth() + @Get('/getValorAutorizado') + @HttpCode(HttpStatus.OK) @UseGuards(AuthGuard('jwt'), RolesGuard) @Roles( RoleEnum.master, // @@ -71,11 +73,12 @@ export class LancamentoController { RoleEnum.lancador_financeiro, RoleEnum.aprovador_financeiro, ) - @Get('/getValorAutorizado') + @ApiOperation({ description: 'Obter a soma dos valores dos Lançamentos.' }) + @ApiBearerAuth() @ApiQuery({ name: 'mes', required: true, description: 'Mês do lançamento' }) @ApiQuery({ name: 'periodo', required: true, description: 'Período do lançamento. Primeira quinzena (dias 1-15) ou segunda quinzena (dias 16 até o fim do mês).' }) @ApiQuery({ name: 'ano', required: true, description: 'Ano do lançamento.' }) - @HttpCode(HttpStatus.OK) + @ApiOperation({ description: `Inclui uma autorização do usuário autenticado para o Lançamento.` }) async getValorAutorizado( @Request() request, // @Query('mes') mes: number, @@ -85,7 +88,8 @@ export class LancamentoController { return await this.lancamentoService.getValorAutorizado(mes, periodo, ano); } - @ApiBearerAuth() + @Post('/create') + @HttpCode(HttpStatus.CREATED) @UseGuards(AuthGuard('jwt'), RolesGuard) @Roles( RoleEnum.master, // @@ -93,9 +97,8 @@ export class LancamentoController { RoleEnum.lancador_financeiro, RoleEnum.aprovador_financeiro, ) + @ApiBearerAuth() @ApiBody({ type: LancamentoInputDto }) - @HttpCode(HttpStatus.CREATED) - @Post('/create') async postCreateLancamento( @Request() req: any, // @Body() lancamentoDto: LancamentoInputDto, @@ -147,7 +150,8 @@ export class LancamentoController { return await this.lancamentoService.update(lancamentoId, lancamentoDto); } - @ApiBearerAuth() + @Get('/:id') + @HttpCode(HttpStatus.OK) @UseGuards(AuthGuard('jwt'), RolesGuard) @Roles( RoleEnum.master, // @@ -155,8 +159,7 @@ export class LancamentoController { RoleEnum.lancador_financeiro, RoleEnum.aprovador_financeiro, ) - @Get('/:id') - @HttpCode(HttpStatus.OK) + @ApiBearerAuth() async getId(@Param('id') id: number) { return await this.lancamentoService.getById(id); } From f73085a8fa8ffe785acb1f7eea54459eb7e8c3bc Mon Sep 17 00:00:00 2001 From: Raphael Rivas Date: Mon, 2 Sep 2024 09:46:59 -0300 Subject: [PATCH 11/29] doc: import and aql table alias --- .../extrato/extrato-header-arquivo.service.ts | 22 +++++++++---------- src/lancamento/lancamento.service.ts | 22 ++++--------------- 2 files changed, 15 insertions(+), 29 deletions(-) diff --git a/src/cnab/service/extrato/extrato-header-arquivo.service.ts b/src/cnab/service/extrato/extrato-header-arquivo.service.ts index c844c3f9..64302d10 100644 --- a/src/cnab/service/extrato/extrato-header-arquivo.service.ts +++ b/src/cnab/service/extrato/extrato-header-arquivo.service.ts @@ -89,23 +89,23 @@ export class ExtratoHeaderArquivoService { : PagadorContaEnum.ContaBilhetagem; const query = ` - SELECT dthe."dataLancamento", - dthe.nsr AS processo, - 'Doc:'|| dthe."numeroInscricao"||'Ag.: '|| dthe.agencia || '-' || dthe."dvAgencia"|| - 'Conta: '|| dthe.conta ||'-'|| dthe."dvConta" AS lancamento, - 'Doc: '||dthe."numeroInscricao"||'Ag.: '||dthe.agencia||'-'||dthe."dvAgencia"|| - 'Conta: '||dthe.conta||'-'||dthe."dvConta" AS operacao, - dthe."tipoLancamento" AS tipo, - dthe."valorLancamento" AS valor + SELECT ede."dataLancamento", + ede.nsr AS processo, + 'Doc:'|| ede."numeroInscricao"||'Ag.: '|| ede.agencia || '-' || ede."dvAgencia"|| + 'Conta: '|| ede.conta ||'-'|| ede."dvConta" AS lancamento, + 'Doc: '||ede."numeroInscricao"||'Ag.: '||ede.agencia||'-'||ede."dvAgencia"|| + 'Conta: '||ede.conta||'-'||ede."dvConta" AS operacao, + ede."tipoLancamento" AS tipo, + ede."valorLancamento" AS valor FROM public.extrato_header_arquivo ha INNER JOIN public.extrato_header_lote ehl on ha.id = ehl."extratoHeaderArquivoId" - INNER JOIN public.extrato_detalhe_e dthe on ehl.id = dthe."extratoHeaderLoteId" + INNER JOIN public.extrato_detalhe_e ede on ehl.id = ede."extratoHeaderLoteId" WHERE ha."numeroConta" = '${_conta}' ${ _tipoLancamento - ? `\nAND dthe."tipoLancamento" = '${_tipoLancamento}'` + ? `\nAND ede."tipoLancamento" = '${_tipoLancamento}'` : '' } - AND dthe."dataLancamento" between '${_dt_inicio}' AND '${_dt_fim}'`; + AND ede."dataLancamento" between '${_dt_inicio}' AND '${_dt_fim}'`; return await this.entityManager.query(compactQuery(query)); } } diff --git a/src/lancamento/lancamento.service.ts b/src/lancamento/lancamento.service.ts index 22330258..73bf31b2 100644 --- a/src/lancamento/lancamento.service.ts +++ b/src/lancamento/lancamento.service.ts @@ -1,17 +1,16 @@ import { HttpException, HttpStatus, Injectable, NotFoundException } from '@nestjs/common'; import * as bcrypt from 'bcryptjs'; -import { endOfDay, endOfMonth, isFriday, lastDayOfMonth, nextFriday, startOfDay, subDays } from 'date-fns'; +import { endOfDay, endOfMonth, isFriday, nextFriday, startOfDay, subDays } from 'date-fns'; +import { ClienteFavorecido } from 'src/cnab/entity/cliente-favorecido.entity'; import { ClienteFavorecidoService } from 'src/cnab/service/cliente-favorecido.service'; import { UsersService } from 'src/users/users.service'; import { CustomLogger } from 'src/utils/custom-logger'; import { CommonHttpException } from 'src/utils/http-exception/common-http-exception'; -import { Between, IsNull, Like, Not } from 'typeorm'; +import { Between, IsNull } from 'typeorm'; import { AutorizaLancamentoDto } from './dtos/AutorizaLancamentoDto'; import { LancamentoInputDto } from './dtos/lancamento-input.dto'; import { ILancamento, Lancamento } from './entities/lancamento.entity'; import { LancamentoRepository } from './lancamento.repository'; -import { ClienteFavorecido } from 'src/cnab/entity/cliente-favorecido.entity'; -import { LancamentoAutorizacao } from './entities/lancamento-autorizacao.entity'; @Injectable() export class LancamentoService { @@ -23,21 +22,8 @@ export class LancamentoService { private readonly clienteFavorecidoService: ClienteFavorecidoService, ) {} - // /** - // * Get unused data (no Transacao Id) from current payment week (thu-wed / qui-qua). - // */ - // async findByLancamentos(lancamentos: LancamentoEntity[]): Promise { - // const ids = lancamentos.map(i => i.id); - // const found = await this.lancamentoRepository.findMany({ - // where: { - // id: In(ids) - // } - // }); - // return found; - // } - /** - * Get unused data (no Transacao Id) from current payment week (sex-qui). + * Procura itens não usados ainda (sem Transacao Id) from current payment week (sex-qui). */ async findToPayWeek(): Promise { const today = new Date(); From 3354eb1beef5cdc747ab62f3f96d5356db713b54 Mon Sep 17 00:00:00 2001 From: Raphael Rivas Date: Mon, 2 Sep 2024 09:53:15 -0300 Subject: [PATCH 12/29] refactor: rename transaca-view folder --- src/app.module.ts | 4 ++-- src/bank-statements/bank-statements.module.ts | 2 +- src/bank-statements/bank-statements.repository.ts | 2 +- src/cnab/cnab.module.ts | 2 +- src/cnab/cnab.service.ts | 6 +++--- src/cnab/service/pagamento/remessa-retorno.service.ts | 2 +- src/cnab/test/cnab-service/cnab.service.spec.ts | 4 ++-- .../cnab-update-transacao-view-bq-seed.module.ts | 2 +- src/ticket-revenues/ticket-revenues-repository.ts | 4 ++-- src/ticket-revenues/ticket-revenues.module.ts | 2 +- src/ticket-revenues/ticket-revenues.service.ts | 4 ++-- .../interfaces/previous-days-args.ts | 0 .../interfaces/sync-form-ordem.interface.ts | 0 .../transacao-view.entity.ts | 0 .../transacao-view.module.ts | 0 .../transacao-view.repository.ts | 0 .../transacao-view.service.ts | 0 src/utils/test-utils.ts | 2 +- 18 files changed, 18 insertions(+), 18 deletions(-) rename src/{transacao-bq => transacao-view}/interfaces/previous-days-args.ts (100%) rename src/{transacao-bq => transacao-view}/interfaces/sync-form-ordem.interface.ts (100%) rename src/{transacao-bq => transacao-view}/transacao-view.entity.ts (100%) rename src/{transacao-bq => transacao-view}/transacao-view.module.ts (100%) rename src/{transacao-bq => transacao-view}/transacao-view.repository.ts (100%) rename src/{transacao-bq => transacao-view}/transacao-view.service.ts (100%) diff --git a/src/app.module.ts b/src/app.module.ts index 7924f152..b8600042 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -43,8 +43,8 @@ import { SftpModule } from './sftp/sftp.module'; import { TestModule } from './test/test.module'; import { TicketRevenuesModule } from './ticket-revenues/ticket-revenues.module'; import { UsersModule } from './users/users.module'; -import { TransacaoViewService } from './transacao-bq/transacao-view.service'; -import { TransacaoViewModule } from './transacao-bq/transacao-view.module'; +import { TransacaoViewService } from './transacao-view/transacao-view.service'; +import { TransacaoViewModule } from './transacao-view/transacao-view.module'; import { AppLoggerMiddleware } from './utils/logger-middleware'; import { RelatorioModule } from './relatorio/relatorio.module'; import { AppService } from './app.service'; diff --git a/src/bank-statements/bank-statements.module.ts b/src/bank-statements/bank-statements.module.ts index 6de84436..3ea2e8d6 100644 --- a/src/bank-statements/bank-statements.module.ts +++ b/src/bank-statements/bank-statements.module.ts @@ -5,7 +5,7 @@ import { BankStatementsRepositoryService } from './bank-statements.repository'; import { BankStatementsController } from './bank-statements.controller'; import { BankStatementsService } from './bank-statements.service'; import { CnabModule } from 'src/cnab/cnab.module'; -import { TransacaoViewModule } from 'src/transacao-bq/transacao-view.module'; +import { TransacaoViewModule } from 'src/transacao-view/transacao-view.module'; @Module({ providers: [BankStatementsService, BankStatementsRepositoryService], diff --git a/src/bank-statements/bank-statements.repository.ts b/src/bank-statements/bank-statements.repository.ts index f8387d32..f1779611 100644 --- a/src/bank-statements/bank-statements.repository.ts +++ b/src/bank-statements/bank-statements.repository.ts @@ -3,7 +3,7 @@ import { endOfDay, isFriday, nextFriday, nextThursday, startOfDay, subDays } fro import { DetalheA } from 'src/cnab/entity/pagamento/detalhe-a.entity'; import { ArquivoPublicacaoService } from 'src/cnab/service/arquivo-publicacao.service'; import { DetalheAService } from 'src/cnab/service/pagamento/detalhe-a.service'; -import { TransacaoViewService } from 'src/transacao-bq/transacao-view.service'; +import { TransacaoViewService } from 'src/transacao-view/transacao-view.service'; import { User } from 'src/users/entities/user.entity'; import { formatDateYMD } from 'src/utils/date-utils'; import { TimeIntervalEnum } from 'src/utils/enums/time-interval.enum'; diff --git a/src/cnab/cnab.module.ts b/src/cnab/cnab.module.ts index b54bf946..eacd3a31 100644 --- a/src/cnab/cnab.module.ts +++ b/src/cnab/cnab.module.ts @@ -6,7 +6,7 @@ import { PagamentosPendentesRepository } from 'src/cnab/repository/pagamento/pag import { LancamentoModule } from 'src/lancamento/lancamento.module'; import { SettingsModule } from 'src/settings/settings.module'; import { SftpModule } from 'src/sftp/sftp.module'; -import { TransacaoViewModule } from 'src/transacao-bq/transacao-view.module'; +import { TransacaoViewModule } from 'src/transacao-view/transacao-view.module'; import { UsersModule } from 'src/users/users.module'; import { CnabController } from './cnab.controller'; import { CnabService } from './cnab.service'; diff --git a/src/cnab/cnab.service.ts b/src/cnab/cnab.service.ts index a18d98aa..04bfff8a 100644 --- a/src/cnab/cnab.service.ts +++ b/src/cnab/cnab.service.ts @@ -7,8 +7,8 @@ import { Lancamento } from 'src/lancamento/entities/lancamento.entity'; import { LancamentoService } from 'src/lancamento/lancamento.service'; import { SftpBackupFolder } from 'src/sftp/enums/sftp-backup-folder.enum'; import { SftpService } from 'src/sftp/sftp.service'; -import { TransacaoView } from 'src/transacao-bq/transacao-view.entity'; -import { TransacaoViewService } from 'src/transacao-bq/transacao-view.service'; +import { TransacaoView } from 'src/transacao-view/transacao-view.entity'; +import { TransacaoViewService } from 'src/transacao-view/transacao-view.service'; import { UsersService } from 'src/users/users.service'; import { InjectDataSource } from '@nestjs/typeorm'; @@ -55,7 +55,7 @@ import { formatErrMsg } from 'src/utils/log-utils'; import e from 'express'; import { getChunks } from 'src/utils/array-utils'; import { isContent, isNotContent } from 'src/utils/type-utils'; -import { ISyncOrdemPgto } from 'src/transacao-bq/interfaces/sync-form-ordem.interface'; +import { ISyncOrdemPgto } from 'src/transacao-view/interfaces/sync-form-ordem.interface'; /** * User cases for CNAB and Payments diff --git a/src/cnab/service/pagamento/remessa-retorno.service.ts b/src/cnab/service/pagamento/remessa-retorno.service.ts index 7ba8c919..3501b032 100644 --- a/src/cnab/service/pagamento/remessa-retorno.service.ts +++ b/src/cnab/service/pagamento/remessa-retorno.service.ts @@ -18,7 +18,7 @@ import { CnabTrailerArquivo104 } from 'src/cnab/interfaces/cnab-240/104/cnab-tra import { CnabFile104Pgto } from 'src/cnab/interfaces/cnab-240/104/pagamento/cnab-file-104-pgto.interface'; import { Cnab104PgtoTemplates } from 'src/cnab/templates/cnab-240/104/pagamento/cnab-104-pgto-templates.const'; import { getCnabFieldConverted } from 'src/cnab/utils/cnab/cnab-field-utils'; -import { TransacaoViewService } from 'src/transacao-bq/transacao-view.service'; +import { TransacaoViewService } from 'src/transacao-view/transacao-view.service'; import { CustomLogger } from 'src/utils/custom-logger'; import { asNumber, asString } from 'src/utils/pipe-utils'; import { Between, DataSource, DeepPartial, IsNull, Not, QueryRunner } from 'typeorm'; diff --git a/src/cnab/test/cnab-service/cnab.service.spec.ts b/src/cnab/test/cnab-service/cnab.service.spec.ts index ed4c15aa..cbf5ed32 100644 --- a/src/cnab/test/cnab-service/cnab.service.spec.ts +++ b/src/cnab/test/cnab-service/cnab.service.spec.ts @@ -17,9 +17,9 @@ // import { UsersService } from 'src/users/users.service'; // import { DeepPartial } from 'typeorm'; // import { CnabService } from '../../cnab.service'; -// import { TransacaoViewService } from 'src/transacao-bq/transacao-view.service'; +// import { TransacaoViewService } from 'src/transacao-view/transacao-view.service'; // import { TestUtils } from '../../../utils/test-utils'; -// import { TransacaoView } from 'src/transacao-bq/transacao-view.entity'; +// import { TransacaoView } from 'src/transacao-view/transacao-view.entity'; // import { BigqueryTransacaoService } from 'src/bigquery/services/bigquery-transacao.service'; // import { BigqueryTransacao } from 'src/bigquery/entities/transacao.bigquery-entity'; diff --git a/src/database/seeds/test/cnab/cnab-update-transacao-view-bq/cnab-update-transacao-view-bq-seed.module.ts b/src/database/seeds/test/cnab/cnab-update-transacao-view-bq/cnab-update-transacao-view-bq-seed.module.ts index 78a0a3e5..434bdd4b 100644 --- a/src/database/seeds/test/cnab/cnab-update-transacao-view-bq/cnab-update-transacao-view-bq-seed.module.ts +++ b/src/database/seeds/test/cnab/cnab-update-transacao-view-bq/cnab-update-transacao-view-bq-seed.module.ts @@ -10,7 +10,7 @@ // import { ItemTransacaoAgrupado } from 'src/cnab/entity/pagamento/item-transacao-agrupado.entity'; // import { Transacao } from 'src/cnab/entity/pagamento/transacao.entity'; // import { TransacaoAgrupado } from 'src/cnab/entity/pagamento/transacao-agrupado.entity'; -// import { TransacaoView } from 'src/transacao-bq/transacao-view.entity'; +// import { TransacaoView } from 'src/transacao-view/transacao-view.entity'; // import { ArquivoPublicacao } from 'src/cnab/entity/arquivo-publicacao.entity'; // import { ClienteFavorecido } from 'src/cnab/entity/cliente-favorecido.entity'; diff --git a/src/ticket-revenues/ticket-revenues-repository.ts b/src/ticket-revenues/ticket-revenues-repository.ts index b9d174e2..287e4cc0 100644 --- a/src/ticket-revenues/ticket-revenues-repository.ts +++ b/src/ticket-revenues/ticket-revenues-repository.ts @@ -1,7 +1,7 @@ import { Injectable, Logger } from '@nestjs/common'; import { endOfDay, isToday, startOfDay } from 'date-fns'; -import { TransacaoView } from 'src/transacao-bq/transacao-view.entity'; -import { TransacaoViewService } from 'src/transacao-bq/transacao-view.service'; +import { TransacaoView } from 'src/transacao-view/transacao-view.entity'; +import { TransacaoViewService } from 'src/transacao-view/transacao-view.service'; import { getPagination } from 'src/utils/get-pagination'; import { getPaymentDates } from 'src/utils/payment-date-utils'; import { PaginationOptions } from 'src/utils/types/pagination-options'; diff --git a/src/ticket-revenues/ticket-revenues.module.ts b/src/ticket-revenues/ticket-revenues.module.ts index 77e5a933..56a08d0d 100644 --- a/src/ticket-revenues/ticket-revenues.module.ts +++ b/src/ticket-revenues/ticket-revenues.module.ts @@ -2,7 +2,7 @@ import { Module } from '@nestjs/common'; import { BigqueryModule } from 'src/bigquery/bigquery.module'; import { CnabModule } from 'src/cnab/cnab.module'; import { SettingsModule } from 'src/settings/settings.module'; -import { TransacaoViewModule } from 'src/transacao-bq/transacao-view.module'; +import { TransacaoViewModule } from 'src/transacao-view/transacao-view.module'; import { UsersModule } from 'src/users/users.module'; import { TicketRevenuesRepositoryService } from './ticket-revenues-repository'; import { TicketRevenuesController } from './ticket-revenues.controller'; diff --git a/src/ticket-revenues/ticket-revenues.service.ts b/src/ticket-revenues/ticket-revenues.service.ts index be711b01..b5a405ba 100644 --- a/src/ticket-revenues/ticket-revenues.service.ts +++ b/src/ticket-revenues/ticket-revenues.service.ts @@ -3,8 +3,8 @@ import { endOfDay, isSameDay, isToday, nextFriday, startOfDay, subDays } from 'd import { DetalheA } from 'src/cnab/entity/pagamento/detalhe-a.entity'; import { ArquivoPublicacaoService } from 'src/cnab/service/arquivo-publicacao.service'; import { DetalheAService } from 'src/cnab/service/pagamento/detalhe-a.service'; -import { TransacaoView } from 'src/transacao-bq/transacao-view.entity'; -import { TransacaoViewService } from 'src/transacao-bq/transacao-view.service'; +import { TransacaoView } from 'src/transacao-view/transacao-view.entity'; +import { TransacaoViewService } from 'src/transacao-view/transacao-view.service'; import { User } from 'src/users/entities/user.entity'; import { UsersService } from 'src/users/users.service'; import { CustomLogger } from 'src/utils/custom-logger'; diff --git a/src/transacao-bq/interfaces/previous-days-args.ts b/src/transacao-view/interfaces/previous-days-args.ts similarity index 100% rename from src/transacao-bq/interfaces/previous-days-args.ts rename to src/transacao-view/interfaces/previous-days-args.ts diff --git a/src/transacao-bq/interfaces/sync-form-ordem.interface.ts b/src/transacao-view/interfaces/sync-form-ordem.interface.ts similarity index 100% rename from src/transacao-bq/interfaces/sync-form-ordem.interface.ts rename to src/transacao-view/interfaces/sync-form-ordem.interface.ts diff --git a/src/transacao-bq/transacao-view.entity.ts b/src/transacao-view/transacao-view.entity.ts similarity index 100% rename from src/transacao-bq/transacao-view.entity.ts rename to src/transacao-view/transacao-view.entity.ts diff --git a/src/transacao-bq/transacao-view.module.ts b/src/transacao-view/transacao-view.module.ts similarity index 100% rename from src/transacao-bq/transacao-view.module.ts rename to src/transacao-view/transacao-view.module.ts diff --git a/src/transacao-bq/transacao-view.repository.ts b/src/transacao-view/transacao-view.repository.ts similarity index 100% rename from src/transacao-bq/transacao-view.repository.ts rename to src/transacao-view/transacao-view.repository.ts diff --git a/src/transacao-bq/transacao-view.service.ts b/src/transacao-view/transacao-view.service.ts similarity index 100% rename from src/transacao-bq/transacao-view.service.ts rename to src/transacao-view/transacao-view.service.ts diff --git a/src/utils/test-utils.ts b/src/utils/test-utils.ts index f76ee024..ff35a0f1 100644 --- a/src/utils/test-utils.ts +++ b/src/utils/test-utils.ts @@ -2,7 +2,7 @@ import { INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { TypeOrmModule, TypeOrmModuleOptions } from '@nestjs/typeorm'; import { DataSource, ObjectLiteral } from 'typeorm'; -import { TransacaoView } from '../../src/transacao-bq/transacao-view.entity'; +import { TransacaoView } from '../../src/transacao-view/transacao-view.entity'; export class TestUtils { private app: INestApplication; From a2add59ca0b520eafb839d4e097b55bb1fcdd66f Mon Sep 17 00:00:00 2001 From: Raphael Rivas Date: Mon, 2 Sep 2024 09:55:48 -0300 Subject: [PATCH 13/29] refactor: move tipo-favorecido --- .../dtos/bigquery-ordem-pagamento.dto.ts | 23 ++++--------------- ...bigquery-find-ordem-pagamento.interface.ts | 2 +- src/cnab/dto/cliente-favorecido.dto.ts | 2 +- .../enums}/tipo-favorecido.enum.ts | 0 .../service/cliente-favorecido.service.ts | 9 ++++---- .../cliente-favorecido-seed-data.ts | 2 +- 6 files changed, 12 insertions(+), 26 deletions(-) rename src/{tipo-favorecido => cnab/enums}/tipo-favorecido.enum.ts (100%) diff --git a/src/bigquery/dtos/bigquery-ordem-pagamento.dto.ts b/src/bigquery/dtos/bigquery-ordem-pagamento.dto.ts index 9adad5ac..35434693 100644 --- a/src/bigquery/dtos/bigquery-ordem-pagamento.dto.ts +++ b/src/bigquery/dtos/bigquery-ordem-pagamento.dto.ts @@ -1,13 +1,6 @@ -import { - IsDateString, - IsNotEmpty, - IsNumber, - IsNumberString, - IsString, - ValidateIf, -} from 'class-validator'; +import { IsDateString, IsNotEmpty, IsNumber, IsNumberString, IsString, ValidateIf } from 'class-validator'; import { isSameDay, nextFriday } from 'date-fns'; -import { TipoFavorecidoEnum } from 'src/tipo-favorecido/tipo-favorecido.enum'; +import { TipoFavorecidoEnum } from 'src/cnab/enums/tipo-favorecido.enum'; import { DeepPartial } from 'typeorm'; /** @@ -158,16 +151,8 @@ export class BigqueryOrdemPagamentoDTO { tipoFavorecido: TipoFavorecidoEnum | null; - public static findAgrupado( - ordemAgs: BigqueryOrdemPagamentoDTO[], - ordem: BigqueryOrdemPagamentoDTO, - newDataOrdem = nextFriday(new Date()), - ) { - const filtered = ordemAgs.filter( - (i) => - isSameDay(new Date(i.dataOrdem), newDataOrdem) && - i.idConsorcio === ordem.idConsorcio, - )[0]; + public static findAgrupado(ordemAgs: BigqueryOrdemPagamentoDTO[], ordem: BigqueryOrdemPagamentoDTO, newDataOrdem = nextFriday(new Date())) { + const filtered = ordemAgs.filter((i) => isSameDay(new Date(i.dataOrdem), newDataOrdem) && i.idConsorcio === ordem.idConsorcio)[0]; return filtered ? filtered : null; } } diff --git a/src/bigquery/interfaces/bigquery-find-ordem-pagamento.interface.ts b/src/bigquery/interfaces/bigquery-find-ordem-pagamento.interface.ts index 5ca6d1c5..c168c6ce 100644 --- a/src/bigquery/interfaces/bigquery-find-ordem-pagamento.interface.ts +++ b/src/bigquery/interfaces/bigquery-find-ordem-pagamento.interface.ts @@ -1,4 +1,4 @@ -import { TipoFavorecidoEnum } from 'src/tipo-favorecido/tipo-favorecido.enum'; +import { TipoFavorecidoEnum } from 'src/cnab/enums/tipo-favorecido.enum'; export interface IBigqueryFindOrdemPagamento { operadorCpfs?: string[]; diff --git a/src/cnab/dto/cliente-favorecido.dto.ts b/src/cnab/dto/cliente-favorecido.dto.ts index a161beda..0d0b9067 100644 --- a/src/cnab/dto/cliente-favorecido.dto.ts +++ b/src/cnab/dto/cliente-favorecido.dto.ts @@ -1,5 +1,5 @@ import { IsNotEmpty, ValidateIf } from 'class-validator'; -import { TipoFavorecidoEnum } from 'src/tipo-favorecido/tipo-favorecido.enum'; +import { TipoFavorecidoEnum } from 'src/cnab/enums/tipo-favorecido.enum'; function isCreate(object: SaveClienteFavorecidoDTO): boolean { return object.id === undefined; diff --git a/src/tipo-favorecido/tipo-favorecido.enum.ts b/src/cnab/enums/tipo-favorecido.enum.ts similarity index 100% rename from src/tipo-favorecido/tipo-favorecido.enum.ts rename to src/cnab/enums/tipo-favorecido.enum.ts diff --git a/src/cnab/service/cliente-favorecido.service.ts b/src/cnab/service/cliente-favorecido.service.ts index d9bcb95e..42d38e3d 100644 --- a/src/cnab/service/cliente-favorecido.service.ts +++ b/src/cnab/service/cliente-favorecido.service.ts @@ -1,22 +1,23 @@ import { HttpStatus, Injectable, Logger } from '@nestjs/common'; import { BigqueryOrdemPagamentoDTO } from 'src/bigquery/dtos/bigquery-ordem-pagamento.dto'; import { Lancamento } from 'src/lancamento/entities/lancamento.entity'; -import { TipoFavorecidoEnum } from 'src/tipo-favorecido/tipo-favorecido.enum'; +import { TipoFavorecidoEnum } from 'src/cnab/enums/tipo-favorecido.enum'; import { User } from 'src/users/entities/user.entity'; +import { CustomLogger } from 'src/utils/custom-logger'; import { CommonHttpException } from 'src/utils/http-exception/common-http-exception'; import { asString } from 'src/utils/pipe-utils'; import { parseStringUpperUnaccent } from 'src/utils/string-utils'; import { EntityCondition } from 'src/utils/types/entity-condition.type'; import { validateDTO } from 'src/utils/validation-utils'; -import { FindManyOptions, FindOneOptions, In } from 'typeorm'; +import { FindOneOptions, In } from 'typeorm'; import { SaveClienteFavorecidoDTO } from '../dto/cliente-favorecido.dto'; import { ClienteFavorecido } from '../entity/cliente-favorecido.entity'; -import { ClienteFavorecidoRepository, IClienteFavorecidoRawWhere } from '../repository/cliente-favorecido.repository'; import { IClienteFavorecidoFindBy } from '../interfaces/cliente-favorecido-find-by.interface'; +import { ClienteFavorecidoRepository, IClienteFavorecidoRawWhere } from '../repository/cliente-favorecido.repository'; @Injectable() export class ClienteFavorecidoService { - private logger: Logger = new Logger('ClienteFavorecidoService', { timestamp: true }); + private logger: Logger = new CustomLogger('ClienteFavorecidoService', { timestamp: true }); constructor(private clienteFavorecidoRepository: ClienteFavorecidoRepository) {} diff --git a/src/database/seeds/cliente-favorecido/cliente-favorecido-seed-data.ts b/src/database/seeds/cliente-favorecido/cliente-favorecido-seed-data.ts index 3b65abf2..0193a5fb 100644 --- a/src/database/seeds/cliente-favorecido/cliente-favorecido-seed-data.ts +++ b/src/database/seeds/cliente-favorecido/cliente-favorecido-seed-data.ts @@ -1,5 +1,5 @@ import { ClienteFavorecido } from 'src/cnab/entity/cliente-favorecido.entity'; -import { TipoFavorecidoEnum } from 'src/tipo-favorecido/tipo-favorecido.enum'; +import { TipoFavorecidoEnum } from 'src/cnab/enums/tipo-favorecido.enum'; /** * Favorecidos from From e3d95de3ea3ae1338c33957f849b228cd1b06018 Mon Sep 17 00:00:00 2001 From: Raphael Rivas Date: Tue, 3 Sep 2024 16:15:37 -0300 Subject: [PATCH 14/29] fix: autor lancamento --- src/lancamento/entities/lancamento.entity.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lancamento/entities/lancamento.entity.ts b/src/lancamento/entities/lancamento.entity.ts index 61f6df19..ae643aec 100644 --- a/src/lancamento/entities/lancamento.entity.ts +++ b/src/lancamento/entities/lancamento.entity.ts @@ -115,8 +115,7 @@ export class Lancamento extends EntityHelper implements ILancamento { /** O autor mais recente da criação/modificação/autorização do Lançamento */ @ManyToOne(() => User, { eager: true }) @JoinColumn({ foreignKeyConstraintName: 'FK_Lancamento_autor_ManyToOne' }) - @Expose({ name: 'userId' }) - @Transform(({ value }) => (value as User).id) + @Transform(({ value }) => ({ id: (value as User).id, fullName: (value as User).fullName } as DeepPartial)) autor: User; @ManyToOne(() => ClienteFavorecido, { eager: true }) From 5da4866346fcddbf4e6f19223f84608b86d49061 Mon Sep 17 00:00:00 2001 From: Raphael Rivas Date: Thu, 5 Sep 2024 10:57:20 -0300 Subject: [PATCH 15/29] WIP: lancamento remessa --- .../dtos/bigquery-ordem-pagamento.dto.ts | 2 +- src/cnab/cnab-manutencao.controller.ts | 51 +++- src/cnab/cnab.service.ts | 276 ++++++++---------- src/cnab/const/bigquery-consorcio.const.ts | 13 + src/cnab/dto/pagamento/ordem-pagamento.dto.ts | 106 +++++++ src/cnab/entity/arquivo-publicacao.entity.ts | 1 + .../item-transacao-agrupado.entity.ts | 52 ++-- .../entity/pagamento/item-transacao.entity.ts | 50 ++-- .../pagamento/transacao-agrupado.entity.ts | 19 +- src/cnab/entity/pagamento/transacao.entity.ts | 13 +- .../pagamento/item-transacao.service.ts | 41 --- .../service/pagamento/transacao.service.ts | 2 +- src/lancamento/entities/lancamento.entity.ts | 19 +- src/lancamento/lancamento.service.ts | 10 +- 14 files changed, 403 insertions(+), 252 deletions(-) create mode 100644 src/cnab/const/bigquery-consorcio.const.ts create mode 100644 src/cnab/dto/pagamento/ordem-pagamento.dto.ts diff --git a/src/bigquery/dtos/bigquery-ordem-pagamento.dto.ts b/src/bigquery/dtos/bigquery-ordem-pagamento.dto.ts index 35434693..2fa17e53 100644 --- a/src/bigquery/dtos/bigquery-ordem-pagamento.dto.ts +++ b/src/bigquery/dtos/bigquery-ordem-pagamento.dto.ts @@ -32,7 +32,7 @@ export class BigqueryOrdemPagamentoDTO { */ @ValidateIf((o, v) => v !== null) @IsDateString() - dataPagamento: string | null; + // dataPagamento: string | null; /** * Id de cadastro.consorcios diff --git a/src/cnab/cnab-manutencao.controller.ts b/src/cnab/cnab-manutencao.controller.ts index 4ff60e66..ee849664 100644 --- a/src/cnab/cnab-manutencao.controller.ts +++ b/src/cnab/cnab-manutencao.controller.ts @@ -21,7 +21,52 @@ export class CnabManutencaoController { constructor(private readonly cnabService: CnabService) {} - @Get('generateRemessa') + @Get('generateRemessaLancamento') + @HttpCode(HttpStatus.OK) + @UseGuards(AuthGuard('jwt'), RolesGuard) + @Roles(RoleEnum.master) + @ApiOperation({ description: 'Feito para manutenção pelos admins.\n\nExecuta a geração e envio de remessa - que normalmente é feita via cronjob.' }) + @ApiQuery({ name: 'dataOrdemInicial', type: String, required: false, description: ApiDescription({ _: 'Data da Ordem de Pagamento Inicial - salvar transações', example: '2024-07-15' }) }) + @ApiQuery({ name: 'dataOrdemFinal', type: String, required: false, description: ApiDescription({ _: 'Data da Ordem de Pagamento Final - salvar transações', example: '2024-07-16' }) }) + @ApiQuery({ name: 'dataPagamento', type: String, required: false, description: ApiDescription({ _: 'Data de pagamento', default: 'O dia de hoje' }) }) + @ApiQuery({ name: 'isConference', type: Boolean, required: true, description: 'Conferencia - Se o remessa será gerado numa tabela de teste.', example: true }) + @ApiQuery({ name: 'isCancelamento', type: Boolean, required: true, description: 'Cancelamento', example: false }) + @ApiQuery({ name: 'nsaInicial', type: Number, required: false, description: ApiDescription({ default: 'O NSA atual' }) }) + @ApiQuery({ name: 'nsaFinal', type: Number, required: false, description: ApiDescription({ default: 'nsaInicial' }) }) + @ApiQuery({ name: 'dataCancelamento', type: String, required: false, description: ApiDescription({ _: 'Data de vencimento da transação a ser cancelada (DetalheA).', 'Required if': 'isCancelamento = true' }), example: '2024-07-16' }) + @ApiBearerAuth() + async getGenerateRemessaLancamento( + @Query('dataOrdemInicial', new ParseDatePipe({ transform: true, optional: true })) _dataOrdemInicial: any, // Date + @Query('dataOrdemFinal', new ParseDatePipe({ transform: true, optional: true })) _dataOrdemFinal: any, // Date + @Query('dataPagamento', new ParseDatePipe({ transform: true, optional: true })) _dataPagamento: any, // Date | undefined + @Query('isConference') isConference: boolean, + @Query('isCancelamento') isCancelamento: boolean, + @Query('nsaInicial', new ParseNumberPipe({ min: 1, optional: true })) nsaInicial: number | undefined, + @Query('nsaFinal', new ParseNumberPipe({ min: 1, optional: true })) nsaFinal: number | undefined, + @Query('dataCancelamento', new ParseDatePipe({ transform: true, optional: true })) _dataCancelamento: any, // Date | undefined + ) { + const dataOrdemInicial = _dataOrdemInicial as Date | undefined; + const dataOrdemFinal = _dataOrdemFinal as Date | undefined; + const dataPgto = _dataPagamento || new Date() as Date; + const dataCancelamento = _dataCancelamento as Date | undefined; + + if (isCancelamento && !dataCancelamento) { + throw new BadRequestException('dataCancelamento é obrigatório se isCancelamento = true'); + } + + return await this.cnabService.getGenerateRemessaLancamento({ + dataOrdemInicial, + dataOrdemFinal, + dataPgto, + isConference, + isCancelamento, + nsaInicial, + nsaFinal, + dataCancelamento, + }); + } + + @Get('generateRemessaJae') @HttpCode(HttpStatus.OK) @UseGuards(AuthGuard('jwt'), RolesGuard) @Roles(RoleEnum.master) @@ -37,7 +82,7 @@ export class CnabManutencaoController { @ApiQuery({ name: 'nsaFinal', description: ApiDescription({ default: 'nsaInicial' }), required: false, type: Number }) @ApiQuery({ name: 'dataCancelamento', description: ApiDescription({ _: 'Data de vencimento da transação a ser cancelada (DetalheA).', 'Required if': 'isCancelamento = true' }), required: false, type: String, example: '2024-07-16' }) @ApiBearerAuth() - async getGenerateRemessa( + async getGenerateRemessaJae( @Query('dataOrdemInicial', new ParseDatePipe({ transform: true })) _dataOrdemInicial: any, // Date @Query('dataOrdemFinal', new ParseDatePipe({ transform: true })) _dataOrdemFinal: any, // Date @Query('diasAnterioresOrdem', new ParseNumberPipe({ min: 0, defaultValue: 0 })) diasAnteriores: number, @@ -58,7 +103,7 @@ export class CnabManutencaoController { throw new BadRequestException('dataCancelamento é obrigatório se isCancelamento = true'); } - return await this.cnabService.getGenerateRemessa({ + return await this.cnabService.getGenerateRemessaJae({ dataOrdemInicial, dataOrdemFinal, diasAnteriores, diff --git a/src/cnab/cnab.service.ts b/src/cnab/cnab.service.ts index a49ca37e..79c7ab65 100644 --- a/src/cnab/cnab.service.ts +++ b/src/cnab/cnab.service.ts @@ -1,5 +1,5 @@ -import { Injectable } from '@nestjs/common'; -import { endOfDay, isFriday, nextFriday, nextThursday, startOfDay, subDays } from 'date-fns'; +import { Injectable, NotImplementedException } from '@nestjs/common'; +import { endOfDay, isFriday, nextFriday, startOfDay, subDays } from 'date-fns'; import { BigqueryOrdemPagamentoDTO } from 'src/bigquery/dtos/bigquery-ordem-pagamento.dto'; import { BigqueryOrdemPagamentoService } from 'src/bigquery/services/bigquery-ordem-pagamento.service'; import { BigqueryTransacaoService } from 'src/bigquery/services/bigquery-transacao.service'; @@ -15,11 +15,15 @@ import { InjectDataSource } from '@nestjs/typeorm'; import { BigqueryTransacao } from 'src/bigquery/entities/transacao.bigquery-entity'; import { cnabSettings } from 'src/settings/cnab.settings'; import { SettingsService } from 'src/settings/settings.service'; +import { ISyncOrdemPgto } from 'src/transacao-view/interfaces/sync-form-ordem.interface'; +import { getChunks } from 'src/utils/array-utils'; import { completeCPFCharacter } from 'src/utils/cpf-cnpj'; import { CustomLogger } from 'src/utils/custom-logger'; import { formatDateInterval, yearMonthDayToDate } from 'src/utils/date-utils'; +import { CommonHttpException } from 'src/utils/http-exception/common-http-exception'; +import { formatErrMsg } from 'src/utils/log-utils'; import { asNumber } from 'src/utils/pipe-utils'; -import { Between, DataSource, DeepPartial, In, IsNull, Not, QueryRunner } from 'typeorm'; +import { Between, DataSource, DeepPartial, QueryRunner } from 'typeorm'; import { HeaderArquivoDTO } from './dto/pagamento/header-arquivo.dto'; import { HeaderLoteDTO } from './dto/pagamento/header-lote.dto'; import { ClienteFavorecido } from './entity/cliente-favorecido.entity'; @@ -50,12 +54,8 @@ import { RemessaRetornoService } from './service/pagamento/remessa-retorno.servi import { TransacaoAgrupadoService } from './service/pagamento/transacao-agrupado.service'; import { TransacaoService } from './service/pagamento/transacao.service'; import { parseCnab240Extrato, parseCnab240Pagamento, stringifyCnab104File } from './utils/cnab/cnab-104-utils'; -import { CommonHttpException } from 'src/utils/http-exception/common-http-exception'; -import { formatErrMsg } from 'src/utils/log-utils'; -import e from 'express'; -import { getChunks } from 'src/utils/array-utils'; -import { isContent, isNotContent } from 'src/utils/type-utils'; -import { ISyncOrdemPgto } from 'src/transacao-view/interfaces/sync-form-ordem.interface'; +import { OrdemPagamentoDto } from './dto/pagamento/ordem-pagamento.dto'; +import { AllPagadorDict } from './interfaces/pagamento/all-pagador-dict.interface'; /** * User cases for CNAB and Payments @@ -90,7 +90,7 @@ export class CnabService { private settingsService: SettingsService, ) {} - async getGenerateRemessa(args: { + async getGenerateRemessaJae(args: { dataOrdemInicial: Date; // dataOrdemFinal: Date; diasAnteriores: number; @@ -102,7 +102,7 @@ export class CnabService { nsaFinal: number | undefined; dataCancelamento: Date | undefined; }) { - const METHOD = 'getGenerateRemessa'; + const METHOD = 'getGenerateRemessaJae'; const { consorcio, dataCancelamento, dataOrdemFinal, dataOrdemInicial, dataPgto, diasAnteriores, isCancelamento, isConference, nsaFinal, nsaInicial } = args; const duration = { saveTransacoesJae: '', generateRemessa: '', sendRemessa: '', total: '' }; const startDate = new Date(); @@ -142,6 +142,56 @@ export class CnabService { }; } + async getGenerateRemessaLancamento(args: { + dataOrdemInicial?: Date; // + dataOrdemFinal?: Date; + dataPgto: Date | undefined; + isConference: boolean; + isCancelamento: boolean; + nsaInicial: number | undefined; + nsaFinal: number | undefined; + dataCancelamento: Date | undefined; + }) { + const METHOD = 'getGenerateRemessaLancamento'; + const { dataCancelamento, dataOrdemFinal, dataOrdemInicial, dataPgto, isCancelamento, isConference, nsaFinal, nsaInicial } = args; + const duration = { saveTransacoesJae: '', generateRemessa: '', sendRemessa: '', total: '' }; + const startDate = new Date(); + let now = new Date(); + this.logger.log('Tarefa iniciada', METHOD); + + this.logger.log('saveTransacoesJae iniciado'); + await this.saveTransacoesLancamento(dataOrdemInicial, dataOrdemFinal); + duration.saveTransacoesJae = formatDateInterval(new Date(), now); + now = new Date(); + this.logger.log(`saveTransacoesJae finalizado - ${duration.saveTransacoesJae}`); + + this.logger.log('generateRemessa started'); + const listCnab = await this.generateRemessa({ + tipo: PagadorContaEnum.CETT, // + dataPgto, + isConference, + isCancelamento, + nsaInicial, + nsaFinal, + dataCancelamento, + }); + duration.generateRemessa = formatDateInterval(new Date(), now); + now = new Date(); + this.logger.log(`generateRemessa finalizado - ${duration.generateRemessa}`); + + this.logger.log('sendRemessa started'); + await this.sendRemessa(listCnab); + duration.sendRemessa = formatDateInterval(new Date(), now); + this.logger.log(`sendRemessa finalizado - ${duration.sendRemessa}`); + + duration.total = formatDateInterval(new Date(), startDate); + this.logger.log(`Tarefa finalizada - ${duration.total}`); + return { + duration, + cnabs: listCnab, + }; + } + /** * Atualiza valores nulos com o Bigquery */ @@ -189,8 +239,6 @@ export class CnabService { return { removed, duration }; } - // #region saveTransacoesJae - /** * Obtém dados de OrdemPagamento e salva em ItemTransacao e afins, para gerar remessa. * @@ -202,7 +250,7 @@ export class CnabService { const dataOrdemInicialDate = startOfDay(new Date(dataOrdemIncial)); const dataOrdemFinalDate = endOfDay(new Date(dataOrdemFinal)); await this.updateAllFavorecidosFromUsers(); - let ordens = await this.bigqueryOrdemPagamentoService.getFromWeek(dataOrdemInicialDate, dataOrdemFinalDate, daysBefore); + const ordens = await this.bigqueryOrdemPagamentoService.getFromWeek(dataOrdemInicialDate, dataOrdemFinalDate, daysBefore); let ordensFilter: BigqueryOrdemPagamentoDTO[]; if (consorcio.trim() === 'Empresa') { ordensFilter = ordens.filter((ordem) => ordem.consorcio.trim() !== 'VLT' && ordem.consorcio.trim() !== 'STPC' && ordem.consorcio.trim() !== 'STPL'); @@ -211,7 +259,13 @@ export class CnabService { } else { ordensFilter = ordens.filter((ordem) => ordem.consorcio === consorcio.trim()); } - await this.saveOrdens(ordensFilter); + const ordemDtos = ordensFilter.map((o) => OrdemPagamentoDto.fromBigqueryOrdem(o)); + await this.saveOrdens(ordemDtos, 'contaBilhetagem'); + } + + private async updateAllFavorecidosFromUsers() { + const allUsers = await this.usersService.findManyRegisteredUsers(); + await this.clienteFavorecidoService.updateAllFromUsers(allUsers); } /** @@ -219,7 +273,7 @@ export class CnabService { */ async updateTransacaoViewBigquery(dataOrdemIncial: Date, dataOrdemFinal: Date, daysBack = 0, consorcio: string = 'Todos', idTransacao: string[] = []) { const METHOD = 'updateTransacaoViewBigquery'; - const trs = await this.getTransacoesBQ(dataOrdemIncial, dataOrdemFinal, daysBack, consorcio); + const trs = await this.findBigqueryTransacao(dataOrdemIncial, dataOrdemFinal, daysBack, consorcio); const queryRunner = this.dataSource.createQueryRunner(); await queryRunner.connect(); try { @@ -267,7 +321,7 @@ export class CnabService { return response; } - async getTransacoesBQ(dataOrdemIncial: Date, dataOrdemFinal: Date, daysBack = 0, consorcio: string) { + async findBigqueryTransacao(dataOrdemIncial: Date, dataOrdemFinal: Date, daysBack = 0, consorcio: string): Promise { const transacoesBq = await this.bigqueryTransacaoService.getFromWeek(startOfDay(dataOrdemIncial), endOfDay(dataOrdemFinal), daysBack); let trs = transacoesBq; if (consorcio === 'Van') { @@ -278,26 +332,13 @@ export class CnabService { return trs; } - public async getTransacoesViewOrdem(dataCaptura: Date, itemAg: ItemTransacaoAgrupado, clienteFavorecido: ClienteFavorecido) { - const dataVencimento = nextFriday(dataCaptura); + // #region saveOrdens - let daysbefore = 9; - if (itemAg.nomeConsorcio === 'VLT') { - daysbefore = 2; - } - const trsDia = await this.getTransacoesViewWeek(subDays(startOfDay(dataVencimento), daysbefore), subDays(endOfDay(dataVencimento), 1)); - const trsOrdem = trsDia.filter((transacaoView) => transacaoView.idOperadora === itemAg.idOperadora && transacaoView.idConsorcio === itemAg.idConsorcio && transacaoView.operadoraCpfCnpj === clienteFavorecido.cpfCnpj); - return trsOrdem; - } - - /** - * Salvar Transacao / ItemTransacao e agrupados - */ - async saveOrdens(ordens: BigqueryOrdemPagamentoDTO[]) { - const pagador = (await this.pagadorService.getAllPagador()).contaBilhetagem; + async saveOrdens(ordens: OrdemPagamentoDto[], pagadorKey: keyof AllPagadorDict) { + const pagador = (await this.pagadorService.getAllPagador())[pagadorKey]; for (const ordem of ordens) { - const cpfCnpj = ordem.consorcioCnpj || ordem.operadoraCpfCnpj; + const cpfCnpj = ordem.favorecidoCpfCnpj; if (!cpfCnpj) { continue; } @@ -311,41 +352,25 @@ export class CnabService { } } - private async saveUpdateItemTransacaoAg(transacaoAg: TransacaoAgrupado, ordem: BigqueryOrdemPagamentoDTO, queryRunner: QueryRunner) { - let itemAg = await this.itemTransacaoAgService.findOne({ - where: { - transacaoAgrupado: { - id: transacaoAg.id, - status: { id: TransacaoStatusEnum.created }, - }, - ...(ordem.consorcio === 'STPC' || ordem.consorcio === 'STPL' ? { idOperadora: ordem.idOperadora } : { idConsorcio: ordem.idConsorcio }), - }, - }); - if (itemAg) { - itemAg.valor += asNumber(ordem.valorTotalTransacaoLiquido); - } else { - itemAg = this.convertItemTransacaoAgrupadoDTO(ordem, transacaoAg); - } - return await this.itemTransacaoAgService.save(itemAg, queryRunner); - } - - async saveAgrupamentos(ordem: BigqueryOrdemPagamentoDTO, pagador: Pagador, favorecido: ClienteFavorecido) { + /** + * A partir da Ordem, salva nas tabelas Transacao, ItemTransacao etc + */ + async saveAgrupamentos(ordem: OrdemPagamentoDto, pagador: Pagador, favorecido: ClienteFavorecido) { + const METHOD = 'saveAgrupamentos'; const queryRunner = this.dataSource.createQueryRunner(); await queryRunner.connect(); let itemAg: ItemTransacaoAgrupado | null = null; try { await queryRunner.startTransaction(); - this.logger.debug('Salva Agrupamento Consorcio: ' + ordem.consorcio); + this.logger.debug(`Salvando Agrupamento - ${JSON.stringify({ consorcio: ordem.consorcio, operadora: ordem.operadora, favorecidoCpfCnpj: ordem.favorecidoCpfCnpj })}`, METHOD); const dataOrdem = yearMonthDayToDate(ordem.dataOrdem); const fridayOrdem = nextFriday(startOfDay(dataOrdem)); - this.logger.debug('Inicia Consulta TransacaoAgrupado'); let transacaoAg = await this.transacaoAgService.findOne({ dataOrdem: fridayOrdem, pagador: { id: pagador.id }, status: { id: TransacaoStatusEnum.created }, }); - this.logger.debug(ordem.consorcio); if (transacaoAg) { itemAg = await this.saveUpdateItemTransacaoAg(transacaoAg, ordem, queryRunner); } else { @@ -364,6 +389,36 @@ export class CnabService { } } + /** + * Rergra de negócio: + * - Usa idConsorcio, idOperadora, consorcio para agrupar um itemAgrupado + */ + private async saveUpdateItemTransacaoAg(transacaoAg: TransacaoAgrupado, ordem: OrdemPagamentoDto, queryRunner: QueryRunner): Promise { + if (!ordem.consorcio.length && !ordem.operadora.length && !ordem.favorecidoCpfCnpj) { + throw new NotImplementedException(`Criar/atualizar ItemAgrupado necessita de consorcio/operadora ou favorecidoCpfCnpj`); + } + + let itemAg = await this.itemTransacaoAgService.findOne({ + where: { + transacaoAgrupado: { + id: transacaoAg.id, + status: { id: TransacaoStatusEnum.created }, + }, + ...(ordem.consorcio.length || ordem.operadora.length // Se for Jaé + ? ordem.consorcio === 'STPC' || ordem.consorcio === 'STPL' + ? { idOperadora: ordem.idOperadora } // agrupa por vanzeiro + : { idConsorcio: ordem.idConsorcio } // ou por empresa + : { itemTransacoes: { clienteFavorecido: { cpfCnpj: ordem.favorecidoCpfCnpj as string } } }), // Se for Lançamento, agrupa por favorecido + }, + }); + if (itemAg) { + itemAg.valor += asNumber(ordem.valorTotalTransacaoLiquido); + } else { + itemAg = ItemTransacaoAgrupado.fromOrdem(ordem, transacaoAg); + } + return await this.itemTransacaoAgService.save(itemAg, queryRunner); + } + async syncTransacaoViewOrdemPgto(args?: ISyncOrdemPgto) { this.logger.debug('Inicio Sync TransacaoView'); const queryRunner = this.dataSource.createQueryRunner(); @@ -376,118 +431,41 @@ export class CnabService { return { duration, count }; } - private async saveTransacaoAgrupado(ordem: BigqueryOrdemPagamentoDTO, pagador: Pagador) { - const transacaoAg = this.convertTransacaoAgrupadoDTO(ordem, pagador); + private async saveTransacaoAgrupado(ordem: OrdemPagamentoDto, pagador: Pagador) { + const transacaoAg = TransacaoAgrupado.fromOrdem(ordem, pagador); return await this.transacaoAgService.save(transacaoAg); } - private async saveItemTransacaoAgrupado(ordem: BigqueryOrdemPagamentoDTO, transacaoAg: TransacaoAgrupado, queryRunner: QueryRunner) { - const itemAg = this.convertItemTransacaoAgrupadoDTO(ordem, transacaoAg); + private async saveItemTransacaoAgrupado(ordem: OrdemPagamentoDto, transacaoAg: TransacaoAgrupado, queryRunner: QueryRunner) { + const itemAg = ItemTransacaoAgrupado.fromOrdem(ordem, transacaoAg); return await this.itemTransacaoAgService.save(itemAg, queryRunner); } /** - * Save or update Transacao. + * A partir do OrdemBigquery salva Transacao * - * Unique id: `idOrdemPagamento` + * Chave primária: TransacaoAgrupado, idOrdemBq, pagador */ - async saveTransacao(ordem: BigqueryOrdemPagamentoDTO, pagador: Pagador, transacaoAgId: number, queryRunner: QueryRunner): Promise { - const transacao = this.convertTransacao(ordem, pagador, transacaoAgId); + async saveTransacao(ordem: OrdemPagamentoDto, pagador: Pagador, transacaoAgId: number, queryRunner: QueryRunner): Promise { + const transacao = Transacao.fromOrdem(ordem, pagador, transacaoAgId); return await this.transacaoService.save(transacao, queryRunner); } - private convertTransacao(ordem: BigqueryOrdemPagamentoDTO, pagador: Pagador, transacaoAgId: number) { - return new Transacao({ dataOrdem: ordem.dataOrdem, dataPagamento: ordem.dataPagamento, pagador: pagador, idOrdemPagamento: ordem.idOrdemPagamento, transacaoAgrupado: { id: transacaoAgId } }); - } - - convertTransacaoAgrupadoDTO(ordem: BigqueryOrdemPagamentoDTO, pagador: Pagador) { - const dataOrdem = yearMonthDayToDate(ordem.dataOrdem); - /** semana de pagamento: sex-qui */ - const fridayOrdem = nextFriday(startOfDay(dataOrdem)); - const transacao = new TransacaoAgrupado({ - dataOrdem: fridayOrdem, - dataPagamento: ordem.dataPagamento, - pagador: pagador, - idOrdemPagamento: ordem.idOrdemPagamento, - status: TransacaoStatus.fromEnum(TransacaoStatusEnum.created), - }); - return transacao; - } - - convertItemTransacaoAgrupadoDTO(ordem: BigqueryOrdemPagamentoDTO, transacaoAg: TransacaoAgrupado) { - const dataOrdem = yearMonthDayToDate(ordem.dataOrdem); - const fridayOrdem = nextFriday(nextThursday(startOfDay(dataOrdem))); - const item = new ItemTransacaoAgrupado({ - dataCaptura: ordem.dataOrdem, - dataOrdem: fridayOrdem, - idConsorcio: ordem.idConsorcio, - idOperadora: ordem.idOperadora, - idOrdemPagamento: ordem.idOrdemPagamento, - nomeConsorcio: ordem.consorcio, - nomeOperadora: ordem.operadora, - valor: ordem.valorTotalTransacaoLiquido, - transacaoAgrupado: transacaoAg, - }); - return item; - } - - async saveItemTransacaoPublicacao(ordem: BigqueryOrdemPagamentoDTO, favorecido: ClienteFavorecido, transacao: Transacao, itemTransacaoAg: ItemTransacaoAgrupado, queryRunner: QueryRunner) { - const item = this.convertItemTransacao(ordem, favorecido, transacao, itemTransacaoAg); + async saveItemTransacaoPublicacao(ordem: OrdemPagamentoDto, favorecido: ClienteFavorecido, transacao: Transacao, itemTransacaoAg: ItemTransacaoAgrupado, queryRunner: QueryRunner) { + const item = ItemTransacao.fromOrdem(ordem, favorecido, transacao, itemTransacaoAg); await this.itemTransacaoService.save(item, queryRunner); const publicacao = await this.arquivoPublicacaoService.convertPublicacaoDTO(item); await this.arquivoPublicacaoService.save(publicacao, queryRunner); } - private convertItemTransacao(ordem: BigqueryOrdemPagamentoDTO, favorecido: ClienteFavorecido, transacao: Transacao, itemTransacaoAg: ItemTransacaoAgrupado) { - return new ItemTransacao({ - clienteFavorecido: favorecido, - dataCaptura: ordem.dataOrdem, - dataOrdem: startOfDay(new Date(ordem.dataOrdem)), - idConsorcio: ordem.idConsorcio, - idOperadora: ordem.idOperadora, - idOrdemPagamento: ordem.idOrdemPagamento, - nomeConsorcio: ordem.consorcio, - nomeOperadora: ordem.operadora, - valor: ordem.valorTotalTransacaoLiquido, - transacao: transacao, - itemTransacaoAgrupado: { id: itemTransacaoAg.id }, - }); - } - - async getTransacoesViewWeek(dataInicio: Date, dataFim: Date) { - let friday = new Date(); - let startDate; - let endDate; - - if (!isFriday(friday)) { - friday = nextFriday(friday); - } - - if (dataInicio != undefined && dataFim != undefined) { - startDate = dataInicio; - endDate = dataFim; - } else { - startDate = startOfDay(subDays(friday, 8)); - endDate = endOfDay(subDays(friday, 2)); - } - return await this.transacaoViewService.find({ datetimeProcessamento: Between(startDate, endDate) }, false); - } + // #endregion - public async saveTransacoesLancamento() { + public async saveTransacoesLancamento(dataOrdemInicial?: Date, dataOrdemFinal?: Date) { + const dataOrdem: [Date, Date] | undefined = dataOrdemInicial && dataOrdemFinal ? [dataOrdemInicial, dataOrdemFinal] : undefined; await this.updateAllFavorecidosFromUsers(); - const newLancamentos = await this.lancamentoService.findToPayWeek(); - const favorecidos = newLancamentos.map((i) => i.clienteFavorecido); - const pagador = (await this.pagadorService.getAllPagador()).contaBilhetagem; - const transacaoDTO = this.transacaoService.generateDTOForLancamento(pagador, newLancamentos); - const savedTransacao = await this.transacaoService.saveForLancamento(transacaoDTO); - const updatedLancamentos = savedTransacao.lancamentos as Lancamento[]; - const itemTransacaoDTOs = this.itemTransacaoService.generateDTOsFromLancamentos(updatedLancamentos, favorecidos); - await this.itemTransacaoService.saveMany(itemTransacaoDTOs); - } - - private async updateAllFavorecidosFromUsers() { - const allUsers = await this.usersService.findManyRegisteredUsers(); - await this.clienteFavorecidoService.updateAllFromUsers(allUsers); + const newLancamentos = await this.lancamentoService.findToPay(dataOrdem); + const ordens = newLancamentos.map((l) => OrdemPagamentoDto.fromLancamento(l)); + await this.saveOrdens(ordens, 'cett'); } public async generateRemessa(args: { diff --git a/src/cnab/const/bigquery-consorcio.const.ts b/src/cnab/const/bigquery-consorcio.const.ts new file mode 100644 index 00000000..b56d5914 --- /dev/null +++ b/src/cnab/const/bigquery-consorcio.const.ts @@ -0,0 +1,13 @@ +export const BigqueryConsorcio = [ + { consorcio: 'STPC', idConsorcio: '2' }, + { consorcio: 'Sem consórcio', idConsorcio: '7' }, + { consorcio: 'STPL', idConsorcio: '8' }, + { consorcio: 'VLT', idConsorcio: '2' }, + { consorcio: 'Intersul', idConsorcio: '221000023' }, + { consorcio: 'Transcarioca', idConsorcio: '221000014' }, + { consorcio: 'Belford Roxo', idConsorcio: '11' }, + { consorcio: 'Condomínio', idConsorcio: '10' }, + { consorcio: 'Internorte', idConsorcio: '221000032' }, + { consorcio: 'Santa Cruz', idConsorcio: '221000041' }, + { consorcio: 'MobiRio', idConsorcio: '229000010' }, +]; diff --git a/src/cnab/dto/pagamento/ordem-pagamento.dto.ts b/src/cnab/dto/pagamento/ordem-pagamento.dto.ts new file mode 100644 index 00000000..13314d4d --- /dev/null +++ b/src/cnab/dto/pagamento/ordem-pagamento.dto.ts @@ -0,0 +1,106 @@ +import { BigqueryOrdemPagamentoDTO } from 'src/bigquery/dtos/bigquery-ordem-pagamento.dto'; +import { Lancamento } from 'src/lancamento/entities/lancamento.entity'; +import { formatDateYMD } from 'src/utils/date-utils'; + +export interface IOrdemPagamento { + dataOrdem: string; + idOrdemPagamento: string; + idConsorcio: string; + consorcio: string; + idOperadora: string; + operadora: string; + valorTotalTransacaoLiquido: number; + favorecidoCpfCnpj: string | null; +} + +/** + * Um DTO base para gerar itens para o Transacao, Item, ItemAgrupado, TransacaoAgrupado. + */ +export class OrdemPagamentoDto implements IOrdemPagamento { + constructor(dto?: IOrdemPagamento) { + if (dto) { + Object.assign(this, dto); + } + } + + public static fromBigqueryOrdem(bqOrdem: BigqueryOrdemPagamentoDTO) { + return new OrdemPagamentoDto({ + dataOrdem: bqOrdem.dataOrdem, + idOrdemPagamento: bqOrdem.idOrdemPagamento, + idConsorcio: bqOrdem.idConsorcio, + consorcio: bqOrdem.consorcio, + idOperadora: bqOrdem.idOperadora, + operadora: bqOrdem.operadora, + valorTotalTransacaoLiquido: bqOrdem.valorTotalTransacaoLiquido, + favorecidoCpfCnpj: bqOrdem.consorcioCnpj || bqOrdem.operadoraCpfCnpj, + }); + } + + public static fromLancamento(lancamento: Lancamento) { + return new OrdemPagamentoDto({ + dataOrdem: formatDateYMD(lancamento.data_ordem), + idOrdemPagamento: `L${new Date().getTime()}`, + idConsorcio: '', + consorcio: '', + idOperadora: '', + operadora: '', + valorTotalTransacaoLiquido: lancamento.valor, + favorecidoCpfCnpj: lancamento.clienteFavorecido.cpfCnpj + }); + } + + /** BigqueryOrdem - Dia único, que representa uma ordem de pagamento */ + dataOrdem: string; + + /** + * BiguqeryOrdem: + * - Identificador da ordem pagamento no banco de dados da Jaé + * - Cada **data_ordem** (dia) possui um id_ordem_pagamento único. + * + * Lançamento + * - Para identificar que este ID é do Lançamento CCT, o id pode ser `L` + * - Exemplo: `L1725458795` + */ + idOrdemPagamento: string; + + /** + * BigqueryOrdem.idConsorcio + * + * Lançando: não utiliza + */ + idConsorcio: string; + + /** + * BigqueryOrdem: Nome do consórcio, para referência + * + * Lançamento: não utiliza + */ + consorcio: string; + + /** + * BigqueryOrdem: Identificador da operadora na tabela cadastro.operadoras + * + * Lançamento: não utiliza + */ + idOperadora: string; + + /** BigqueryOrdem - Nome da operadora */ + operadora: string; + + /** Valor total das transações menos o valor_desconto_taxa (R$) */ + valorTotalTransacaoLiquido: number; + + /** + * BigqueryOrdem: consorcioCnpj ou operadoraCpfCnpj, respectivamente + * + * Lançamento: clienteFavorecido.cpfCnpj + */ + favorecidoCpfCnpj: string | null; + + /** + * BigqueryOrdem: não utiliza + * + * Lançamento: associa com ItemTransacao + */ + lancamento?: Lancamento; +} diff --git a/src/cnab/entity/arquivo-publicacao.entity.ts b/src/cnab/entity/arquivo-publicacao.entity.ts index bb5796ba..37d87155 100644 --- a/src/cnab/entity/arquivo-publicacao.entity.ts +++ b/src/cnab/entity/arquivo-publicacao.entity.ts @@ -16,6 +16,7 @@ export interface IArquivoPublicacao { updatedAt: Date; } /** + * Representa uma transação de uma ordem de pagamento (idOrdem) a um destinatário específico (idOperadora) * Unique Jaé FK: idOrdemPagamento, idConsorcio, idOperadora */ @Entity() diff --git a/src/cnab/entity/pagamento/item-transacao-agrupado.entity.ts b/src/cnab/entity/pagamento/item-transacao-agrupado.entity.ts index a51bcd7b..5063d985 100644 --- a/src/cnab/entity/pagamento/item-transacao-agrupado.entity.ts +++ b/src/cnab/entity/pagamento/item-transacao-agrupado.entity.ts @@ -1,34 +1,31 @@ import { EntityHelper } from 'src/utils/entity-helper'; import { asStringOrNumber } from 'src/utils/pipe-utils'; -import { - AfterLoad, - Column, - CreateDateColumn, - DeepPartial, - Entity, - JoinColumn, - ManyToOne, - PrimaryGeneratedColumn, - UpdateDateColumn, -} from 'typeorm'; +import { AfterLoad, Column, CreateDateColumn, DeepPartial, Entity, JoinColumn, ManyToOne, OneToMany, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'; import { TransacaoAgrupado } from './transacao-agrupado.entity'; +import { BigqueryOrdemPagamentoDTO } from 'src/bigquery/dtos/bigquery-ordem-pagamento.dto'; +import { yearMonthDayToDate } from 'src/utils/date-utils'; +import { nextFriday, nextThursday, startOfDay } from 'date-fns'; +import { OrdemPagamentoDto } from 'src/cnab/dto/pagamento/ordem-pagamento.dto'; +import { ItemTransacao } from './item-transacao.entity'; /** * Representa um destinatário, a ser pago pelo remetente (TransacaoAgrupado). - * + * * Esta tabela contém a soma de todas as transações (ItemTransacao) * a serem feitas neste CNAB (TransacaoAgrupado). - * + * * Colunas: * - dataOrdem: sexta de pagamento (baseado no BigqueryOrdemPgto.dataOrdem (dia da ordem D+1)) - * + * * Identificador: * - TransacaoAgrupado (CNAB / remetente) * - ClienteFavorecido (destinatário) */ @Entity() export class ItemTransacaoAgrupado extends EntityHelper { + private readonly FKs = ['transacaoAgrupado', 'clienteFavorecido']; + constructor(dto?: DeepPartial) { super(); if (dto) { @@ -39,7 +36,21 @@ export class ItemTransacaoAgrupado extends EntityHelper { } } - private readonly FKs = ['transacaoAgrupado', 'clienteFavorecido']; + public static fromOrdem(ordem: OrdemPagamentoDto, transacaoAg: TransacaoAgrupado, dataOrdem?: Date) { + const fridayOrdem = nextFriday(nextThursday(startOfDay(yearMonthDayToDate(ordem.dataOrdem)))); + const item = new ItemTransacaoAgrupado({ + dataCaptura: ordem.dataOrdem, + dataOrdem: dataOrdem || fridayOrdem, + idConsorcio: ordem.idConsorcio, + idOperadora: ordem.idOperadora, + idOrdemPagamento: ordem.idOrdemPagamento, + nomeConsorcio: ordem.consorcio, + nomeOperadora: ordem.operadora, + valor: ordem.valorTotalTransacaoLiquido, + transacaoAgrupado: transacaoAg, + }); + return item; + } @PrimaryGeneratedColumn({ primaryKeyConstraintName: 'PK_ItemTransacaoAgrupado_id', @@ -50,8 +61,7 @@ export class ItemTransacaoAgrupado extends EntityHelper { eager: true, }) @JoinColumn({ - foreignKeyConstraintName: - 'FK_ItemTransacaoAgrupado_transacaoAgrupado_ManyToOne', + foreignKeyConstraintName: 'FK_ItemTransacaoAgrupado_transacaoAgrupado_ManyToOne', }) transacaoAgrupado: TransacaoAgrupado; @@ -105,13 +115,15 @@ export class ItemTransacaoAgrupado extends EntityHelper { @UpdateDateColumn() updatedAt: Date; + /** Não é uma coluna, usado apenas para consulta no ORM. */ + @OneToMany(() => ItemTransacao, (it) => it.itemTransacaoAgrupado) + itemTransacoes: ItemTransacao[]; + public getLogInfo(): string { return `#{ idOP: ${this.idOrdemPagamento}, op: ${this.idOperadora}, co: ${this.idConsorcio} }`; } - public static getUniqueIdJae( - entity: DeepPartial, - ): string { + public static getUniqueIdJae(entity: DeepPartial): string { return `${entity.idOrdemPagamento}|${entity.idConsorcio}|${entity.idOperadora}`; } diff --git a/src/cnab/entity/pagamento/item-transacao.entity.ts b/src/cnab/entity/pagamento/item-transacao.entity.ts index 7e98bdff..23b9632d 100644 --- a/src/cnab/entity/pagamento/item-transacao.entity.ts +++ b/src/cnab/entity/pagamento/item-transacao.entity.ts @@ -1,26 +1,19 @@ +import { startOfDay } from 'date-fns'; +import { OrdemPagamentoDto } from 'src/cnab/dto/pagamento/ordem-pagamento.dto'; +import { Lancamento } from 'src/lancamento/entities/lancamento.entity'; import { EntityHelper } from 'src/utils/entity-helper'; import { asStringOrNumber } from 'src/utils/pipe-utils'; -import { - AfterLoad, - Column, - CreateDateColumn, - DeepPartial, - Entity, - JoinColumn, - ManyToOne, - PrimaryGeneratedColumn, - UpdateDateColumn, -} from 'typeorm'; +import { AfterLoad, Column, CreateDateColumn, DeepPartial, Entity, JoinColumn, ManyToOne, OneToOne, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'; import { ClienteFavorecido } from '../cliente-favorecido.entity'; import { ItemTransacaoAgrupado } from './item-transacao-agrupado.entity'; import { Transacao } from './transacao.entity'; /** * Representa uma BqOrdemPgto (ArquivoPublicacao) - * + * * Colunas: * - dataOrdem: BqOrdemPgto.dataOrdem - * + * * Identificador: * - ItemTransacaoAgrupado * - dataTransacao @@ -34,11 +27,27 @@ export class ItemTransacao extends EntityHelper { if (dto) { Object.assign(this, dto); if (dto.itemTransacaoAgrupado) { - this.itemTransacaoAgrupado = new ItemTransacaoAgrupado(dto.itemTransacaoAgrupado) + this.itemTransacaoAgrupado = new ItemTransacaoAgrupado(dto.itemTransacaoAgrupado); } } } + public static fromOrdem(ordem: OrdemPagamentoDto, favorecido: ClienteFavorecido, transacao: Transacao, itemTransacaoAg: ItemTransacaoAgrupado) { + return new ItemTransacao({ + clienteFavorecido: favorecido, + dataCaptura: ordem.dataOrdem, + dataOrdem: startOfDay(new Date(ordem.dataOrdem)), + idConsorcio: ordem.idConsorcio, + idOperadora: ordem.idOperadora, + idOrdemPagamento: ordem.idOrdemPagamento, + nomeConsorcio: ordem.consorcio, + nomeOperadora: ordem.operadora, + valor: ordem.valorTotalTransacaoLiquido, + transacao: transacao, + itemTransacaoAgrupado: { id: itemTransacaoAg.id }, + }); + } + @PrimaryGeneratedColumn({ primaryKeyConstraintName: 'PK_ItemTransacao_id' }) id: number; @@ -54,8 +63,7 @@ export class ItemTransacao extends EntityHelper { eager: true, }) @JoinColumn({ - foreignKeyConstraintName: - 'FK_ItemTransacao_itemTransacaoAgrupado_ManyToOne', + foreignKeyConstraintName: 'FK_ItemTransacao_itemTransacaoAgrupado_ManyToOne', }) itemTransacaoAgrupado: ItemTransacaoAgrupado; @@ -85,7 +93,7 @@ export class ItemTransacao extends EntityHelper { }) clienteFavorecido: ClienteFavorecido; - /** + /** * Valor do lançamento. */ @Column({ @@ -112,9 +120,9 @@ export class ItemTransacao extends EntityHelper { // Unique columns Lancamento - /** + /** * DataOrdem from bigquery - * + * * D+1 (sex-qui) */ @Column({ type: Date, unique: false, nullable: false }) @@ -126,6 +134,10 @@ export class ItemTransacao extends EntityHelper { @UpdateDateColumn() updatedAt: Date; + /** Não é uma coluna, usado apenas para consulta no ORM. */ + @OneToOne(() => Lancamento, (l) => l.itemTransacao) + lancamento: Lancamento | null; + public getLogInfo(): string { return `#{ idOP: ${this.idOrdemPagamento}, op: ${this.idOperadora}, co: ${this.idConsorcio} }`; } diff --git a/src/cnab/entity/pagamento/transacao-agrupado.entity.ts b/src/cnab/entity/pagamento/transacao-agrupado.entity.ts index f1508d1d..f8d9a342 100644 --- a/src/cnab/entity/pagamento/transacao-agrupado.entity.ts +++ b/src/cnab/entity/pagamento/transacao-agrupado.entity.ts @@ -1,4 +1,8 @@ +import { nextFriday, startOfDay } from 'date-fns'; +import { OrdemPagamentoDto } from 'src/cnab/dto/pagamento/ordem-pagamento.dto'; +import { TransacaoStatusEnum } from 'src/cnab/enums/pagamento/transacao-status.enum'; import { Lancamento } from 'src/lancamento/entities/lancamento.entity'; +import { yearMonthDayToDate } from 'src/utils/date-utils'; import { EntityHelper } from 'src/utils/entity-helper'; import { Column, CreateDateColumn, DeepPartial, Entity, JoinColumn, ManyToOne, OneToMany, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'; import { ItemTransacaoAgrupado } from './item-transacao-agrupado.entity'; @@ -21,6 +25,19 @@ export class TransacaoAgrupado extends EntityHelper { } } + public static fromOrdem(ordem: OrdemPagamentoDto, pagador: Pagador, dataOrdem?: Date) { + /** semana de pagamento: sex-qui */ + const fridayOrdem = nextFriday(startOfDay(yearMonthDayToDate(ordem.dataOrdem))); + const transacao = new TransacaoAgrupado({ + dataOrdem: dataOrdem || fridayOrdem, + dataPagamento: null, + pagador: pagador, + idOrdemPagamento: ordem.idOrdemPagamento, + status: TransacaoStatus.fromEnum(TransacaoStatusEnum.created), + }); + return transacao; + } + @PrimaryGeneratedColumn({ primaryKeyConstraintName: 'PK_TransacaoAgrupado_id', }) @@ -52,7 +69,7 @@ export class TransacaoAgrupado extends EntityHelper { status: TransacaoStatus; /** Not a physical column */ - @OneToMany(() => Lancamento, (lancamento) => lancamento.transacao, { + @OneToMany(() => Lancamento, (lancamento) => lancamento.itemTransacao, { nullable: true, }) @JoinColumn({ diff --git a/src/cnab/entity/pagamento/transacao.entity.ts b/src/cnab/entity/pagamento/transacao.entity.ts index 82c97a5e..6e4f90a9 100644 --- a/src/cnab/entity/pagamento/transacao.entity.ts +++ b/src/cnab/entity/pagamento/transacao.entity.ts @@ -1,3 +1,4 @@ +import { OrdemPagamentoDto } from 'src/cnab/dto/pagamento/ordem-pagamento.dto'; import { Lancamento } from 'src/lancamento/entities/lancamento.entity'; import { EntityHelper } from 'src/utils/entity-helper'; import { Column, CreateDateColumn, DeepPartial, Entity, JoinColumn, ManyToOne, OneToMany, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'; @@ -30,6 +31,16 @@ export class Transacao extends EntityHelper { } } + public static fromOrdem(ordem: OrdemPagamentoDto, pagador: Pagador, transacaoAgrupadoId: number) { + return new Transacao({ + dataOrdem: ordem.dataOrdem, // + dataPagamento: null, + pagador: pagador, + idOrdemPagamento: ordem.idOrdemPagamento, + transacaoAgrupado: { id: transacaoAgrupadoId }, + }); + } + @PrimaryGeneratedColumn({ primaryKeyConstraintName: 'PK_Transacao_id' }) id: number; @@ -58,7 +69,7 @@ export class Transacao extends EntityHelper { transacaoAgrupado: TransacaoAgrupado; /** Not a physical column */ - @OneToMany(() => Lancamento, (lancamento) => lancamento.transacao, { + @OneToMany(() => Lancamento, (lancamento) => lancamento.itemTransacao, { nullable: true, }) @JoinColumn({ diff --git a/src/cnab/service/pagamento/item-transacao.service.ts b/src/cnab/service/pagamento/item-transacao.service.ts index 241b1b29..4e1ff42c 100644 --- a/src/cnab/service/pagamento/item-transacao.service.ts +++ b/src/cnab/service/pagamento/item-transacao.service.ts @@ -1,13 +1,10 @@ import { Injectable, Logger } from '@nestjs/common'; import { ItemTransacaoDTO } from 'src/cnab/dto/pagamento/item-transacao.dto'; -import { ClienteFavorecido } from 'src/cnab/entity/cliente-favorecido.entity'; import { ItemTransacao } from 'src/cnab/entity/pagamento/item-transacao.entity'; import { Transacao } from 'src/cnab/entity/pagamento/transacao.entity'; import { ItemTransacaoRepository } from 'src/cnab/repository/pagamento/item-transacao.repository'; -import { Lancamento } from 'src/lancamento/entities/lancamento.entity'; import { CustomLogger } from 'src/utils/custom-logger'; import { logDebug } from 'src/utils/log-utils'; -import { asObject } from 'src/utils/pipe-utils'; import { SaveIfNotExists } from 'src/utils/types/save-if-not-exists.type'; import { DeepPartial, FindManyOptions, FindOptionsWhere, In, Not, QueryRunner } from 'typeorm'; @@ -23,44 +20,6 @@ export class ItemTransacaoService { return await this.itemTransacaoRepository.update(id, dto); } - // #region generateDTOsFromLancamentos - - /** - * @param publicacoes Ready to save or saved Entity. Must contain valid Transacao - */ - public generateDTOsFromLancamentos(lancamentos: Lancamento[], favorecidos: ClienteFavorecido[]): ItemTransacao[] { - /** Key: id ClienteFavorecido. Eficient way to find favorecido. */ - const favorecidosMap: Record = favorecidos.reduce((map, i) => ({ ...map, [i.id]: i }), {}); - - const itens: ItemTransacao[] = []; - - // Mount DTOs - for (const lancamento of lancamentos) { - const favorecido = favorecidosMap[lancamento.clienteFavorecido.id]; - itens.push(this.generateDTOFromLancamento(lancamento, favorecido)); - } - return itens; - } - - /** - * A simple pipe thar converts BigqueryOrdemPagamento into ItemTransacaoDTO. - * - * **status** is Created. - */ - public generateDTOFromLancamento(lancamento: Lancamento, favorecido: ClienteFavorecido): ItemTransacao { - const transacao = asObject(lancamento.transacao); - /** detalheA = null, isRegistered = false */ - const itemTransacao = new ItemTransacao({ - clienteFavorecido: { id: favorecido.id }, - transacao: { id: transacao.id }, - valor: lancamento.valor, - dataOrdem: lancamento.data_ordem, - }); - return itemTransacao; - } - - // #endregion - /** * Bulk save Transacao. */ diff --git a/src/cnab/service/pagamento/transacao.service.ts b/src/cnab/service/pagamento/transacao.service.ts index 4384fcd2..7b58758a 100644 --- a/src/cnab/service/pagamento/transacao.service.ts +++ b/src/cnab/service/pagamento/transacao.service.ts @@ -133,7 +133,7 @@ export class TransacaoService { private setLazyLancamentos(transacoes: Transacao[]) { for (const transacao of transacoes) { for (const lancamento of transacao.lancamentos || []) { - lancamento.transacao = { id: transacao.id } as Transacao; + lancamento.itemTransacao = { id: transacao.id } as Transacao; } } } diff --git a/src/lancamento/entities/lancamento.entity.ts b/src/lancamento/entities/lancamento.entity.ts index ae643aec..5f35758e 100644 --- a/src/lancamento/entities/lancamento.entity.ts +++ b/src/lancamento/entities/lancamento.entity.ts @@ -5,9 +5,10 @@ import { Transacao } from 'src/cnab/entity/pagamento/transacao.entity'; import { User } from 'src/users/entities/user.entity'; import { EntityHelper } from 'src/utils/entity-helper'; import { asStringOrNumber } from 'src/utils/pipe-utils'; -import { AfterLoad, Column, CreateDateColumn, DeepPartial, Entity, JoinColumn, JoinTable, ManyToMany, ManyToOne, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'; +import { AfterLoad, Column, CreateDateColumn, DeepPartial, Entity, JoinColumn, JoinTable, ManyToMany, ManyToOne, OneToOne, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'; import { LancamentoInputDto } from '../dtos/lancamento-input.dto'; import { LancamentoAutorizacao } from './lancamento-autorizacao.entity'; +import { ItemTransacao } from 'src/cnab/entity/pagamento/item-transacao.entity'; export interface ILancamento { id: number; @@ -15,7 +16,7 @@ export interface ILancamento { data_ordem: Date; data_pgto: Date | null; data_lancamento: Date; - transacao: Transacao; + itemTransacao: ItemTransacao; autorizacoes: User[]; autor: User; clienteFavorecido: ClienteFavorecido; @@ -45,7 +46,7 @@ export class Lancamento extends EntityHelper implements ILancamento { algoritmo: dto.algoritmo, glosa: dto.glosa, recurso: dto.recurso, - anexo: dto.anexo, + anexo: dto.anexo || 0, numero_processo: dto.numero_processo, clienteFavorecido: { id: dto.id_cliente_favorecido }, autor: dto.author, @@ -86,15 +87,15 @@ export class Lancamento extends EntityHelper implements ILancamento { @Column({ type: 'timestamp', nullable: true }) data_pgto: Date | null; - /** createdAt */ + /** Data da criação do Lançamento */ @Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' }) data_lancamento: Date; - /** Remessa */ - @ApiProperty({ description: 'Transação do CNAB remessa associado a este Lançamento' }) - @ManyToOne(() => Transacao, { nullable: true }) - @JoinColumn({ foreignKeyConstraintName: 'FK_Lancamento_transacao_ManyToOne' }) - transacao: Transacao; + /** Geração de Remessa */ + @ApiProperty({ description: 'ItemTransação do CNAB remessa associado a este Lançamento' }) + @OneToOne(() => ItemTransacao, { nullable: true }) + @JoinColumn({ foreignKeyConstraintName: 'FK_Lancamento_itemTransacao_OneToOne' }) + itemTransacao: ItemTransacao; @ManyToMany(() => User, (user) => user) @JoinTable({ diff --git a/src/lancamento/lancamento.service.ts b/src/lancamento/lancamento.service.ts index 73bf31b2..c419dc54 100644 --- a/src/lancamento/lancamento.service.ts +++ b/src/lancamento/lancamento.service.ts @@ -25,15 +25,11 @@ export class LancamentoService { /** * Procura itens não usados ainda (sem Transacao Id) from current payment week (sex-qui). */ - async findToPayWeek(): Promise { - const today = new Date(); - const friday = isFriday(today) ? today : nextFriday(today); - const sex = startOfDay(subDays(friday, 7)); - const qui = endOfDay(subDays(friday, 1)); + async findToPay(dataOrdemBetween?: [Date, Date]): Promise { return await this.lancamentoRepository.findMany({ where: { - data_lancamento: Between(sex, qui), - transacao: IsNull(), + ...(dataOrdemBetween ? { data_lancamento: Between(...dataOrdemBetween) } : {}), + itemTransacao: IsNull(), is_autorizado: true, }, }); From 181aa080f1d9d2c1d348dae027a167eb96fa12b8 Mon Sep 17 00:00:00 2001 From: Raphael Rivas Date: Mon, 9 Sep 2024 14:08:49 -0300 Subject: [PATCH 16/29] feat: Remessa lancamento --- src/cnab/cnab-manutencao.controller.ts | 5 +- src/cnab/cnab.service.ts | 56 +-- src/cnab/dto/pagamento/ordem-pagamento.dto.ts | 46 +- .../item-transacao-agrupado.entity.ts | 12 +- .../entity/pagamento/item-transacao.entity.ts | 1 + .../pagamento/transacao-agrupado.entity.ts | 7 +- .../service/arquivo-publicacao.service.ts | 5 +- .../pagamento/remessa-retorno.service.ts | 34 +- .../service/pagamento/transacao.service.ts | 92 +--- .../1725545132372-PagamentosPendentes2v.ts | 14 + .../1725545232586-LancamentoRemessa.ts | 20 + .../migrations/1725895301219-Lancamento3v.ts | 14 + .../seeds/user/user-seed-data.service.ts | 409 +++++++++--------- src/lancamento/entities/lancamento.entity.ts | 121 ++++-- src/lancamento/lancamento.repository.ts | 41 +- src/lancamento/lancamento.service.ts | 14 +- src/roles/roles.enum.ts | 1 + src/transacao-view/transacao-view.entity.ts | 127 +++--- .../transacao-view.repository.ts | 3 +- 19 files changed, 534 insertions(+), 488 deletions(-) create mode 100644 src/database/migrations/1725545132372-PagamentosPendentes2v.ts create mode 100644 src/database/migrations/1725545232586-LancamentoRemessa.ts create mode 100644 src/database/migrations/1725895301219-Lancamento3v.ts diff --git a/src/cnab/cnab-manutencao.controller.ts b/src/cnab/cnab-manutencao.controller.ts index ee849664..940f883c 100644 --- a/src/cnab/cnab-manutencao.controller.ts +++ b/src/cnab/cnab-manutencao.controller.ts @@ -38,7 +38,7 @@ export class CnabManutencaoController { async getGenerateRemessaLancamento( @Query('dataOrdemInicial', new ParseDatePipe({ transform: true, optional: true })) _dataOrdemInicial: any, // Date @Query('dataOrdemFinal', new ParseDatePipe({ transform: true, optional: true })) _dataOrdemFinal: any, // Date - @Query('dataPagamento', new ParseDatePipe({ transform: true, optional: true })) _dataPagamento: any, // Date | undefined + @Query('dataPagamento', new ParseDatePipe({ transform: true, optional: true })) dataPagamento: Date | undefined, // Date | undefined @Query('isConference') isConference: boolean, @Query('isCancelamento') isCancelamento: boolean, @Query('nsaInicial', new ParseNumberPipe({ min: 1, optional: true })) nsaInicial: number | undefined, @@ -47,7 +47,6 @@ export class CnabManutencaoController { ) { const dataOrdemInicial = _dataOrdemInicial as Date | undefined; const dataOrdemFinal = _dataOrdemFinal as Date | undefined; - const dataPgto = _dataPagamento || new Date() as Date; const dataCancelamento = _dataCancelamento as Date | undefined; if (isCancelamento && !dataCancelamento) { @@ -57,7 +56,7 @@ export class CnabManutencaoController { return await this.cnabService.getGenerateRemessaLancamento({ dataOrdemInicial, dataOrdemFinal, - dataPgto, + dataPgto: dataPagamento, isConference, isCancelamento, nsaInicial, diff --git a/src/cnab/cnab.service.ts b/src/cnab/cnab.service.ts index 79c7ab65..34b46f4e 100644 --- a/src/cnab/cnab.service.ts +++ b/src/cnab/cnab.service.ts @@ -1,31 +1,30 @@ -import { Injectable, NotImplementedException } from '@nestjs/common'; -import { endOfDay, isFriday, nextFriday, startOfDay, subDays } from 'date-fns'; +import { Injectable } from '@nestjs/common'; +import { InjectDataSource } from '@nestjs/typeorm'; +import { endOfDay, startOfDay } from 'date-fns'; import { BigqueryOrdemPagamentoDTO } from 'src/bigquery/dtos/bigquery-ordem-pagamento.dto'; +import { BigqueryTransacao } from 'src/bigquery/entities/transacao.bigquery-entity'; import { BigqueryOrdemPagamentoService } from 'src/bigquery/services/bigquery-ordem-pagamento.service'; import { BigqueryTransacaoService } from 'src/bigquery/services/bigquery-transacao.service'; -import { Lancamento } from 'src/lancamento/entities/lancamento.entity'; import { LancamentoService } from 'src/lancamento/lancamento.service'; +import { cnabSettings } from 'src/settings/cnab.settings'; +import { SettingsService } from 'src/settings/settings.service'; import { SftpBackupFolder } from 'src/sftp/enums/sftp-backup-folder.enum'; import { SftpService } from 'src/sftp/sftp.service'; +import { ISyncOrdemPgto } from 'src/transacao-view/interfaces/sync-form-ordem.interface'; import { TransacaoView } from 'src/transacao-view/transacao-view.entity'; import { TransacaoViewService } from 'src/transacao-view/transacao-view.service'; import { UsersService } from 'src/users/users.service'; - -import { InjectDataSource } from '@nestjs/typeorm'; -import { BigqueryTransacao } from 'src/bigquery/entities/transacao.bigquery-entity'; -import { cnabSettings } from 'src/settings/cnab.settings'; -import { SettingsService } from 'src/settings/settings.service'; -import { ISyncOrdemPgto } from 'src/transacao-view/interfaces/sync-form-ordem.interface'; import { getChunks } from 'src/utils/array-utils'; import { completeCPFCharacter } from 'src/utils/cpf-cnpj'; import { CustomLogger } from 'src/utils/custom-logger'; -import { formatDateInterval, yearMonthDayToDate } from 'src/utils/date-utils'; +import { formatDateInterval } from 'src/utils/date-utils'; import { CommonHttpException } from 'src/utils/http-exception/common-http-exception'; import { formatErrMsg } from 'src/utils/log-utils'; import { asNumber } from 'src/utils/pipe-utils'; -import { Between, DataSource, DeepPartial, QueryRunner } from 'typeorm'; +import { DataSource, DeepPartial, QueryRunner } from 'typeorm'; import { HeaderArquivoDTO } from './dto/pagamento/header-arquivo.dto'; import { HeaderLoteDTO } from './dto/pagamento/header-lote.dto'; +import { OrdemPagamentoDto } from './dto/pagamento/ordem-pagamento.dto'; import { ClienteFavorecido } from './entity/cliente-favorecido.entity'; import { ItemTransacaoAgrupado } from './entity/pagamento/item-transacao-agrupado.entity'; import { ItemTransacao } from './entity/pagamento/item-transacao.entity'; @@ -39,6 +38,7 @@ import { TransacaoStatusEnum } from './enums/pagamento/transacao-status.enum'; import { CnabHeaderArquivo104 } from './interfaces/cnab-240/104/cnab-header-arquivo-104.interface'; import { CnabFile104Extrato } from './interfaces/cnab-240/104/extrato/cnab-file-104-extrato.interface'; import { CnabRegistros104Pgto } from './interfaces/cnab-240/104/pagamento/cnab-registros-104-pgto.interface'; +import { AllPagadorDict } from './interfaces/pagamento/all-pagador-dict.interface'; import { ArquivoPublicacaoService } from './service/arquivo-publicacao.service'; import { ClienteFavorecidoService } from './service/cliente-favorecido.service'; import { ExtratoDetalheEService } from './service/extrato/extrato-detalhe-e.service'; @@ -54,8 +54,6 @@ 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 { OrdemPagamentoDto } from './dto/pagamento/ordem-pagamento.dto'; -import { AllPagadorDict } from './interfaces/pagamento/all-pagador-dict.interface'; /** * User cases for CNAB and Payments @@ -359,20 +357,18 @@ export class CnabService { const METHOD = 'saveAgrupamentos'; const queryRunner = this.dataSource.createQueryRunner(); await queryRunner.connect(); - let itemAg: ItemTransacaoAgrupado | null = null; try { await queryRunner.startTransaction(); this.logger.debug(`Salvando Agrupamento - ${JSON.stringify({ consorcio: ordem.consorcio, operadora: ordem.operadora, favorecidoCpfCnpj: ordem.favorecidoCpfCnpj })}`, METHOD); - const dataOrdem = yearMonthDayToDate(ordem.dataOrdem); - const fridayOrdem = nextFriday(startOfDay(dataOrdem)); let transacaoAg = await this.transacaoAgService.findOne({ - dataOrdem: fridayOrdem, + dataOrdem: ordem.getTransacaoAgrupadoDataOrdem(), pagador: { id: pagador.id }, status: { id: TransacaoStatusEnum.created }, }); + let itemAg: ItemTransacaoAgrupado | null = null; if (transacaoAg) { - itemAg = await this.saveUpdateItemTransacaoAg(transacaoAg, ordem, queryRunner); + itemAg = await this.saveUpdateItemTransacaoAgrupado(transacaoAg, ordem, queryRunner); } else { transacaoAg = await this.saveTransacaoAgrupado(ordem, pagador); itemAg = await this.saveItemTransacaoAgrupado(ordem, transacaoAg, queryRunner); @@ -393,9 +389,9 @@ export class CnabService { * Rergra de negócio: * - Usa idConsorcio, idOperadora, consorcio para agrupar um itemAgrupado */ - private async saveUpdateItemTransacaoAg(transacaoAg: TransacaoAgrupado, ordem: OrdemPagamentoDto, queryRunner: QueryRunner): Promise { + private async saveUpdateItemTransacaoAgrupado(transacaoAg: TransacaoAgrupado, ordem: OrdemPagamentoDto, queryRunner: QueryRunner): Promise { if (!ordem.consorcio.length && !ordem.operadora.length && !ordem.favorecidoCpfCnpj) { - throw new NotImplementedException(`Criar/atualizar ItemAgrupado necessita de consorcio/operadora ou favorecidoCpfCnpj`); + throw new Error(`Não implementado - Criar/atualizar ItemAgrupado necessita de consorcio/operadora ou favorecidoCpfCnpj`); } let itemAg = await this.itemTransacaoAgService.findOne({ @@ -404,11 +400,13 @@ export class CnabService { id: transacaoAg.id, status: { id: TransacaoStatusEnum.created }, }, - ...(ordem.consorcio.length || ordem.operadora.length // Se for Jaé - ? ordem.consorcio === 'STPC' || ordem.consorcio === 'STPL' - ? { idOperadora: ordem.idOperadora } // agrupa por vanzeiro - : { idConsorcio: ordem.idConsorcio } // ou por empresa - : { itemTransacoes: { clienteFavorecido: { cpfCnpj: ordem.favorecidoCpfCnpj as string } } }), // Se for Lançamento, agrupa por favorecido + ...(ordem.consorcio.length || ordem.operadora.length + ? /** Se for Jaé, agrupa por vanzeiro ou empresa */ + ordem.consorcio === 'STPC' || ordem.consorcio === 'STPL' + ? { idOperadora: ordem.idOperadora } + : { idConsorcio: ordem.idConsorcio } + : /** Se for Lançamento, agrupa por favorecido e dataOrdem */ + { itemTransacoes: { clienteFavorecido: { cpfCnpj: ordem.favorecidoCpfCnpj as string } }, dataOrdem: startOfDay(new Date((ordem.dataOrdem))) }), }, }); if (itemAg) { @@ -454,8 +452,10 @@ export class CnabService { async saveItemTransacaoPublicacao(ordem: OrdemPagamentoDto, favorecido: ClienteFavorecido, transacao: Transacao, itemTransacaoAg: ItemTransacaoAgrupado, queryRunner: QueryRunner) { const item = ItemTransacao.fromOrdem(ordem, favorecido, transacao, itemTransacaoAg); await this.itemTransacaoService.save(item, queryRunner); - const publicacao = await this.arquivoPublicacaoService.convertPublicacaoDTO(item); - await this.arquivoPublicacaoService.save(publicacao, queryRunner); + if (!ordem.lancamento) { + const publicacao = await this.arquivoPublicacaoService.convertPublicacaoDTO(item); + await this.arquivoPublicacaoService.save(publicacao, queryRunner); + } } // #endregion @@ -479,6 +479,7 @@ export class CnabService { nsaFinal?: number; dataCancelamento?: Date; }): Promise { + const METHOD = this.sendRemessa.name; const currentNSA = parseInt((await this.settingsService.getOneBySettingData(cnabSettings.any__cnab_current_nsa)).value); const { tipo, dataPgto, isConference, isCancelamento } = args; @@ -486,7 +487,6 @@ export class CnabService { let nsaFinal = args.nsaFinal || nsaInicial; const dataCancelamento = args?.dataCancelamento || new Date(); - const METHOD = this.sendRemessa.name; const queryRunner = this.dataSource.createQueryRunner(); await queryRunner.connect(); try { diff --git a/src/cnab/dto/pagamento/ordem-pagamento.dto.ts b/src/cnab/dto/pagamento/ordem-pagamento.dto.ts index 13314d4d..fabe5540 100644 --- a/src/cnab/dto/pagamento/ordem-pagamento.dto.ts +++ b/src/cnab/dto/pagamento/ordem-pagamento.dto.ts @@ -1,6 +1,7 @@ +import { nextFriday, startOfDay } from 'date-fns'; import { BigqueryOrdemPagamentoDTO } from 'src/bigquery/dtos/bigquery-ordem-pagamento.dto'; import { Lancamento } from 'src/lancamento/entities/lancamento.entity'; -import { formatDateYMD } from 'src/utils/date-utils'; +import { formatDateYMD, yearMonthDayToDate } from 'src/utils/date-utils'; export interface IOrdemPagamento { dataOrdem: string; @@ -11,6 +12,7 @@ export interface IOrdemPagamento { operadora: string; valorTotalTransacaoLiquido: number; favorecidoCpfCnpj: string | null; + lancamento?: Lancamento; } /** @@ -36,19 +38,35 @@ export class OrdemPagamentoDto implements IOrdemPagamento { }); } - public static fromLancamento(lancamento: Lancamento) { + public static fromLancamento(lancamento: Lancamento, idOrdemPagamento?: string) { return new OrdemPagamentoDto({ dataOrdem: formatDateYMD(lancamento.data_ordem), - idOrdemPagamento: `L${new Date().getTime()}`, + idOrdemPagamento: idOrdemPagamento || OrdemPagamentoDto.getIdOrdemPagamentoLancamento(), idConsorcio: '', consorcio: '', idOperadora: '', operadora: '', valorTotalTransacaoLiquido: lancamento.valor, - favorecidoCpfCnpj: lancamento.clienteFavorecido.cpfCnpj + favorecidoCpfCnpj: lancamento.clienteFavorecido.cpfCnpj, + lancamento, }); } + /** + * Bigquery + * - dataOrdem: sexta de pagamento + * + * Lançamento + * - dataOrdem: dia de hoje + */ + getTransacaoAgrupadoDataOrdem() { + return this.lancamento ? startOfDay(new Date()) : nextFriday(startOfDay(yearMonthDayToDate(this.dataOrdem))); + } + + /** Regra de negócio: O formato do id se refere ao dia, assim como ocorre no Jaé */ + public static getIdOrdemPagamentoLancamento() { + return `L${startOfDay(new Date()).getTime()}`; + } /** BigqueryOrdem - Dia único, que representa uma ordem de pagamento */ dataOrdem: string; @@ -63,16 +81,16 @@ export class OrdemPagamentoDto implements IOrdemPagamento { */ idOrdemPagamento: string; - /** + /** * BigqueryOrdem.idConsorcio - * + * * Lançando: não utiliza */ idConsorcio: string; - /** + /** * BigqueryOrdem: Nome do consórcio, para referência - * + * * Lançamento: não utiliza */ consorcio: string; @@ -90,17 +108,19 @@ export class OrdemPagamentoDto implements IOrdemPagamento { /** Valor total das transações menos o valor_desconto_taxa (R$) */ valorTotalTransacaoLiquido: number; - /** + /** * BigqueryOrdem: consorcioCnpj ou operadoraCpfCnpj, respectivamente - * + * * Lançamento: clienteFavorecido.cpfCnpj */ favorecidoCpfCnpj: string | null; - /** + /** * BigqueryOrdem: não utiliza - * - * Lançamento: associa com ItemTransacao + * + * Lançamento: + * - Associa com ItemTransacao. + * - Usado para verificar se esta Ordem é do Lançamento. */ lancamento?: Lancamento; } diff --git a/src/cnab/entity/pagamento/item-transacao-agrupado.entity.ts b/src/cnab/entity/pagamento/item-transacao-agrupado.entity.ts index 5063d985..3ad2f1f5 100644 --- a/src/cnab/entity/pagamento/item-transacao-agrupado.entity.ts +++ b/src/cnab/entity/pagamento/item-transacao-agrupado.entity.ts @@ -2,12 +2,11 @@ import { EntityHelper } from 'src/utils/entity-helper'; import { asStringOrNumber } from 'src/utils/pipe-utils'; import { AfterLoad, Column, CreateDateColumn, DeepPartial, Entity, JoinColumn, ManyToOne, OneToMany, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'; -import { TransacaoAgrupado } from './transacao-agrupado.entity'; -import { BigqueryOrdemPagamentoDTO } from 'src/bigquery/dtos/bigquery-ordem-pagamento.dto'; -import { yearMonthDayToDate } from 'src/utils/date-utils'; import { nextFriday, nextThursday, startOfDay } from 'date-fns'; import { OrdemPagamentoDto } from 'src/cnab/dto/pagamento/ordem-pagamento.dto'; +import { yearMonthDayToDate } from 'src/utils/date-utils'; import { ItemTransacao } from './item-transacao.entity'; +import { TransacaoAgrupado } from './transacao-agrupado.entity'; /** * Representa um destinatário, a ser pago pelo remetente (TransacaoAgrupado). @@ -36,11 +35,10 @@ export class ItemTransacaoAgrupado extends EntityHelper { } } - public static fromOrdem(ordem: OrdemPagamentoDto, transacaoAg: TransacaoAgrupado, dataOrdem?: Date) { + public static fromOrdem(ordem: OrdemPagamentoDto, transacaoAg: TransacaoAgrupado) { const fridayOrdem = nextFriday(nextThursday(startOfDay(yearMonthDayToDate(ordem.dataOrdem)))); const item = new ItemTransacaoAgrupado({ - dataCaptura: ordem.dataOrdem, - dataOrdem: dataOrdem || fridayOrdem, + dataOrdem: ordem.lancamento ? ordem.dataOrdem : fridayOrdem, idConsorcio: ordem.idConsorcio, idOperadora: ordem.idOperadora, idOrdemPagamento: ordem.idOrdemPagamento, @@ -68,7 +66,7 @@ export class ItemTransacaoAgrupado extends EntityHelper { @CreateDateColumn() dataProcessamento: Date; - /** Ao gravar pegamos dataOrdem */ + /** TODO: remover, não usaremos mais pois já tem o createdAt - Ao gravar pegamos dataOrdem */ @CreateDateColumn() dataCaptura: Date; diff --git a/src/cnab/entity/pagamento/item-transacao.entity.ts b/src/cnab/entity/pagamento/item-transacao.entity.ts index 23b9632d..3c59df0a 100644 --- a/src/cnab/entity/pagamento/item-transacao.entity.ts +++ b/src/cnab/entity/pagamento/item-transacao.entity.ts @@ -45,6 +45,7 @@ export class ItemTransacao extends EntityHelper { valor: ordem.valorTotalTransacaoLiquido, transacao: transacao, itemTransacaoAgrupado: { id: itemTransacaoAg.id }, + ...(ordem?.lancamento ? { lancamento: { id: ordem.lancamento.id } } : {}), }); } diff --git a/src/cnab/entity/pagamento/transacao-agrupado.entity.ts b/src/cnab/entity/pagamento/transacao-agrupado.entity.ts index f8d9a342..494fb1ce 100644 --- a/src/cnab/entity/pagamento/transacao-agrupado.entity.ts +++ b/src/cnab/entity/pagamento/transacao-agrupado.entity.ts @@ -25,11 +25,10 @@ export class TransacaoAgrupado extends EntityHelper { } } - public static fromOrdem(ordem: OrdemPagamentoDto, pagador: Pagador, dataOrdem?: Date) { - /** semana de pagamento: sex-qui */ - const fridayOrdem = nextFriday(startOfDay(yearMonthDayToDate(ordem.dataOrdem))); + + public static fromOrdem(ordem: OrdemPagamentoDto, pagador: Pagador) { const transacao = new TransacaoAgrupado({ - dataOrdem: dataOrdem || fridayOrdem, + dataOrdem: ordem.getTransacaoAgrupadoDataOrdem(), dataPagamento: null, pagador: pagador, idOrdemPagamento: ordem.idOrdemPagamento, diff --git a/src/cnab/service/arquivo-publicacao.service.ts b/src/cnab/service/arquivo-publicacao.service.ts index 3b5374ad..f0ee9914 100644 --- a/src/cnab/service/arquivo-publicacao.service.ts +++ b/src/cnab/service/arquivo-publicacao.service.ts @@ -2,7 +2,7 @@ import { Injectable, Logger } from '@nestjs/common'; import { addDays, isDate, isThursday, nextFriday, startOfDay } from 'date-fns'; import { asNumber } from 'src/utils/pipe-utils'; import { DeepPartial, FindManyOptions, QueryRunner } from 'typeorm'; -import { ArquivoPublicacao } from '../entity/arquivo-publicacao.entity'; +import { ArquivoPublicacao, IArquivoPublicacao } from '../entity/arquivo-publicacao.entity'; import { DetalheA } from '../entity/pagamento/detalhe-a.entity'; import { ItemTransacao } from '../entity/pagamento/item-transacao.entity'; import { ArquivoPublicacaoRepository, IArquivoPublicacaoRawWhere } from '../repository/arquivo-publicacao.repository'; @@ -84,6 +84,9 @@ export class ArquivoPublicacaoService { fieldNames = ['id', 'isPago', 'valorRealEfetivado', 'dataEfetivacao', 'dataGeracaoRetorno']; } const fieldValues = dtos.map((dto) => `(${EntityHelper.getQueryFieldValues(dto, fieldNames, ArquivoPublicacao.sqlFieldTypes)})`).join(', '); + // const reference: keyof IArquivoPublicacao = 'id'; + // const updatedAt: keyof IArquivoPublicacao = 'updatedAt'; + // const query = EntityHelper.getQueryUpdate('arquivo_publicacao', dtos, fieldNames, ArquivoPublicacao.sqlFieldTypes, reference, updatedAt); const query = ` UPDATE arquivo_publicacao SET ${fieldNames.map((f) => `"${f}" = sub.${f == 'id' ? '_id' : `"${f}"`}`).join(', ')}, "updatedAt" = NOW() diff --git a/src/cnab/service/pagamento/remessa-retorno.service.ts b/src/cnab/service/pagamento/remessa-retorno.service.ts index 3501b032..8dfec722 100644 --- a/src/cnab/service/pagamento/remessa-retorno.service.ts +++ b/src/cnab/service/pagamento/remessa-retorno.service.ts @@ -91,8 +91,10 @@ export class RemessaRetornoService { const lotes: HeaderLoteDTO[] = []; let nsrTed = 0; let nsrCC = 0; - let loteTed; - let loteCC; + /** @type HeaderLoteDTO */ + let loteTed: any; + /** @type HeaderLoteDTO */ + let loteCC: any; for (const itemTransacaoAgrupado of itemTransacaoAgs) { const itemTransacao = await this.itemTransacaoService.findOne({ where: { @@ -331,20 +333,14 @@ export class RemessaRetornoService { * @param dataPgto O padrão é o dia de hoje. O valor será sempre >= hoje. * @returns null if failed ItemTransacao to CNAB */ public async saveDetalhes104(numeroDocumento: number, headerLote: HeaderLoteDTO, itemTransacaoAg: ItemTransacaoAgrupado, nsr: number, isConference: boolean, dataPgto?: Date, isCancelamento = false, detalheAC = new DetalheA()): Promise { - let favorecido; + /** @type ClienteFavorecido */ + let favorecido: any; if (itemTransacaoAg != undefined) { - const itemTransacao = await this.itemTransacaoService.findOne({ - where: { itemTransacaoAgrupado: { id: itemTransacaoAg.id } }, - }); + const itemTransacao = await this.itemTransacaoService.findOne({ where: { itemTransacaoAgrupado: { id: itemTransacaoAg.id } } }); favorecido = itemTransacao?.clienteFavorecido; } else { const itemTransacaoAg = detalheAC.headerLote.headerArquivo.transacaoAgrupado?.itemTransacoesAgrupado[0]; - const itemTransacao = await this.itemTransacaoService.findOne({ - where: { - itemTransacaoAgrupado: { id: itemTransacaoAg?.id }, - }, - }); - + const itemTransacao = await this.itemTransacaoService.findOne({ where: { itemTransacaoAgrupado: { id: itemTransacaoAg?.id } } }); favorecido = itemTransacao?.clienteFavorecido; } @@ -365,8 +361,9 @@ export class RemessaRetornoService { return null; } - if (dataPgto && dataPgto < new Date()) { - dataPgto = new Date(); + let _dataPgto = dataPgto || itemTransacaoAg.dataOrdem; + if (_dataPgto < new Date()) { + _dataPgto = new Date(); } // Save detalheA @@ -378,14 +375,7 @@ export class RemessaRetornoService { detalheA.dvContaDestino.value = favorecido.dvContaCorrente; detalheA.nomeTerceiro.value = favorecido.nome; detalheA.numeroDocumentoEmpresa.value = numeroDocumento; - - const fridayOrdem = itemTransacaoAg.dataOrdem; - detalheA.dataVencimento.value = fridayOrdem; - if (dataPgto === undefined) { - detalheA.dataVencimento.value = detalheA.dataVencimento.value; - } else { - detalheA.dataVencimento.value = dataPgto; - } + detalheA.dataVencimento.value = _dataPgto; if (!isCancelamento) { detalheA.valorLancamento.value = itemTransacaoAg.valor; diff --git a/src/cnab/service/pagamento/transacao.service.ts b/src/cnab/service/pagamento/transacao.service.ts index 7b58758a..a4a16982 100644 --- a/src/cnab/service/pagamento/transacao.service.ts +++ b/src/cnab/service/pagamento/transacao.service.ts @@ -1,12 +1,10 @@ -import { Injectable, Logger } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { TransacaoDTO } from '../../dto/pagamento/transacao.dto'; import { Transacao } from '../../entity/pagamento/transacao.entity'; import { TransacaoRepository } from '../../repository/pagamento/transacao.repository'; -import { isFriday, nextFriday } from 'date-fns'; -import { Pagador } from 'src/cnab/entity/pagamento/pagador.entity'; import { PagadorContaEnum } from 'src/cnab/enums/pagamento/pagador.enum'; -import { Lancamento } from 'src/lancamento/entities/lancamento.entity'; +import { CustomLogger } from 'src/utils/custom-logger'; import { asNumber, asString } from 'src/utils/pipe-utils'; import { EntityCondition } from 'src/utils/types/entity-condition.type'; import { SaveIfNotExists } from 'src/utils/types/save-if-not-exists.type'; @@ -15,9 +13,7 @@ import { DeepPartial, FindManyOptions, QueryRunner, UpdateResult } from 'typeorm @Injectable() export class TransacaoService { - private logger: Logger = new Logger(TransacaoService.name, { - timestamp: true, - }); + private logger = new CustomLogger(TransacaoService.name, { timestamp: true }); constructor(private transacaoRepository: TransacaoRepository) {} @@ -29,71 +25,10 @@ export class TransacaoService { return await this.transacaoRepository.findOne(fields); } - /** - * **status** is Created. - * - * It will automatically update Lancamentos via OneToMany - * - * @param newLancamentos It must have at least 1 unused Lancamento - */ - public generateDTOForLancamento(pagador: Pagador, newLancamentos: Lancamento[]): Transacao { - const today = new Date(); - const friday = isFriday(today) ? today : nextFriday(today); - const transacao = new Transacao({ - dataOrdem: friday, - dataPagamento: null, - lancamentos: newLancamentos, // unique id for Lancamentos - pagador: { id: pagador.id } as Pagador, - }); - return transacao; - } - public update(dto: DeepPartial) { return this.transacaoRepository.update(asNumber(dto.id), dto); } - // #region generateDTOsFromPublicacoes - - // public generateDTOsFromPublicacoes( - // publicacoes: ArquivoPublicacao[], - // pagador: Pagador, - // ) { - // const transacoes: Transacao[] = []; - // /** key: idOrdemPagamento */ - // const transacaoMap: Record = {}; - // for (const publicacao of publicacoes) { - // const transacaoPK = publicacao.idOrdemPagamento; - // const newTransacao = this.generateDTOFromPublicacao(publicacao, pagador); - // if (!transacaoMap[transacaoPK]) { - // transacoes.push(newTransacao); - // transacaoMap[transacaoPK] = newTransacao; - // } - // } - // return transacoes; - // } - - /** - * getTransacaoFromOrdem() - * - * **status** is Created. - */ - // public generateDTOFromPublicacao( - // publicacao: ArquivoPublicacao, - // pagador: Pagador, - // ): Transacao { - // const transacao = new Transacao({ - // dataOrdem: publicacao.dataOrdem, - // dataPagamento: null, - // idOrdemPagamento: publicacao.idOrdemPagamento, // unique id for Ordem - // pagador: { id: pagador.id } as Pagador, - // status: new TransacaoStatus(TransacaoStatusEnum.created), - // ocorrencias: [], - // }); - // return transacao; - // } - - // #endregion - /** * Use first Transacao as set to update and all Transacoes to get ids. */ @@ -117,27 +52,6 @@ export class TransacaoService { }); } - /** - * Save Transacao for Lancamento - */ - public async saveForLancamento(dto: TransacaoDTO): Promise { - await validateDTO(TransacaoDTO, dto); - const saved = await this.transacaoRepository.save(dto); - this.setLazyLancamentos([saved]); - return saved; - } - - /** - * Set lazy value (only id) to transacao.lancamentos - */ - private setLazyLancamentos(transacoes: Transacao[]) { - for (const transacao of transacoes) { - for (const lancamento of transacao.lancamentos || []) { - lancamento.itemTransacao = { id: transacao.id } as Transacao; - } - } - } - /** * Save Transacao if Jae unique column not exists */ diff --git a/src/database/migrations/1725545132372-PagamentosPendentes2v.ts b/src/database/migrations/1725545132372-PagamentosPendentes2v.ts new file mode 100644 index 00000000..7509a1e6 --- /dev/null +++ b/src/database/migrations/1725545132372-PagamentosPendentes2v.ts @@ -0,0 +1,14 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class PagamentosPendentes2v1725545132372 implements MigrationInterface { + name = 'PagamentosPendentes2v1725545132372' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "pagamentos_pendentes" ADD "dataReferencia" TIMESTAMP`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "pagamentos_pendentes" DROP COLUMN "dataReferencia"`); + } + +} diff --git a/src/database/migrations/1725545232586-LancamentoRemessa.ts b/src/database/migrations/1725545232586-LancamentoRemessa.ts new file mode 100644 index 00000000..97aa506b --- /dev/null +++ b/src/database/migrations/1725545232586-LancamentoRemessa.ts @@ -0,0 +1,20 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class LancamentoRemessa1725545232586 implements MigrationInterface { + name = 'LancamentoRemessa1725545232586' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "lancamento" DROP CONSTRAINT "FK_Lancamento_transacao_ManyToOne"`); + await queryRunner.query(`ALTER TABLE "lancamento" RENAME COLUMN "transacaoId" TO "itemTransacaoId"`); + await queryRunner.query(`ALTER TABLE "lancamento" ADD CONSTRAINT "UQ_Lancamento_itemTransacao" UNIQUE ("itemTransacaoId")`); + await queryRunner.query(`ALTER TABLE "lancamento" ADD CONSTRAINT "FK_Lancamento_itemTransacao_OneToOne" FOREIGN KEY ("itemTransacaoId") REFERENCES "item_transacao"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "lancamento" DROP CONSTRAINT "FK_Lancamento_itemTransacao_OneToOne"`); + await queryRunner.query(`ALTER TABLE "lancamento" DROP CONSTRAINT "UQ_Lancamento_itemTransacao"`); + await queryRunner.query(`ALTER TABLE "lancamento" RENAME COLUMN "itemTransacaoId" TO "transacaoId"`); + await queryRunner.query(`ALTER TABLE "lancamento" ADD CONSTRAINT "FK_Lancamento_transacao_ManyToOne" FOREIGN KEY ("transacaoId") REFERENCES "transacao"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); + } + +} diff --git a/src/database/migrations/1725895301219-Lancamento3v.ts b/src/database/migrations/1725895301219-Lancamento3v.ts new file mode 100644 index 00000000..0088e4ed --- /dev/null +++ b/src/database/migrations/1725895301219-Lancamento3v.ts @@ -0,0 +1,14 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class Lancamento3v1725895301219 implements MigrationInterface { + name = 'Lancamento3v1725895301219' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "lancamento" ADD "is_pago" boolean NOT NULL DEFAULT false`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "lancamento" DROP COLUMN "is_pago"`); + } + +} diff --git a/src/database/seeds/user/user-seed-data.service.ts b/src/database/seeds/user/user-seed-data.service.ts index 28a75116..664c670e 100644 --- a/src/database/seeds/user/user-seed-data.service.ts +++ b/src/database/seeds/user/user-seed-data.service.ts @@ -54,216 +54,219 @@ LIMIT 5 } return [ - // // Dev team - // { - // fullName: 'Alexander Rivail Ruiz', - // email: 'ruiz.smtr@gmail.com', - // password: this.generateRandomPassword(), - // role: new Role(RoleEnum.admin), - // status: new Status(StatusEnum.active), - // }, - // { - // fullName: 'Bernardo Marcos', - // email: 'bernardo.marcos64@gmail.com', - // password: this.generateRandomPassword(), - // role: new Role(RoleEnum.admin), - // status: new Status(StatusEnum.active), - // }, - // { - // fullName: 'Gabriel Fortes', - // email: 'glfg01092001@gmail.com', - // password: this.generateRandomPassword(), - // role: new Role(RoleEnum.admin), - // status: new Status(StatusEnum.active), - // }, - // { - // fullName: 'Raphael Baptista Rivas de Araújo', - // email: 'raphaelrivasbra@gmail.com', - // password: this.generateRandomPassword(), - // role: new Role(RoleEnum.admin), - // status: new Status(StatusEnum.active), - // }, - // { - // fullName: 'William FL 2007', - // email: 'williamfl2007@gmail.com', - // password: this.generateRandomPassword(), - // role: new Role(RoleEnum.admin), - // status: new Status(StatusEnum.active), - // }, - // // Admin CCT - // { - // fullName: 'Felipe Ribeiro', - // email: 'felipe.ribeiro@prefeitura.rio', - // password: this.generateRandomPassword(), - // role: new Role(RoleEnum.admin), - // status: new Status(StatusEnum.active), - // }, - // { - // fullName: 'Monique Lopes', - // email: 'monique.lopes@prefeitura.rio', - // password: this.generateRandomPassword(), - // role: new Role(RoleEnum.admin), - // status: new Status(StatusEnum.active), - // }, + // Dev team + { + fullName: 'Alexander Rivail Ruiz', + email: 'ruiz.smtr@gmail.com', + password: this.generateRandomPassword(), + role: new Role(RoleEnum.master), + status: new Status(StatusEnum.active), + }, + { + fullName: 'Bernardo Marcos', + email: 'bernardo.marcos64@gmail.com', + password: this.generateRandomPassword(), + role: new Role(RoleEnum.master), + status: new Status(StatusEnum.active), + }, + { + fullName: 'Gabriel Fortes', + email: 'glfg01092001@gmail.com', + password: this.generateRandomPassword(), + role: new Role(RoleEnum.master), + status: new Status(StatusEnum.active), + }, + { + fullName: 'Raphael Baptista Rivas de Araújo', + email: 'raphaelrivasbra@gmail.com', + password: this.generateRandomPassword(), + role: new Role(RoleEnum.master), + status: new Status(StatusEnum.active), + }, + { + fullName: 'William FL 2007', + email: 'williamfl2007@gmail.com', + password: this.generateRandomPassword(), + role: new Role(RoleEnum.master), + status: new Status(StatusEnum.active), + }, - // // Admins - // { - // fullName: 'Jéssica Venancio Teixeira Cardoso Simas', - // email: 'jessicasimas.smtr@gmail.com', - // password: this.generateRandomPassword(), - // role: new Role(RoleEnum.admin), - // status: new Status(StatusEnum.active), - // }, - // { - // fullName: 'Leandro Almeida dos Santos', - // email: 'leandro.smtr@gmail.com', - // password: this.generateRandomPassword(), - // role: new Role(RoleEnum.admin), - // status: new Status(StatusEnum.active), - // }, - // { - // fullName: 'Carolina Maia dos Santos', - // email: 'cms.smtr@gmail.com', - // password: this.generateRandomPassword(), - // role: new Role(RoleEnum.admin), - // status: new Status(StatusEnum.active), - // }, - // { - // fullName: 'Carolina Salomé Kingma Orlando', - // email: 'carolkingma2013@gmail.com', - // password: this.generateRandomPassword(), - // role: new Role(RoleEnum.admin), - // status: new Status(StatusEnum.active), - // }, - // { - // fullName: 'Lauro Costa Silvestre', - // email: 'laurosilvestre.smtr@gmail.com', - // password: this.generateRandomPassword(), - // role: new Role(RoleEnum.admin), - // status: new Status(StatusEnum.active), - // }, - // { - // fullName: 'Admin bigquery', - // email: 'admin_bigquery@example.com', - // password: 'secret', - // role: { id: RoleEnum.admin } as Role, - // status: { id: StatusEnum.active } as Status, - // }, + // Admin Master + { + fullName: 'Lauro Costa Silvestre', + email: 'laurosilvestre.smtr@gmail.com', + password: this.generateRandomPassword(), + role: new Role(RoleEnum.master), + status: new Status(StatusEnum.active), + }, - // // Usuários lançamento financeiro - // { - // fullName: 'Usuário lançamento', - // email: 'ruizalexander@id.uff.br', - // password: this.generateRandomPassword(), - // role: new Role(RoleEnum.lancador_financeiro), - // status: new Status(StatusEnum.active), - // }, - // { - // fullName: 'João Victor Spala', - // email: 'jvspala.smtr@gmail.com', - // password: this.generateRandomPassword(), - // role: new Role(RoleEnum.lancador_financeiro), - // status: new Status(StatusEnum.active), - // }, - // { - // fullName: 'Marcia Marques', - // email: 'marques.mcc@gmail.com', - // password: this.generateRandomPassword(), - // role: new Role(RoleEnum.lancador_financeiro), - // status: new Status(StatusEnum.active), - // }, - // { - // fullName: 'Letícia Correa', - // email: 'leticiacorrea.smtr@gmail.com', - // password: this.generateRandomPassword(), - // role: new Role(RoleEnum.lancador_financeiro), - // status: new Status(StatusEnum.active), - // }, - // { - // fullName: 'Louise Sanglard', - // email: 'louise.smtr@gmail.com', - // password: this.generateRandomPassword(), - // role: new Role(RoleEnum.lancador_financeiro), - // status: new Status(StatusEnum.active), - // }, - // { - // fullName: 'Simone Costa', - // email: 'simonecosta.smtr@gmail.com', - // password: this.generateRandomPassword(), - // role: new Role(RoleEnum.admin_finan), - // status: new Status(StatusEnum.active), - // }, - // { - // fullName: 'Luciana Fernandes', - // email: 'lucianafernandes.smtr@gmail.com', - // password: this.generateRandomPassword(), - // role: new Role(RoleEnum.admin_finan), - // status: new Status(StatusEnum.active), - // }, + // Admin Vanzeiro + { + fullName: 'Felipe Ribeiro', + email: 'felipe.ribeiro@prefeitura.rio', + password: this.generateRandomPassword(), + role: new Role(RoleEnum.admin), + status: new Status(StatusEnum.active), + }, + { + fullName: 'Monique Lopes', + email: 'monique.lopes@prefeitura.rio', + password: this.generateRandomPassword(), + role: new Role(RoleEnum.admin), + status: new Status(StatusEnum.active), + }, + { + fullName: 'Jéssica Venancio Teixeira Cardoso Simas', + email: 'jessicasimas.smtr@gmail.com', + password: this.generateRandomPassword(), + role: new Role(RoleEnum.admin), + status: new Status(StatusEnum.active), + }, + { + fullName: 'Leandro Almeida dos Santos', + email: 'leandro.smtr@gmail.com', + password: this.generateRandomPassword(), + role: new Role(RoleEnum.admin), + status: new Status(StatusEnum.active), + }, + { + fullName: 'Carolina Maia dos Santos', + email: 'cms.smtr@gmail.com', + password: this.generateRandomPassword(), + role: new Role(RoleEnum.admin), + status: new Status(StatusEnum.active), + }, + { + fullName: 'Carolina Salomé Kingma Orlando', + email: 'carolkingma2013@gmail.com', + password: this.generateRandomPassword(), + role: new Role(RoleEnum.admin), + status: new Status(StatusEnum.active), + }, + { + fullName: 'Admin bigquery', + email: 'admin_bigquery@example.com', + password: 'secret', + role: { id: RoleEnum.admin } as Role, + status: { id: StatusEnum.active } as Status, + }, - // //apagar após teste - // { - // fullName: 'alex test seed approval', - // email: 'approval@example.com', - // password: 'secret', - // permitCode: '', - // cpfCnpj: this.cpfSamples?.[0], - // role: { id: RoleEnum.lancador_financeiro } as Role, - // status: { id: StatusEnum.active } as Status, - // }, - // { - // fullName: 'teste approval', - // email: 'approval@example.com', - // password: 'secret', - // permitCode: '', - // cpfCnpj: this.cpfSamples?.[0], - // role: { id: RoleEnum.lancador_financeiro } as Role, - // status: { id: StatusEnum.active } as Status, - // }, - // { - // fullName: 'teste launcher', - // email: 'launcher@example.com', - // password: 'secret', - // permitCode: '', - // cpfCnpj: this.cpfSamples?.[0], - // role: { id: RoleEnum.aprovador_financeiro } as Role, - // status: { id: StatusEnum.active } as Status, - // }, + // Admin lançamento + { + fullName: 'Simone Costa', + email: 'simonecosta.smtr@gmail.com', + password: this.generateRandomPassword(), + role: new Role(RoleEnum.admin_finan), + status: new Status(StatusEnum.active), + }, + { + fullName: 'Luciana Fernandes', + email: 'lucianafernandes.smtr@gmail.com', + password: this.generateRandomPassword(), + role: new Role(RoleEnum.admin_finan), + status: new Status(StatusEnum.active), + }, + + // Usuário lançamento + { + fullName: 'Usuário lançamento', + email: 'ruizalexander@id.uff.br', + password: this.generateRandomPassword(), + role: new Role(RoleEnum.lancador_financeiro), + status: new Status(StatusEnum.active), + }, + { + fullName: 'João Victor Spala', + email: 'jvspala.smtr@gmail.com', + password: this.generateRandomPassword(), + role: new Role(RoleEnum.lancador_financeiro), + status: new Status(StatusEnum.active), + }, + { + fullName: 'Marcia Marques', + email: 'marques.mcc@gmail.com', + password: this.generateRandomPassword(), + role: new Role(RoleEnum.lancador_financeiro), + status: new Status(StatusEnum.active), + }, + { + fullName: 'Letícia Correa', + email: 'leticiacorrea.smtr@gmail.com', + password: this.generateRandomPassword(), + role: new Role(RoleEnum.lancador_financeiro), + status: new Status(StatusEnum.active), + }, + { + fullName: 'Louise Sanglard', + email: 'louise.smtr@gmail.com', + password: this.generateRandomPassword(), + role: new Role(RoleEnum.lancador_financeiro), + status: new Status(StatusEnum.active), + }, + + //apagar após teste + { + fullName: 'alex test seed approval', + email: 'approval@example.com', + password: 'secret', + permitCode: '', + cpfCnpj: this.cpfSamples?.[0], + role: { id: RoleEnum.lancador_financeiro } as Role, + status: { id: StatusEnum.active } as Status, + }, + { + fullName: 'teste approval', + email: 'approval@example.com', + password: 'secret', + permitCode: '', + cpfCnpj: this.cpfSamples?.[0], + role: { id: RoleEnum.lancador_financeiro } as Role, + status: { id: StatusEnum.active } as Status, + }, + { + fullName: 'teste launcher', + email: 'launcher@example.com', + password: 'secret', + permitCode: '', + cpfCnpj: this.cpfSamples?.[0], + role: { id: RoleEnum.aprovador_financeiro } as Role, + status: { id: StatusEnum.active } as Status, + }, // Development only ...(this.nodeEnv() === 'local' || this.nodeEnv() === 'test' ? ([ - // // Vanzeiros - // { - // fullName: 'Henrique Santos Template Cpf Van', - // email: 'henrique@example.com', - // password: 'secret', - // permitCode: '213890329890312', - // cpfCnpj: this.cpfSamples?.[0], - // role: { id: RoleEnum.user } as Role, - // status: { id: StatusEnum.active } as Status, - // bankAccount: '000000000567', - // bankAccountDigit: '8', - // }, - // { - // fullName: 'Márcia Clara Template Cnpj Brt etc', - // email: 'marcia@example.com', - // password: 'secret', - // permitCode: '319274392832023', - // cpfCnpj: this.cnpjSamples?.[0], - // role: { id: RoleEnum.user } as Role, - // status: { id: StatusEnum.active } as Status, - // }, - // // Roles - // { - // fullName: 'Usuário Teste dos Santos Oliveira', - // email: 'user@example.com', - // password: 'secret', - // permitCode: '213890329890749', - // cpfCnpj: this.cpfSamples?.[0], - // role: { id: RoleEnum.user } as Role, - // status: { id: StatusEnum.active } as Status, - // }, + // Vanzeiros + { + fullName: 'Henrique Santos Template Cpf Van', + email: 'henrique@example.com', + password: 'secret', + permitCode: '213890329890312', + cpfCnpj: this.cpfSamples?.[0], + role: { id: RoleEnum.user } as Role, + status: { id: StatusEnum.active } as Status, + bankAccount: '000000000567', + bankAccountDigit: '8', + }, + { + fullName: 'Márcia Clara Template Cnpj Brt etc', + email: 'marcia@example.com', + password: 'secret', + permitCode: '319274392832023', + cpfCnpj: this.cnpjSamples?.[0], + role: { id: RoleEnum.user } as Role, + status: { id: StatusEnum.active } as Status, + }, + // Roles + { + fullName: 'Usuário Teste dos Santos Oliveira', + email: 'user@example.com', + password: 'secret', + permitCode: '213890329890749', + cpfCnpj: this.cpfSamples?.[0], + role: { id: RoleEnum.user } as Role, + status: { id: StatusEnum.active } as Status, + }, { fullName: 'Administrador Financeiro Teste', email: 'finan.admin@example.com', diff --git a/src/lancamento/entities/lancamento.entity.ts b/src/lancamento/entities/lancamento.entity.ts index 5f35758e..5cab91a8 100644 --- a/src/lancamento/entities/lancamento.entity.ts +++ b/src/lancamento/entities/lancamento.entity.ts @@ -1,14 +1,15 @@ import { ApiProperty } from '@nestjs/swagger'; import { Exclude, Expose, Transform } from 'class-transformer'; import { ClienteFavorecido } from 'src/cnab/entity/cliente-favorecido.entity'; -import { Transacao } from 'src/cnab/entity/pagamento/transacao.entity'; +import { ItemTransacao } from 'src/cnab/entity/pagamento/item-transacao.entity'; import { User } from 'src/users/entities/user.entity'; import { EntityHelper } from 'src/utils/entity-helper'; import { asStringOrNumber } from 'src/utils/pipe-utils'; import { AfterLoad, Column, CreateDateColumn, DeepPartial, Entity, JoinColumn, JoinTable, ManyToMany, ManyToOne, OneToOne, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'; import { LancamentoInputDto } from '../dtos/lancamento-input.dto'; import { LancamentoAutorizacao } from './lancamento-autorizacao.entity'; -import { ItemTransacao } from 'src/cnab/entity/pagamento/item-transacao.entity'; +import { Ocorrencia } from 'src/cnab/entity/pagamento/ocorrencia.entity'; +import { DetalheA } from 'src/cnab/entity/pagamento/detalhe-a.entity'; export interface ILancamento { id: number; @@ -53,26 +54,6 @@ export class Lancamento extends EntityHelper implements ILancamento { }); } - updateFromInputDto(dto: LancamentoInputDto) { - this.valor = dto.valor; - this.data_ordem = dto.data_ordem; - this.data_lancamento = dto.data_lancamento; - this.algoritmo = dto.algoritmo; - if (dto.glosa !== undefined) { - this.glosa = dto.glosa; - } - if (dto.recurso !== undefined) { - this.recurso = dto.recurso; - } - if (dto.anexo !== undefined) { - this.anexo = dto.anexo; - } - this.valor = dto.valor; - this.numero_processo = dto.numero_processo; - this.clienteFavorecido = new ClienteFavorecido({ id: dto.id_cliente_favorecido }); - this.autor = new User(dto.author); - } - @Expose() @PrimaryGeneratedColumn({ primaryKeyConstraintName: 'PK_Lancamento_id' }) id: number; @@ -91,7 +72,11 @@ export class Lancamento extends EntityHelper implements ILancamento { @Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' }) data_lancamento: Date; - /** Geração de Remessa */ + /** + * Geração de Remessa + * + * uniqueConstraintName: `UQ_Lancamento_itemTransacao` + */ @ApiProperty({ description: 'ItemTransação do CNAB remessa associado a este Lançamento' }) @OneToOne(() => ItemTransacao, { nullable: true }) @JoinColumn({ foreignKeyConstraintName: 'FK_Lancamento_itemTransacao_OneToOne' }) @@ -140,15 +125,15 @@ export class Lancamento extends EntityHelper implements ILancamento { @Column({ type: 'numeric', nullable: false, default: 0 }) anexo: number; - // @Column({ type: 'numeric', nullable: false }) - // valor_a_pagar: number; - @Column({ type: 'varchar', nullable: false }) numero_processo: string; @Column({ type: Boolean, nullable: false, default: false }) is_autorizado: boolean; + @Column({ type: Boolean, nullable: false, default: false }) + is_pago: boolean; + @Exclude() @CreateDateColumn() createdAt: Date; @@ -157,6 +142,9 @@ export class Lancamento extends EntityHelper implements ILancamento { @UpdateDateColumn() updatedAt: Date; + /** Coluna virtual para consultar as ocorrências */ + ocorrencias: Ocorrencia[] = []; + @AfterLoad() setReadValues() { this.glosa = asStringOrNumber(this.glosa); @@ -165,6 +153,7 @@ export class Lancamento extends EntityHelper implements ILancamento { this.anexo = asStringOrNumber(this.anexo); this.valor = asStringOrNumber(this.valor); this.autorizacoes = this.autorizacoes || []; + // if (this.itemTransacao?.itemTransacaoAgrupado.de) } getIsAutorizado(): boolean { @@ -184,4 +173,84 @@ export class Lancamento extends EntityHelper implements ILancamento { hasAutorizadoPor(userId: number): boolean { return Boolean(this.autorizacoes.find((u) => u.id == userId)); } + + updateFromInputDto(dto: LancamentoInputDto) { + this.valor = dto.valor; + this.data_ordem = dto.data_ordem; + this.data_lancamento = dto.data_lancamento; + this.algoritmo = dto.algoritmo; + if (dto.glosa !== undefined) { + this.glosa = dto.glosa; + } + if (dto.recurso !== undefined) { + this.recurso = dto.recurso; + } + if (dto.anexo !== undefined) { + this.anexo = dto.anexo; + } + this.valor = dto.valor; + this.numero_processo = dto.numero_processo; + this.clienteFavorecido = new ClienteFavorecido({ id: dto.id_cliente_favorecido }); + this.autor = new User(dto.author); + } + + public static getSqlFields(table?: string, castType?: 'sql' | 'entity'): Record { + return { + id: `${table ? `${table}.` : ''}"id"`, + valor: `${table ? `${table}.` : ''}"valor"`, // number, + data_ordem: `${table ? `${table}.` : ''}"data_ordem"`, // Date, + data_pgto: `${table ? `${table}.` : ''}"data_pgto"`, // Date | null, + data_lancamento: `${table ? `${table}.` : ''}"data_lancamento"`, // Date, + itemTransacao: `${table ? `${table}.` : ''}"itemTransacao"`, // ItemTransacao, + autorizacoes: `${table ? `${table}.` : ''}"autorizacoes"`, //User [], + autor: `${table ? `${table}.` : ''}"autor"`, // User, + clienteFavorecido: `${table ? `${table}.` : ''}"clienteFavorecido"`, // ClienteFavorecido, + algoritmo: `${table ? `${table}.` : ''}"algoritmo"`, // number, + glosa: `${table ? `${table}.` : ''}"glosa"`, // number, + recurso: `${table ? `${table}.` : ''}"recurso"`, // number, + anexo: `${table ? `${table}.` : ''}"anexo"`, // number, + numero_processo: `${table ? `${table}.` : ''}"numero_processo"`, // string, + createdAt: `${table ? `${table}.` : ''}"createdAt"`, // Date, + updatedAt: `${table ? `${table}.` : ''}"updatedAt"`, // Date, + }; + } + + public static sqlFieldType: Record = { + id: 'INT', + valor: 'NUMERIC', + data_ordem: 'TIMSTAMP', + data_pgto: 'TIMSTAMP', + data_lancamento: 'TIMSTAMP', + itemTransacao: 'INT', + autorizacoes: '', + autor: '', + clienteFavorecido: '', + algoritmo: 'NUMERIC', + glosa: 'NUMERIC', + recurso: 'NUMERIC', + anexo: 'NUMERIC', + numero_processo: 'VARCHAR', + createdAt: 'TIMSTAMP', + updatedAt: 'TIMSTAMP', + }; + public static getSqlFieldEntityType(userAlias = 'u', itemTransacaoAlias = 'it'): Record { + return { + id: 'INT', + valor: 'NUMERIC', + data_ordem: 'TIMSTAMP', + data_pgto: 'TIMSTAMP', + data_lancamento: 'TIMSTAMP', + itemTransacao: 'INT', + autorizacoes: '', + autor: '', + clienteFavorecido: '', + algoritmo: 'FLOAT', + glosa: 'FLOAT', + recurso: 'FLOAT', + anexo: 'FLOAT', + numero_processo: 'VARCHAR', + createdAt: 'TIMSTAMP', + updatedAt: 'TIMSTAMP', + }; + } } diff --git a/src/lancamento/lancamento.repository.ts b/src/lancamento/lancamento.repository.ts index 3f51d9e7..fe7d4efa 100644 --- a/src/lancamento/lancamento.repository.ts +++ b/src/lancamento/lancamento.repository.ts @@ -18,24 +18,51 @@ export class LancamentoRepository { return this.lancamentoRepository.save(entity, options); } - findOne(options: FindOneOptions): Promise { - return this.lancamentoRepository.findOne(options); + async findOne(options: FindOneOptions): Promise { + let qb = this.lancamentoRepository + .createQueryBuilder('lancamento') // + .leftJoinAndSelect('lancamento.autorizacoes', 'autorizacoes') + .leftJoinAndSelect('lancamento.autor', 'autor') + .leftJoinAndSelect('lancamento.itemTransacao', 'itemTransacao') + .leftJoinAndSelect('itemTransacao.itemTransacaoAgrupado', 'itemTransacaoAgrupado') + .leftJoin('detalhe_a', 'detalheA', 'detalheA.itemTransacaoAgrupadoId = itemTransacaoAgrupado.id') + .leftJoinAndMapMany('lancamento.ocorrencias', 'ocorrencia', 'ocorrencia', 'ocorrencia.detalheAId = detalheA.id'); + + if (options?.where) { + qb = qb.where(options.where); + } + + return await qb.getOne(); } - async getOne(where: FindOneOptions): Promise { - const found = await this.lancamentoRepository.findOne(where); + async getOne(options: FindOneOptions): Promise { + const found = await this.findOne(options); if (!found) { throw new HttpException('Lancamento não encontrado', HttpStatus.NOT_FOUND); } return found; } - findMany(options?: FindManyOptions | undefined): Promise { - return this.lancamentoRepository.find(options); + async findMany(options?: FindManyOptions | undefined): Promise { + let qb = this.lancamentoRepository + .createQueryBuilder('lancamento') // + .leftJoinAndSelect('lancamento.autorizacoes', 'autorizacoes') + .leftJoinAndSelect('lancamento.autor', 'autor') + .leftJoinAndSelect('lancamento.itemTransacao', 'itemTransacao') + .leftJoinAndSelect('itemTransacao.itemTransacaoAgrupado', 'itemTransacaoAgrupado') + .leftJoin('detalhe_a', 'detalheA', 'detalheA.itemTransacaoAgrupadoId = itemTransacaoAgrupado.id') + .leftJoinAndMapMany('lancamento.ocorrencias', 'ocorrencia', 'ocorrencia', 'ocorrencia.detalheAId = detalheA.id'); + + if (options?.where) { + qb = qb.where(options?.where); + } + qb = qb.orderBy('lancamento.id', 'DESC'); + const ret = await qb.getMany(); + return ret; } getAll(): Promise { - return this.lancamentoRepository.find(); + return this.findMany(); } delete(id: number): Promise { diff --git a/src/lancamento/lancamento.service.ts b/src/lancamento/lancamento.service.ts index c419dc54..2de7b8f5 100644 --- a/src/lancamento/lancamento.service.ts +++ b/src/lancamento/lancamento.service.ts @@ -54,13 +54,13 @@ export class LancamentoService { } async findByStatus(isAutorizado: boolean): Promise { - const lancamentos = await this.lancamentoRepository.findMany({ where: { is_autorizado: isAutorizado }, relations: ['autorizacoes'] as (keyof ILancamento)[] }); + const lancamentos = await this.lancamentoRepository.findMany({ where: { is_autorizado: isAutorizado } }); return lancamentos; } async getValorAutorizado(month: number, period: number, year: number) { const [startDate, endDate] = this.getMonthDateRange(year, month, period); - const autorizados = await this.lancamentoRepository.findMany({ where: { data_lancamento: Between(startDate, endDate) }, relations: ['autorizacoes'] as (keyof ILancamento)[] }); + const autorizados = await this.lancamentoRepository.findMany({ where: { data_lancamento: Between(startDate, endDate) } }); const autorizadoSum = autorizados.reduce((sum, lancamento) => sum + lancamento.valor, 0); const resp = { valor_autorizado: autorizadoSum }; return resp; @@ -69,7 +69,7 @@ export class LancamentoService { async create(dto: LancamentoInputDto): Promise { const lancamento = await this.validateLancamentoDto(dto); const created = await this.lancamentoRepository.save(this.lancamentoRepository.create(lancamento)); - const getCreated = await this.lancamentoRepository.getOne({ where: { clienteFavorecido: { id: created.clienteFavorecido.id } }, relations: ['autorizacoes'] as (keyof ILancamento)[] }); + const getCreated = await this.lancamentoRepository.getOne({ where: { id: created.id } }); return getCreated; } @@ -84,7 +84,7 @@ export class LancamentoService { } async autorizarPagamento(userId: number, lancamentoId: string, AutorizaLancamentoDto: AutorizaLancamentoDto): Promise { - const lancamento = await this.lancamentoRepository.findOne({ where: { id: parseInt(lancamentoId) }, relations: ['autorizacoes'] as (keyof ILancamento)[] }); + const lancamento = await this.lancamentoRepository.findOne({ where: { id: parseInt(lancamentoId) } }); if (!lancamento) { throw new HttpException('Lançamento não encontrado.', HttpStatus.NOT_FOUND); } @@ -109,19 +109,19 @@ export class LancamentoService { } async update(id: number, updateDto: LancamentoInputDto): Promise { - const lancamento = await this.lancamentoRepository.findOne({ where: { id }, relations: ['autorizacoes'] as (keyof ILancamento)[] }); + const lancamento = await this.lancamentoRepository.findOne({ where: { id } }); if (!lancamento) { throw new NotFoundException(`Lançamento com ID ${id} não encontrado.`); } lancamento.updateFromInputDto(updateDto); await this.lancamentoRepository.save(lancamento); - const updated = await this.lancamentoRepository.getOne({ where: { id: lancamento.id }, relations: ['autorizacoes'] as (keyof ILancamento)[] }); + const updated = await this.lancamentoRepository.getOne({ where: { id: lancamento.id } }); this.logger.log(`Lancamento #${updated.id} atualizado por ${updated.clienteFavorecido.nome}.`); return updated; } async getById(id: number): Promise { - const lancamento = await this.lancamentoRepository.findOne({ where: { id }, relations: ['autorizacoes'] as (keyof ILancamento)[] }); + const lancamento = await this.lancamentoRepository.findOne({ where: { id } }); if (!lancamento) { throw new NotFoundException(`Lançamento com ID ${id} não encontrado.`); } diff --git a/src/roles/roles.enum.ts b/src/roles/roles.enum.ts index 10fb9b93..2e860c5b 100644 --- a/src/roles/roles.enum.ts +++ b/src/roles/roles.enum.ts @@ -1,5 +1,6 @@ export enum RoleEnum { 'master' = 0, + /** Admin vanzeiros */ 'admin' = 1, 'user' = 2, 'lancador_financeiro' = 3, diff --git a/src/transacao-view/transacao-view.entity.ts b/src/transacao-view/transacao-view.entity.ts index 3fcc7049..5bab76bf 100644 --- a/src/transacao-view/transacao-view.entity.ts +++ b/src/transacao-view/transacao-view.entity.ts @@ -38,61 +38,11 @@ export class TransacaoView { if (transacao) { Object.assign(this, transacao); if (transacao.arquivoPublicacao) { - this.arquivoPublicacao = new ArquivoPublicacao(transacao.arquivoPublicacao) + this.arquivoPublicacao = new ArquivoPublicacao(transacao.arquivoPublicacao); } } } - public static getSqlFields(table?: string): Record { - return { - id: `${table ? `${table}.` : ''}"id"`, - datetimeTransacao: `${table ? `${table}.` : ''}"datetimeTransacao"`, - datetimeProcessamento: `${table ? `${table}.` : ''}"datetimeProcessamento"`, - datetimeCaptura: `${table ? `${table}.` : ''}"datetimeCaptura"`, - modo: `${table ? `${table}.` : ''}"modo"`, - idConsorcio: `${table ? `${table}.` : ''}"idConsorcio"`, - nomeConsorcio: `${table ? `${table}.` : ''}"nomeConsorcio"`, - idOperadora: `${table ? `${table}.` : ''}"idOperadora"`, - nomeOperadora: `${table ? `${table}.` : ''}"nomeOperadora"`, - idTransacao: `${table ? `${table}.` : ''}"idTransacao"`, - tipoPagamento: `${table ? `${table}.` : ''}"tipoPagamento"`, - tipoTransacao: `${table ? `${table}.` : ''}"tipoTransacao"`, - tipoGratuidade: `${table ? `${table}.` : ''}"tipoGratuidade"`, - valorTransacao: `${table ? `${table}.` : ''}"valorTransacao"`, - valorPago: `${table ? `${table}.` : ''}"valorPago"`, - operadoraCpfCnpj: `${table ? `${table}.` : ''}"operadoraCpfCnpj"`, - consorcioCnpj: `${table ? `${table}.` : ''}"consorcioCnpj"`, - arquivoPublicacao: `${table ? `${table}.` : ''}"arquivoPublicacaoId"`, - itemTransacaoAgrupadoId: `${table ? `${table}.` : ''}"itemTransacaoAgrupadoId"`, - createdAt: `${table ? `${table}.` : ''}"createdAt"`, - updatedAt: `${table ? `${table}.` : ''}"updatedAt"`, - }; - } - - public static sqlFieldTypes: Record = { - id: 'INT', - datetimeTransacao: 'TIMESTAMP', - datetimeProcessamento: 'TIMESTAMP', - datetimeCaptura: 'TIMESTAMP', - modo: 'VARCHAR', - idConsorcio: 'VARCHAR', - nomeConsorcio: 'VARCHAR', - idOperadora: 'VARCHAR', - nomeOperadora: 'VARCHAR', - idTransacao: 'VARCHAR', - tipoPagamento: 'VARCHAR', - tipoTransacao: 'VARCHAR', - tipoGratuidade: 'VARCHAR', - valorTransacao: 'NUMERIC', - valorPago: 'NUMERIC', - operadoraCpfCnpj: 'VARCHAR', - consorcioCnpj: 'VARCHAR', - arquivoPublicacao: 'INT', - itemTransacaoAgrupadoId: 'INT', - createdAt: 'TIMESTAMP', - updatedAt: 'TIMESTAMP', - }; - @PrimaryGeneratedColumn({ primaryKeyConstraintName: 'PK_TransacaoView_id', }) @@ -176,6 +126,56 @@ export class TransacaoView { } } + public static getSqlFields(table?: string): Record { + return { + id: `${table ? `${table}.` : ''}"id"`, + datetimeTransacao: `${table ? `${table}.` : ''}"datetimeTransacao"`, + datetimeProcessamento: `${table ? `${table}.` : ''}"datetimeProcessamento"`, + datetimeCaptura: `${table ? `${table}.` : ''}"datetimeCaptura"`, + modo: `${table ? `${table}.` : ''}"modo"`, + idConsorcio: `${table ? `${table}.` : ''}"idConsorcio"`, + nomeConsorcio: `${table ? `${table}.` : ''}"nomeConsorcio"`, + idOperadora: `${table ? `${table}.` : ''}"idOperadora"`, + nomeOperadora: `${table ? `${table}.` : ''}"nomeOperadora"`, + idTransacao: `${table ? `${table}.` : ''}"idTransacao"`, + tipoPagamento: `${table ? `${table}.` : ''}"tipoPagamento"`, + tipoTransacao: `${table ? `${table}.` : ''}"tipoTransacao"`, + tipoGratuidade: `${table ? `${table}.` : ''}"tipoGratuidade"`, + valorTransacao: `${table ? `${table}.` : ''}"valorTransacao"`, + valorPago: `${table ? `${table}.` : ''}"valorPago"`, + operadoraCpfCnpj: `${table ? `${table}.` : ''}"operadoraCpfCnpj"`, + consorcioCnpj: `${table ? `${table}.` : ''}"consorcioCnpj"`, + arquivoPublicacao: `${table ? `${table}.` : ''}"arquivoPublicacaoId"`, + itemTransacaoAgrupadoId: `${table ? `${table}.` : ''}"itemTransacaoAgrupadoId"`, + createdAt: `${table ? `${table}.` : ''}"createdAt"`, + updatedAt: `${table ? `${table}.` : ''}"updatedAt"`, + }; + } + + public static sqlFieldTypes: Record = { + id: 'INT', + datetimeTransacao: 'TIMESTAMP', + datetimeProcessamento: 'TIMESTAMP', + datetimeCaptura: 'TIMESTAMP', + modo: 'VARCHAR', + idConsorcio: 'VARCHAR', + nomeConsorcio: 'VARCHAR', + idOperadora: 'VARCHAR', + nomeOperadora: 'VARCHAR', + idTransacao: 'VARCHAR', + tipoPagamento: 'VARCHAR', + tipoTransacao: 'VARCHAR', + tipoGratuidade: 'VARCHAR', + valorTransacao: 'NUMERIC', + valorPago: 'NUMERIC', + operadoraCpfCnpj: 'VARCHAR', + consorcioCnpj: 'VARCHAR', + arquivoPublicacao: 'INT', + itemTransacaoAgrupadoId: 'INT', + createdAt: 'TIMESTAMP', + updatedAt: 'TIMESTAMP', + }; + public static fromBigqueryTransacao(bq: BigqueryTransacao) { return new TransacaoView({ datetimeCaptura: asStringDate(bq.datetime_captura), @@ -232,29 +232,4 @@ export class TransacaoView { }); return revenue; } - - getProperties() { - return { - id: this.id, - datetimeTransacao: this.datetimeTransacao, - datetimeProcessamento: this.datetimeProcessamento, - datetimeCaptura: this.datetimeCaptura, - modo: this.modo, - idConsorcio: this.idConsorcio, - nomeConsorcio: this.nomeConsorcio, - idOperadora: this.idOperadora, - nomeOperadora: this.nomeOperadora, - idTransacao: this.idTransacao, - tipoPagamento: this.tipoPagamento, - tipoTransacao: this.tipoTransacao, - tipoGratuidade: this.tipoGratuidade, - valorTransacao: this.valorTransacao, - valorPago: this.valorPago, - operadoraCpfCnpj: this.operadoraCpfCnpj, - consorcioCnpj: this.consorcioCnpj, - arquivoPublicacao: this.arquivoPublicacao, - createdAt: this.createdAt, - updatedAt: this.updatedAt, - }; - } } diff --git a/src/transacao-view/transacao-view.repository.ts b/src/transacao-view/transacao-view.repository.ts index 362c6b08..f388d635 100644 --- a/src/transacao-view/transacao-view.repository.ts +++ b/src/transacao-view/transacao-view.repository.ts @@ -64,8 +64,7 @@ export class TransacaoViewRepository { ita.id AS ita_id, tv."datetimeTransacao", tv."datetimeProcessamento", - ita."dataOrdem", - ita."dataCaptura" + ita."dataOrdem" FROM item_transacao_agrupado ita INNER JOIN detalhe_a da ON da."itemTransacaoAgrupadoId" = ita.id INNER JOIN item_transacao it ON it."itemTransacaoAgrupadoId" = ita.id From e5e502d9b572214b2c0608418f696d59984c0905 Mon Sep 17 00:00:00 2001 From: Raphael Rivas Date: Mon, 9 Sep 2024 16:10:42 -0300 Subject: [PATCH 17/29] wip: save retorno Lancamento --- src/cnab/cnab.service.ts | 5 +- .../item-transacao-agrupado.entity.ts | 2 + .../pagamento/all-pagador-dict.interface.ts | 2 + .../service/arquivo-publicacao.service.ts | 13 ++-- .../service/pagamento/detalhe-a.service.ts | 3 + .../pagamento/remessa-retorno.service.ts | 45 +++++++++++++- src/lancamento/entities/lancamento.entity.ts | 61 +++++++++---------- src/lancamento/lancamento.repository.ts | 29 ++++++--- src/lancamento/lancamento.service.ts | 32 +++++++--- 9 files changed, 130 insertions(+), 62 deletions(-) diff --git a/src/cnab/cnab.service.ts b/src/cnab/cnab.service.ts index 34b46f4e..e680acae 100644 --- a/src/cnab/cnab.service.ts +++ b/src/cnab/cnab.service.ts @@ -614,10 +614,11 @@ export class CnabService { const retorno104 = parseCnab240Pagamento(cnabString); cnabs.push(cnabName); await this.remessaRetornoService.saveRetorno(retorno104); - await this.sftpService.moveToBackup(cnabName, SftpBackupFolder.RetornoSuccess, cnabString, folder); + // 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); + break; } catch (error) { 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); @@ -625,7 +626,7 @@ export class CnabService { this.logger.log('Retorno não encontrado, abortando tarefa.', METHOD); return; } - await this.sftpService.moveToBackup(cnabName, SftpBackupFolder.RetornoFailure, cnabString, folder); + // await this.sftpService.moveToBackup(cnabName, SftpBackupFolder.RetornoFailure, cnabString, folder); failed.push(cnabName); } const cnab = await this.sftpService.getFirstCnabRetorno(folder); diff --git a/src/cnab/entity/pagamento/item-transacao-agrupado.entity.ts b/src/cnab/entity/pagamento/item-transacao-agrupado.entity.ts index 3ad2f1f5..9b743cf3 100644 --- a/src/cnab/entity/pagamento/item-transacao-agrupado.entity.ts +++ b/src/cnab/entity/pagamento/item-transacao-agrupado.entity.ts @@ -7,6 +7,7 @@ import { OrdemPagamentoDto } from 'src/cnab/dto/pagamento/ordem-pagamento.dto'; import { yearMonthDayToDate } from 'src/utils/date-utils'; import { ItemTransacao } from './item-transacao.entity'; import { TransacaoAgrupado } from './transacao-agrupado.entity'; +import { Exclude } from 'class-transformer'; /** * Representa um destinatário, a ser pago pelo remetente (TransacaoAgrupado). @@ -23,6 +24,7 @@ import { TransacaoAgrupado } from './transacao-agrupado.entity'; */ @Entity() export class ItemTransacaoAgrupado extends EntityHelper { + @Exclude() private readonly FKs = ['transacaoAgrupado', 'clienteFavorecido']; constructor(dto?: DeepPartial) { diff --git a/src/cnab/interfaces/pagamento/all-pagador-dict.interface.ts b/src/cnab/interfaces/pagamento/all-pagador-dict.interface.ts index 338decf7..499ed398 100644 --- a/src/cnab/interfaces/pagamento/all-pagador-dict.interface.ts +++ b/src/cnab/interfaces/pagamento/all-pagador-dict.interface.ts @@ -3,6 +3,8 @@ import { Pagador } from "src/cnab/entity/pagamento/pagador.entity"; export interface AllPagadorDict { /** * Only for items from Lancamento + * + * Conta de Estabilização Tarifária dos Transportes */ cett: Pagador, diff --git a/src/cnab/service/arquivo-publicacao.service.ts b/src/cnab/service/arquivo-publicacao.service.ts index f0ee9914..26b9240b 100644 --- a/src/cnab/service/arquivo-publicacao.service.ts +++ b/src/cnab/service/arquivo-publicacao.service.ts @@ -1,17 +1,15 @@ import { Injectable, Logger } from '@nestjs/common'; import { addDays, isDate, isThursday, nextFriday, startOfDay } from 'date-fns'; -import { asNumber } from 'src/utils/pipe-utils'; +import { compactQuery } from 'src/utils/console-utils'; +import { EntityHelper } from 'src/utils/entity-helper'; import { DeepPartial, FindManyOptions, QueryRunner } from 'typeorm'; -import { ArquivoPublicacao, IArquivoPublicacao } from '../entity/arquivo-publicacao.entity'; +import { ArquivoPublicacaoBigqueryDTO } from '../dto/arquivo-publicacao-bigquery.dto'; +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, 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'; -import { ArquivoPublicacaoBigqueryDTO } from '../dto/arquivo-publicacao-bigquery.dto'; -import { compactQuery } from 'src/utils/console-utils'; export type ArquivoPublicacaoFields = 'savePublicacaoRetorno'; @@ -79,6 +77,9 @@ export class ArquivoPublicacaoService { } public async updateManyRaw(dtos: DeepPartial[], fields: ArquivoPublicacaoFields, queryRunner: QueryRunner): Promise { + if (!dtos.length) { + return []; + } let fieldNames: (keyof ArquivoPublicacao)[] = []; if (fields == 'savePublicacaoRetorno') { fieldNames = ['id', 'isPago', 'valorRealEfetivado', 'dataEfetivacao', 'dataGeracaoRetorno']; diff --git a/src/cnab/service/pagamento/detalhe-a.service.ts b/src/cnab/service/pagamento/detalhe-a.service.ts index 02bfc595..36db309e 100644 --- a/src/cnab/service/pagamento/detalhe-a.service.ts +++ b/src/cnab/service/pagamento/detalhe-a.service.ts @@ -83,6 +83,9 @@ export class DetalheAService { nsr: Number(r.detalheA.nsr.value), ocorrenciasCnab: r.detalheA.ocorrencias.value.trim() || headerLotePgto.ocorrencias.value.trim() || headerArq.ocorrenciaCobrancaSemPapel.value.trim(), }); + if (r.detalheA.numeroDocumentoEmpresa.convertedValue == 1766) { + let a = 1; + } return await this.detalheARepository.save(saveDetalheA); } } else { diff --git a/src/cnab/service/pagamento/remessa-retorno.service.ts b/src/cnab/service/pagamento/remessa-retorno.service.ts index 8dfec722..3a771c83 100644 --- a/src/cnab/service/pagamento/remessa-retorno.service.ts +++ b/src/cnab/service/pagamento/remessa-retorno.service.ts @@ -43,6 +43,7 @@ import { HeaderLoteService } from './header-lote.service'; import { ItemTransacaoAgrupadoService } from './item-transacao-agrupado.service'; import { ItemTransacaoService } from './item-transacao.service'; import { ClienteFavorecido } from 'src/cnab/entity/cliente-favorecido.entity'; +import { LancamentoService } from 'src/lancamento/lancamento.service'; const sc = structuredClone; const PgtoRegistros = Cnab104PgtoTemplates.file104.registros; @@ -53,7 +54,23 @@ export class RemessaRetornoService { timestamp: true, }); - constructor(private arquivoPublicacaoService: ArquivoPublicacaoService, private detalheAConfService: DetalheAConfService, private detalheAService: DetalheAService, private detalheBConfService: DetalheBConfService, private detalheBService: DetalheBService, private headerArquivoConfService: HeaderArquivoConfService, private headerArquivoService: HeaderArquivoService, private headerLoteConfService: HeaderLoteConfService, private headerLoteService: HeaderLoteService, private itemTransacaoAgService: ItemTransacaoAgrupadoService, private itemTransacaoService: ItemTransacaoService, private ocorrenciaService: OcorrenciaService, private transacaoViewService: TransacaoViewService, private dataSource: DataSource) {} + constructor( + private arquivoPublicacaoService: ArquivoPublicacaoService, // + private lancamentoService: LancamentoService, + private detalheAConfService: DetalheAConfService, + private detalheAService: DetalheAService, + private detalheBConfService: DetalheBConfService, + private detalheBService: DetalheBService, + private headerArquivoConfService: HeaderArquivoConfService, + private headerArquivoService: HeaderArquivoService, + private headerLoteConfService: HeaderLoteConfService, + private headerLoteService: HeaderLoteService, + private itemTransacaoAgService: ItemTransacaoAgrupadoService, + private itemTransacaoService: ItemTransacaoService, + private ocorrenciaService: OcorrenciaService, + private transacaoViewService: TransacaoViewService, + private dataSource: DataSource, + ) {} public async saveHeaderArquivoDTO(transacaoAg: TransacaoAgrupado, isConference: boolean): Promise { let headerArquivoDTO; @@ -445,6 +462,9 @@ export class RemessaRetornoService { const logRegistro = `HeaderArquivo: ${cnab.headerArquivo.nsa.convertedValue}, lote: ${cnabLote.headerLote.codigoRegistro.value}`; // Save Detalhes + if (registro.detalheA.numeroDocumentoEmpresa.convertedValue == 1766) { + let a = 1; + } detalheAUpdated = await this.detalheAService.saveRetornoFrom104(cnab.headerArquivo, cnabLote.headerLote, registro, dataEfetivacao); if (!detalheAUpdated) { const numeroDocumento = registro.detalheA.numeroDocumentoEmpresa.convertedValue; @@ -481,7 +501,8 @@ export class RemessaRetornoService { */ public async compareRemessaToRetorno(detalheA: DetalheA, queryRunner: QueryRunner): Promise { await this.saveOcorrenciasDetalheA(detalheA, queryRunner); - await this.savePublicacaoRetorno(detalheA, queryRunner); + await this.saveRetornoPublicacao(detalheA, queryRunner); + await this.saveRetornoLancamento(detalheA, queryRunner); } async saveOcorrenciasDetalheA(detalheARetorno: DetalheA, queryRunner: QueryRunner) { @@ -501,7 +522,8 @@ export class RemessaRetornoService { await this.ocorrenciaService.saveMany(ocorrencias, queryRunner); } - async savePublicacaoRetorno(detalheARetorno: DetalheA, queryRunner: QueryRunner) { + /** Se o retorno for de Publicacao, atualiza */ + async saveRetornoPublicacao(detalheARetorno: DetalheA, queryRunner: QueryRunner) { const publicacoes = await this.arquivoPublicacaoService.findManyRaw({ itemTransacaoAgrupadoId: [detalheARetorno.itemTransacaoAgrupado.id], }); @@ -519,6 +541,23 @@ export class RemessaRetornoService { await this.arquivoPublicacaoService.updateManyRaw(publicacoes, 'savePublicacaoRetorno', queryRunner); } + /** Se o retorno for de Lancamento, atualiza */ + async saveRetornoLancamento(detalheARetorno: DetalheA, queryRunner: QueryRunner) { + const lancamentos = await this.lancamentoService.find({ detalheA: { id: [detalheARetorno.id] } }); + for (const lancamento of lancamentos) { + if (lancamento.id == 2) { + let a = 1; + } + lancamento.is_pago = detalheARetorno.isPago(); + if (lancamento.is_pago) { + lancamento.data_pgto = detalheARetorno.dataEfetivacao; + } else { + lancamento.data_pgto = null; + } + } + await this.lancamentoService.updateManyRaw(lancamentos, ['is_pago', 'data_pgto'], queryRunner); + } + async compareTransacaoViewPublicacao(detalheA: DetalheA, queryRunner: QueryRunner) { const transacoesView = await this.getTransacoesViewWeek(subDays(detalheA.dataVencimento, 8), detalheA.dataVencimento); const publicacoesDetalhe = await this.arquivoPublicacaoService.getPublicacoesWeek(detalheA); diff --git a/src/lancamento/entities/lancamento.entity.ts b/src/lancamento/entities/lancamento.entity.ts index 5cab91a8..d60f7085 100644 --- a/src/lancamento/entities/lancamento.entity.ts +++ b/src/lancamento/entities/lancamento.entity.ts @@ -26,6 +26,8 @@ export interface ILancamento { recurso: number; anexo: number; numero_processo: string; + is_autorizado: boolean; + is_pago: boolean; createdAt: Date; updatedAt: Date; } @@ -77,9 +79,11 @@ export class Lancamento extends EntityHelper implements ILancamento { * * uniqueConstraintName: `UQ_Lancamento_itemTransacao` */ + // @Exclude() @ApiProperty({ description: 'ItemTransação do CNAB remessa associado a este Lançamento' }) @OneToOne(() => ItemTransacao, { nullable: true }) @JoinColumn({ foreignKeyConstraintName: 'FK_Lancamento_itemTransacao_OneToOne' }) + // @Transform(({ value }) => (!value ? null : (it = value as ItemTransacao) => ({ id: it.id, itemTransacaoAgrupado: { id: it.itemTransacaoAgrupado.id } }))) itemTransacao: ItemTransacao; @ManyToMany(() => User, (user) => user) @@ -90,12 +94,7 @@ export class Lancamento extends EntityHelper implements ILancamento { synchronize: false, // We use LancamentoAutorizacao to generate migration }) @Expose({ name: 'autorizado_por' }) - @Transform(({ value }) => - (value as User[]).map((u) => ({ - id: u.id, - nome: u.fullName, - })), - ) + @Transform(({ value }) => (value as User[]).map((u) => ({ id: u.id, nome: u.fullName }))) autorizacoes: User[]; /** O autor mais recente da criação/modificação/autorização do Lançamento */ @@ -142,6 +141,18 @@ export class Lancamento extends EntityHelper implements ILancamento { @UpdateDateColumn() updatedAt: Date; + /** Coluna virtual */ + @Transform(({ value }) => + value === null + ? null + : ((da = value as DetalheA) => ({ + id: da.id, + ocorrenciasCnab: da.ocorrenciasCnab, + numeroDocumentoEmpresa: da.numeroDocumentoEmpresa, + }))(), + ) + detalheA: DetalheA | null = null; + /** Coluna virtual para consultar as ocorrências */ ocorrencias: Ocorrencia[] = []; @@ -194,7 +205,7 @@ export class Lancamento extends EntityHelper implements ILancamento { this.autor = new User(dto.author); } - public static getSqlFields(table?: string, castType?: 'sql' | 'entity'): Record { + public static getSqlFields(table?: string, castType?: boolean): Record { return { id: `${table ? `${table}.` : ''}"id"`, valor: `${table ? `${table}.` : ''}"valor"`, // number, @@ -210,17 +221,19 @@ export class Lancamento extends EntityHelper implements ILancamento { recurso: `${table ? `${table}.` : ''}"recurso"`, // number, anexo: `${table ? `${table}.` : ''}"anexo"`, // number, numero_processo: `${table ? `${table}.` : ''}"numero_processo"`, // string, + is_autorizado: `${table ? `${table}.` : ''}"is_autorizado"`, // boolean, + is_pago: `${table ? `${table}.` : ''}"is_pago"`, // boolean, createdAt: `${table ? `${table}.` : ''}"createdAt"`, // Date, updatedAt: `${table ? `${table}.` : ''}"updatedAt"`, // Date, }; } - public static sqlFieldType: Record = { + public static sqlFieldTypes: Record = { id: 'INT', valor: 'NUMERIC', - data_ordem: 'TIMSTAMP', - data_pgto: 'TIMSTAMP', - data_lancamento: 'TIMSTAMP', + data_ordem: 'TIMESTAMP', + data_pgto: 'TIMESTAMP', + data_lancamento: 'TIMESTAMP', itemTransacao: 'INT', autorizacoes: '', autor: '', @@ -230,27 +243,9 @@ export class Lancamento extends EntityHelper implements ILancamento { recurso: 'NUMERIC', anexo: 'NUMERIC', numero_processo: 'VARCHAR', - createdAt: 'TIMSTAMP', - updatedAt: 'TIMSTAMP', + is_pago: 'BOOLEAN', + is_autorizado: 'BOOLEAN', + createdAt: 'TIMESTAMP', + updatedAt: 'TIMESTAMP', }; - public static getSqlFieldEntityType(userAlias = 'u', itemTransacaoAlias = 'it'): Record { - return { - id: 'INT', - valor: 'NUMERIC', - data_ordem: 'TIMSTAMP', - data_pgto: 'TIMSTAMP', - data_lancamento: 'TIMSTAMP', - itemTransacao: 'INT', - autorizacoes: '', - autor: '', - clienteFavorecido: '', - algoritmo: 'FLOAT', - glosa: 'FLOAT', - recurso: 'FLOAT', - anexo: 'FLOAT', - numero_processo: 'VARCHAR', - createdAt: 'TIMSTAMP', - updatedAt: 'TIMSTAMP', - }; - } } diff --git a/src/lancamento/lancamento.repository.ts b/src/lancamento/lancamento.repository.ts index fe7d4efa..119a3d43 100644 --- a/src/lancamento/lancamento.repository.ts +++ b/src/lancamento/lancamento.repository.ts @@ -2,6 +2,7 @@ import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { DeepPartial, DeleteResult, FindManyOptions, FindOneOptions, Repository, SaveOptions } from 'typeorm'; import { Lancamento } from './entities/lancamento.entity'; +import { DetalheA } from 'src/cnab/entity/pagamento/detalhe-a.entity'; @Injectable() export class LancamentoRepository { @@ -25,13 +26,14 @@ export class LancamentoRepository { .leftJoinAndSelect('lancamento.autor', 'autor') .leftJoinAndSelect('lancamento.itemTransacao', 'itemTransacao') .leftJoinAndSelect('itemTransacao.itemTransacaoAgrupado', 'itemTransacaoAgrupado') - .leftJoin('detalhe_a', 'detalheA', 'detalheA.itemTransacaoAgrupadoId = itemTransacaoAgrupado.id') + // .leftJoin('detalhe_a', 'detalheA', 'detalheA.itemTransacaoAgrupadoId = itemTransacaoAgrupado.id') + .leftJoinAndMapOne('lancamento.detalheA', 'detalhe_a', 'detalheA', 'detalheA.itemTransacaoAgrupadoId = itemTransacaoAgrupado.id') .leftJoinAndMapMany('lancamento.ocorrencias', 'ocorrencia', 'ocorrencia', 'ocorrencia.detalheAId = detalheA.id'); - - if (options?.where) { - qb = qb.where(options.where); + + if (options?.where) { + qb = qb.where(options.where); } - + return await qb.getOne(); } @@ -43,18 +45,25 @@ export class LancamentoRepository { return found; } - async findMany(options?: FindManyOptions | undefined): Promise { + async findMany(options?: FindManyOptions | undefined, andWhere?: { detalheA: { id: number[] } }): Promise { + let whereCount = 0; let qb = this.lancamentoRepository .createQueryBuilder('lancamento') // .leftJoinAndSelect('lancamento.autorizacoes', 'autorizacoes') .leftJoinAndSelect('lancamento.autor', 'autor') .leftJoinAndSelect('lancamento.itemTransacao', 'itemTransacao') .leftJoinAndSelect('itemTransacao.itemTransacaoAgrupado', 'itemTransacaoAgrupado') - .leftJoin('detalhe_a', 'detalheA', 'detalheA.itemTransacaoAgrupadoId = itemTransacaoAgrupado.id') + // .leftJoin('detalhe_a', 'detalheA', 'detalheA.itemTransacaoAgrupadoId = itemTransacaoAgrupado.id') + .leftJoinAndMapOne('lancamento.detalheA', 'detalhe_a', 'detalheA', 'detalheA.itemTransacaoAgrupadoId = itemTransacaoAgrupado.id') .leftJoinAndMapMany('lancamento.ocorrencias', 'ocorrencia', 'ocorrencia', 'ocorrencia.detalheAId = detalheA.id'); - - if (options?.where) { - qb = qb.where(options?.where); + + if (options?.where) { + qb = qb[!whereCount ? 'where' : 'andWhere'](options?.where); + whereCount += 1; + } + if (andWhere) { + qb = qb[!whereCount ? 'where' : 'andWhere']('detalheA.id IN(:daId)', { daId: andWhere.detalheA.id.join(',') }); + whereCount += 1; } qb = qb.orderBy('lancamento.id', 'DESC'); const ret = await qb.getMany(); diff --git a/src/lancamento/lancamento.service.ts b/src/lancamento/lancamento.service.ts index 2de7b8f5..b0a195b5 100644 --- a/src/lancamento/lancamento.service.ts +++ b/src/lancamento/lancamento.service.ts @@ -6,11 +6,13 @@ import { ClienteFavorecidoService } from 'src/cnab/service/cliente-favorecido.se import { UsersService } from 'src/users/users.service'; import { CustomLogger } from 'src/utils/custom-logger'; import { CommonHttpException } from 'src/utils/http-exception/common-http-exception'; -import { Between, IsNull } from 'typeorm'; +import { Between, DeepPartial, IsNull, QueryRunner } from 'typeorm'; import { AutorizaLancamentoDto } from './dtos/AutorizaLancamentoDto'; import { LancamentoInputDto } from './dtos/lancamento-input.dto'; import { ILancamento, Lancamento } from './entities/lancamento.entity'; import { LancamentoRepository } from './lancamento.repository'; +import { EntityHelper } from 'src/utils/entity-helper'; +import { compactQuery } from 'src/utils/console-utils'; @Injectable() export class LancamentoService { @@ -35,20 +37,23 @@ export class LancamentoService { }); } - async find(args?: { mes?: number; periodo?: number; ano?: number; autorizado?: boolean }): Promise { + async find(args?: { mes?: number; periodo?: number; ano?: number; autorizado?: boolean; detalheA?: { id: number[] } }): Promise { /** [startDate, endDate] */ let dateRange: [Date, Date] | null = null; if (args?.mes && args?.periodo && args?.ano) { dateRange = this.getMonthDateRange(args.ano, args.mes, args?.periodo); } - const lancamentos = await this.lancamentoRepository.findMany({ - where: { - ...(dateRange ? { data_lancamento: Between(...dateRange) } : {}), - ...(args?.autorizado !== undefined ? { is_autorizado: args.autorizado } : {}), + const lancamentos = await this.lancamentoRepository.findMany( + { + where: { + ...(dateRange ? { data_lancamento: Between(...dateRange) } : {}), + ...(args?.autorizado !== undefined ? { is_autorizado: args.autorizado } : {}), + }, + relations: ['autorizacoes'] as (keyof ILancamento)[], }, - relations: ['autorizacoes'] as (keyof ILancamento)[], - }); + args?.detalheA && { detalheA: args.detalheA }, + ); return lancamentos; } @@ -120,6 +125,17 @@ export class LancamentoService { return updated; } + public async updateManyRaw(dtos: DeepPartial[], fields: (keyof ILancamento)[], queryRunner: QueryRunner): Promise { + if (!dtos.length) { + return []; + } + const id: keyof ILancamento = 'id'; + const updatedAt: keyof ILancamento = 'updatedAt'; + const query = EntityHelper.getQueryUpdate('lancamento', dtos, fields, Lancamento.sqlFieldTypes, id, updatedAt); + await queryRunner.manager.query(compactQuery(query)); + return dtos.map((dto) => new Lancamento(dto)); + } + async getById(id: number): Promise { const lancamento = await this.lancamentoRepository.findOne({ where: { id } }); if (!lancamento) { From 896ccba8516f21146648f5ec391a177dbd36b734 Mon Sep 17 00:00:00 2001 From: Raphael Rivas Date: Mon, 9 Sep 2024 16:29:37 -0300 Subject: [PATCH 18/29] fix: merge main --- src/cnab/cnab.service.ts | 1 + src/ticket-revenues/ticket-revenues.repository.ts | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/cnab/cnab.service.ts b/src/cnab/cnab.service.ts index d245beef..e0a17b80 100644 --- a/src/cnab/cnab.service.ts +++ b/src/cnab/cnab.service.ts @@ -21,6 +21,7 @@ import { formatDateInterval } from 'src/utils/date-utils'; import { CommonHttpException } from 'src/utils/http-exception/common-http-exception'; import { formatErrMsg } from 'src/utils/log-utils'; import { asNumber } from 'src/utils/pipe-utils'; +import { isContent } from 'src/utils/type-utils'; import { DataSource, DeepPartial, QueryRunner } from 'typeorm'; import { HeaderArquivoDTO } from './dto/pagamento/header-arquivo.dto'; import { HeaderLoteDTO } from './dto/pagamento/header-lote.dto'; diff --git a/src/ticket-revenues/ticket-revenues.repository.ts b/src/ticket-revenues/ticket-revenues.repository.ts index 36426b3c..6cf154de 100644 --- a/src/ticket-revenues/ticket-revenues.repository.ts +++ b/src/ticket-revenues/ticket-revenues.repository.ts @@ -1,8 +1,10 @@ -import { Injectable, Logger, NotImplementedException } from '@nestjs/common'; +import { Injectable, Logger } from '@nestjs/common'; import { InjectDataSource } from '@nestjs/typeorm'; import { endOfDay, isToday, startOfDay } from 'date-fns'; import { ArquivoPublicacaoService } from 'src/cnab/service/arquivo-publicacao.service'; -import { TransacaoViewService } from 'src/transacao-bq/transacao-view.service'; +import { TransacaoView } from 'src/transacao-view/transacao-view.entity'; +import { TransacaoViewService } from 'src/transacao-view/transacao-view.service'; +import { compactQuery } from 'src/utils/console-utils'; import { formatDateYMD } from 'src/utils/date-utils'; import { getPagination } from 'src/utils/get-pagination'; import { getPaymentDates } from 'src/utils/payment-date-utils'; @@ -13,8 +15,6 @@ import { TicketRevenueDTO } from './dtos/ticket-revenue.dto'; import { TicketRevenuesGroupDto } from './dtos/ticket-revenues-group.dto'; import { ITRGetMeIndividualValidArgs } from './interfaces/tr-get-me-individual-args.interface'; import { ITRGetMeIndividualResponse } from './interfaces/tr-get-me-individual-response.interface'; -import { TransacaoView } from 'src/transacao-bq/transacao-view.entity'; -import { compactQuery } from 'src/utils/console-utils'; export interface TicketRevenuesIndividualOptions { where: { From 8ca8fd6fcf363be1c01ee5c88143f2106d3c9a90 Mon Sep 17 00:00:00 2001 From: Raphael Rivas Date: Mon, 9 Sep 2024 16:46:24 -0300 Subject: [PATCH 19/29] feat: read retorno --- package.json | 2 +- src/cnab/entity/pagamento/detalhe-a.entity.ts | 2 +- src/cnab/repository/pagamento/detalhe-a.repository.ts | 6 +++--- src/cnab/service/pagamento/detalhe-a.service.ts | 5 +---- src/cnab/service/pagamento/remessa-retorno.service.ts | 6 ------ src/lancamento/lancamento.repository.ts | 1 - 6 files changed, 6 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index 7ef964e8..140e091c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "api-cct", - "version": "0.12.7", + "version": "0.12.8", "description": "", "author": "", "private": true, diff --git a/src/cnab/entity/pagamento/detalhe-a.entity.ts b/src/cnab/entity/pagamento/detalhe-a.entity.ts index 2c883ffd..5ba35872 100644 --- a/src/cnab/entity/pagamento/detalhe-a.entity.ts +++ b/src/cnab/entity/pagamento/detalhe-a.entity.ts @@ -148,7 +148,7 @@ export class DetalheA extends EntityHelper { public isPago() { const errors = Ocorrencia.getErrorCodesFromString(this.ocorrenciasCnab || ''); - return errors.length === 0; + return errors.length === 0 && Boolean(this.ocorrenciasCnab?.length); } public static getOcorrenciaErrors(detalhes: DetalheA[]) { diff --git a/src/cnab/repository/pagamento/detalhe-a.repository.ts b/src/cnab/repository/pagamento/detalhe-a.repository.ts index 8d8e1479..981eb9ce 100644 --- a/src/cnab/repository/pagamento/detalhe-a.repository.ts +++ b/src/cnab/repository/pagamento/detalhe-a.repository.ts @@ -93,9 +93,9 @@ export class DetalheARepository { 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", + da."ocorrenciasCnab", da."periodoVencimento", da."quantidadeMoeda"::FLOAT, + da."quantidadeParcelas", da."tipoMoeda", da."updatedAt", da."valorLancamento"::FLOAT, + da."valorRealEfetivado"::FLOAT, json_build_object( 'id', da."itemTransacaoAgrupadoId", 'transacaoAgrupado', json_build_object( diff --git a/src/cnab/service/pagamento/detalhe-a.service.ts b/src/cnab/service/pagamento/detalhe-a.service.ts index 36db309e..2640fcf0 100644 --- a/src/cnab/service/pagamento/detalhe-a.service.ts +++ b/src/cnab/service/pagamento/detalhe-a.service.ts @@ -62,7 +62,7 @@ export class DetalheAService { numeroDocumentoEmpresa: r.detalheA.numeroDocumentoEmpresa.convertedValue, }); if (detalheA) { - if (detalheA.ocorrenciasCnab === undefined || detalheA.ocorrenciasCnab === '' || detalheA.ocorrenciasCnab !== r.detalheA.ocorrencias.value.trim()) { + if (detalheA.ocorrenciasCnab === undefined || detalheA.ocorrenciasCnab === '' || detalheA.ocorrenciasCnab !== r.detalheA.ocorrencias.value.trim() || !detalheA.dataEfetivacao) { const saveDetalheA = new DetalheADTO({ id: detalheA.id, loteServico: Number(r.detalheA.loteServico.value), @@ -83,9 +83,6 @@ export class DetalheAService { nsr: Number(r.detalheA.nsr.value), ocorrenciasCnab: r.detalheA.ocorrencias.value.trim() || headerLotePgto.ocorrencias.value.trim() || headerArq.ocorrenciaCobrancaSemPapel.value.trim(), }); - if (r.detalheA.numeroDocumentoEmpresa.convertedValue == 1766) { - let a = 1; - } return await this.detalheARepository.save(saveDetalheA); } } else { diff --git a/src/cnab/service/pagamento/remessa-retorno.service.ts b/src/cnab/service/pagamento/remessa-retorno.service.ts index 3a771c83..e18855cc 100644 --- a/src/cnab/service/pagamento/remessa-retorno.service.ts +++ b/src/cnab/service/pagamento/remessa-retorno.service.ts @@ -462,9 +462,6 @@ export class RemessaRetornoService { const logRegistro = `HeaderArquivo: ${cnab.headerArquivo.nsa.convertedValue}, lote: ${cnabLote.headerLote.codigoRegistro.value}`; // Save Detalhes - if (registro.detalheA.numeroDocumentoEmpresa.convertedValue == 1766) { - let a = 1; - } detalheAUpdated = await this.detalheAService.saveRetornoFrom104(cnab.headerArquivo, cnabLote.headerLote, registro, dataEfetivacao); if (!detalheAUpdated) { const numeroDocumento = registro.detalheA.numeroDocumentoEmpresa.convertedValue; @@ -545,9 +542,6 @@ export class RemessaRetornoService { async saveRetornoLancamento(detalheARetorno: DetalheA, queryRunner: QueryRunner) { const lancamentos = await this.lancamentoService.find({ detalheA: { id: [detalheARetorno.id] } }); for (const lancamento of lancamentos) { - if (lancamento.id == 2) { - let a = 1; - } lancamento.is_pago = detalheARetorno.isPago(); if (lancamento.is_pago) { lancamento.data_pgto = detalheARetorno.dataEfetivacao; diff --git a/src/lancamento/lancamento.repository.ts b/src/lancamento/lancamento.repository.ts index 119a3d43..cc070a7e 100644 --- a/src/lancamento/lancamento.repository.ts +++ b/src/lancamento/lancamento.repository.ts @@ -2,7 +2,6 @@ import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { DeepPartial, DeleteResult, FindManyOptions, FindOneOptions, Repository, SaveOptions } from 'typeorm'; import { Lancamento } from './entities/lancamento.entity'; -import { DetalheA } from 'src/cnab/entity/pagamento/detalhe-a.entity'; @Injectable() export class LancamentoRepository { From 7b4d66671ad525411685c48d8911d6415fb36847 Mon Sep 17 00:00:00 2001 From: Raphael Rivas Date: Tue, 10 Sep 2024 13:14:03 -0300 Subject: [PATCH 20/29] feat: remessa retorno with status --- .../bank-statements.controller.ts | 2 +- src/cnab/cnab-manutencao.controller.ts | 8 +-- src/cnab/cnab.controller.ts | 2 +- src/cnab/cnab.service.ts | 15 ++---- .../pagamento/remessa-retorno.service.ts | 5 +- .../1725912379556-LancamentoStatus.ts | 14 ++++++ src/lancamento/entities/lancamento.entity.ts | 39 ++++++++------- .../enums/lancamento-status.enum.ts | 7 +++ src/lancamento/lancamento.controller.ts | 11 ++++- src/lancamento/lancamento.repository.ts | 49 +++++++++++++++---- src/lancamento/lancamento.service.ts | 24 +++++++-- ...parse-list.pipe.ts => parse-array.pipe.ts} | 13 ++++- ...lidate-enum.pipe.ts => parse-enum.pipe.ts} | 0 13 files changed, 137 insertions(+), 52 deletions(-) create mode 100644 src/database/migrations/1725912379556-LancamentoStatus.ts create mode 100644 src/lancamento/enums/lancamento-status.enum.ts rename src/utils/pipes/{parse-list.pipe.ts => parse-array.pipe.ts} (73%) rename src/utils/pipes/{validate-enum.pipe.ts => parse-enum.pipe.ts} (100%) diff --git a/src/bank-statements/bank-statements.controller.ts b/src/bank-statements/bank-statements.controller.ts index e3f62185..f7bad1e7 100644 --- a/src/bank-statements/bank-statements.controller.ts +++ b/src/bank-statements/bank-statements.controller.ts @@ -10,7 +10,7 @@ import { TimeIntervalEnum } from 'src/utils/enums/time-interval.enum'; import { getPagination } from 'src/utils/get-pagination'; import { IRequest } from 'src/utils/interfaces/request.interface'; import { ParseNumberPipe } from 'src/utils/pipes/parse-number.pipe'; -import { ParseEnumPipe } from 'src/utils/pipes/validate-enum.pipe'; +import { ParseEnumPipe } from 'src/utils/pipes/parse-enum.pipe'; import { DateQueryParams } from 'src/utils/query-param/date.query-param'; import { PaginationQueryParams } from 'src/utils/query-param/pagination.query-param'; import { getRequestLog } from 'src/utils/request-utils'; diff --git a/src/cnab/cnab-manutencao.controller.ts b/src/cnab/cnab-manutencao.controller.ts index 795f29f3..32515195 100644 --- a/src/cnab/cnab-manutencao.controller.ts +++ b/src/cnab/cnab-manutencao.controller.ts @@ -1,4 +1,4 @@ -import { BadRequestException, Controller, Get, HttpCode, HttpStatus, ParseArrayPipe, Query, UseGuards } from '@nestjs/common'; +import { BadRequestException, Controller, Get, HttpCode, HttpStatus, Query, UseGuards } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; import { ApiBearerAuth, ApiOperation, ApiQuery, ApiTags } from '@nestjs/swagger'; import { Roles } from 'src/roles/roles.decorator'; @@ -6,8 +6,8 @@ import { RoleEnum } from 'src/roles/roles.enum'; import { RolesGuard } from 'src/roles/roles.guard'; import { ApiDescription } from 'src/utils/api-param/description-api-param'; import { CustomLogger } from 'src/utils/custom-logger'; +import { ParseArrayPipe } from 'src/utils/pipes/parse-array.pipe'; import { ParseDatePipe } from 'src/utils/pipes/parse-date.pipe'; -import { ParseListPipe } from 'src/utils/pipes/parse-list.pipe'; import { ParseNumberPipe } from 'src/utils/pipes/parse-number.pipe'; import { CnabService } from './cnab.service'; @@ -143,7 +143,7 @@ export class CnabManutencaoController { async getSyncTransacaoViewOrdemPgto( @Query('dataOrdemInicial', new ParseDatePipe({ transform: true, optional: true })) dataOrdemInicial: Date | undefined, // @Query('dataOrdemFinal', new ParseDatePipe({ transform: true, optional: true })) dataOrdemFinal: Date | undefined, - @Query('nomeFavorecido', new ParseListPipe({ transform: true, optional: true })) nomeFavorecido: string[] | undefined, + @Query('nomeFavorecido', new ParseArrayPipe({ transform: true, optional: true })) nomeFavorecido: string[] | undefined, ) { const dataOrdem_between = dataOrdemInicial && dataOrdemFinal && ([dataOrdemInicial, dataOrdemFinal] as [Date, Date]); return await this.cnabService.syncTransacaoViewOrdemPgto({ dataOrdem_between, nomeFavorecido }); @@ -163,7 +163,7 @@ export class CnabManutencaoController { @Query('dataOrdemInicial', new ParseDatePipe({ transform: true })) dataOrdemInicial: any, // @Query('dataOrdemFinal', new ParseDatePipe({ transform: true })) dataOrdemFinal: any, @Query('consorcio') consorcio: string | undefined, - @Query('idTransacao', new ParseArrayPipe({ items: String, separator: ',', optional: true })) idTransacao: string[], // + @Query('idTransacao', new ParseArrayPipe({ optional: true })) idTransacao: string[], // ) { const _dataOrdemInicial: Date = dataOrdemInicial; const _dataOrdemFinal: Date = dataOrdemFinal; diff --git a/src/cnab/cnab.controller.ts b/src/cnab/cnab.controller.ts index 960e8732..d2dbfe7d 100644 --- a/src/cnab/cnab.controller.ts +++ b/src/cnab/cnab.controller.ts @@ -8,7 +8,7 @@ import { ApiDescription } from 'src/utils/api-param/description-api-param'; import { CustomLogger } from 'src/utils/custom-logger'; import { ParseDatePipe } from 'src/utils/pipes/parse-date.pipe'; import { ParseNumberPipe } from 'src/utils/pipes/parse-number.pipe'; -import { ParseEnumPipe } from 'src/utils/pipes/validate-enum.pipe'; +import { ParseEnumPipe } from 'src/utils/pipes/parse-enum.pipe'; import { ClienteFavorecido } from './entity/cliente-favorecido.entity'; import { GetClienteFavorecidoConsorcioEnum } from './enums/get-cliente-favorecido-consorcio.enum'; import { ArquivoPublicacaoService } from './service/arquivo-publicacao.service'; diff --git a/src/cnab/cnab.service.ts b/src/cnab/cnab.service.ts index e0a17b80..6dad3235 100644 --- a/src/cnab/cnab.service.ts +++ b/src/cnab/cnab.service.ts @@ -55,6 +55,7 @@ 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 { LancamentoStatus } from 'src/lancamento/enums/lancamento-status.enum'; /** * User cases for CNAB and Payments @@ -403,13 +404,7 @@ export class CnabService { id: transacaoAg.id, status: { id: TransacaoStatusEnum.created }, }, - ...(ordem.consorcio.length || ordem.operadora.length - ? /** Se for Jaé, agrupa por vanzeiro ou empresa */ - ordem.consorcio === 'STPC' || ordem.consorcio === 'STPL' - ? { idOperadora: ordem.idOperadora } - : { idConsorcio: ordem.idConsorcio } - : /** Se for Lançamento, agrupa por favorecido e dataOrdem */ - { itemTransacoes: { clienteFavorecido: { cpfCnpj: ordem.favorecidoCpfCnpj as string } }, dataOrdem: startOfDay(new Date((ordem.dataOrdem))) }), + ...(ordem.consorcio.length || ordem.operadora.length ? (/** Se for Jaé, agrupa por vanzeiro ou empresa */ ordem.consorcio === 'STPC' || ordem.consorcio === 'STPL' ? { idOperadora: ordem.idOperadora } : { idConsorcio: ordem.idConsorcio }) : /** Se for Lançamento, agrupa por favorecido e dataOrdem */ { itemTransacoes: { clienteFavorecido: { cpfCnpj: ordem.favorecidoCpfCnpj as string } }, dataOrdem: startOfDay(new Date(ordem.dataOrdem)) }), }, }); if (itemAg) { @@ -566,10 +561,8 @@ export class CnabService { private async updateStatusRemessa(headerArquivoDTO: HeaderArquivoDTO, cnabHeaderArquivo: CnabHeaderArquivo104, transacaoAgId: number) { await this.remessaRetornoService.updateHeaderArquivoDTOFrom104(headerArquivoDTO, cnabHeaderArquivo); - await this.transacaoAgService.save({ - id: transacaoAgId, - status: TransacaoStatus.fromEnum(TransacaoStatusEnum.remessa), - }); + await this.transacaoAgService.save({ id: transacaoAgId, status: TransacaoStatus.fromEnum(TransacaoStatusEnum.remessa) }); + await this.lancamentoService.updateRaw({ status: LancamentoStatus._3_remessa }, { transacaoAgrupadoId: transacaoAgId }); } private validateCancel(nsaInicial: number, nsaFinal: number) { diff --git a/src/cnab/service/pagamento/remessa-retorno.service.ts b/src/cnab/service/pagamento/remessa-retorno.service.ts index e18855cc..46fb4e8a 100644 --- a/src/cnab/service/pagamento/remessa-retorno.service.ts +++ b/src/cnab/service/pagamento/remessa-retorno.service.ts @@ -44,6 +44,7 @@ import { ItemTransacaoAgrupadoService } from './item-transacao-agrupado.service' import { ItemTransacaoService } from './item-transacao.service'; import { ClienteFavorecido } from 'src/cnab/entity/cliente-favorecido.entity'; import { LancamentoService } from 'src/lancamento/lancamento.service'; +import { LancamentoStatus } from 'src/lancamento/enums/lancamento-status.enum'; const sc = structuredClone; const PgtoRegistros = Cnab104PgtoTemplates.file104.registros; @@ -545,11 +546,13 @@ export class RemessaRetornoService { lancamento.is_pago = detalheARetorno.isPago(); if (lancamento.is_pago) { lancamento.data_pgto = detalheARetorno.dataEfetivacao; + lancamento.status = LancamentoStatus._4_pago; } else { lancamento.data_pgto = null; + lancamento.status = LancamentoStatus._5_erro; } } - await this.lancamentoService.updateManyRaw(lancamentos, ['is_pago', 'data_pgto'], queryRunner); + await this.lancamentoService.updateManyRaw(lancamentos, ['is_pago', 'data_pgto', 'status'], queryRunner); } async compareTransacaoViewPublicacao(detalheA: DetalheA, queryRunner: QueryRunner) { diff --git a/src/database/migrations/1725912379556-LancamentoStatus.ts b/src/database/migrations/1725912379556-LancamentoStatus.ts new file mode 100644 index 00000000..ff3204b8 --- /dev/null +++ b/src/database/migrations/1725912379556-LancamentoStatus.ts @@ -0,0 +1,14 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class LancamentoStatus1725912379556 implements MigrationInterface { + name = 'LancamentoStatus1725912379556' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "lancamento" ADD "status" character varying NOT NULL DEFAULT 'criado'`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "lancamento" DROP COLUMN "status"`); + } + +} diff --git a/src/lancamento/entities/lancamento.entity.ts b/src/lancamento/entities/lancamento.entity.ts index d60f7085..e3485a4b 100644 --- a/src/lancamento/entities/lancamento.entity.ts +++ b/src/lancamento/entities/lancamento.entity.ts @@ -1,15 +1,16 @@ import { ApiProperty } from '@nestjs/swagger'; import { Exclude, Expose, Transform } from 'class-transformer'; import { ClienteFavorecido } from 'src/cnab/entity/cliente-favorecido.entity'; +import { DetalheA } from 'src/cnab/entity/pagamento/detalhe-a.entity'; import { ItemTransacao } from 'src/cnab/entity/pagamento/item-transacao.entity'; +import { Ocorrencia } from 'src/cnab/entity/pagamento/ocorrencia.entity'; import { User } from 'src/users/entities/user.entity'; import { EntityHelper } from 'src/utils/entity-helper'; import { asStringOrNumber } from 'src/utils/pipe-utils'; import { AfterLoad, Column, CreateDateColumn, DeepPartial, Entity, JoinColumn, JoinTable, ManyToMany, ManyToOne, OneToOne, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'; +import { LancamentoStatus } from '../enums/lancamento-status.enum'; import { LancamentoInputDto } from '../dtos/lancamento-input.dto'; import { LancamentoAutorizacao } from './lancamento-autorizacao.entity'; -import { Ocorrencia } from 'src/cnab/entity/pagamento/ocorrencia.entity'; -import { DetalheA } from 'src/cnab/entity/pagamento/detalhe-a.entity'; export interface ILancamento { id: number; @@ -17,7 +18,7 @@ export interface ILancamento { data_ordem: Date; data_pgto: Date | null; data_lancamento: Date; - itemTransacao: ItemTransacao; + itemTransacao: ItemTransacao | null; autorizacoes: User[]; autor: User; clienteFavorecido: ClienteFavorecido; @@ -28,6 +29,7 @@ export interface ILancamento { numero_processo: string; is_autorizado: boolean; is_pago: boolean; + status: LancamentoStatus; createdAt: Date; updatedAt: Date; } @@ -79,12 +81,12 @@ export class Lancamento extends EntityHelper implements ILancamento { * * uniqueConstraintName: `UQ_Lancamento_itemTransacao` */ - // @Exclude() + @Exclude() @ApiProperty({ description: 'ItemTransação do CNAB remessa associado a este Lançamento' }) @OneToOne(() => ItemTransacao, { nullable: true }) @JoinColumn({ foreignKeyConstraintName: 'FK_Lancamento_itemTransacao_OneToOne' }) - // @Transform(({ value }) => (!value ? null : (it = value as ItemTransacao) => ({ id: it.id, itemTransacaoAgrupado: { id: it.itemTransacaoAgrupado.id } }))) - itemTransacao: ItemTransacao; + @Transform(({ value }) => (!value ? null : ((it = value as ItemTransacao) => ({ id: it.id, itemTransacaoAgrupado: { id: it.itemTransacaoAgrupado.id } }))())) + itemTransacao: ItemTransacao | null; @ManyToMany(() => User, (user) => user) @JoinTable({ @@ -105,7 +107,6 @@ export class Lancamento extends EntityHelper implements ILancamento { @ManyToOne(() => ClienteFavorecido, { eager: true }) @JoinColumn({ name: 'id_cliente_favorecido', foreignKeyConstraintName: 'FK_Lancamento_idClienteFavorecido_ManyToOne' }) - // @Expose({ name: 'id_cliente_favorecido' }) @Transform(({ value }) => ({ id: (value as ClienteFavorecido).id, nome: (value as ClienteFavorecido).nome, @@ -133,6 +134,10 @@ export class Lancamento extends EntityHelper implements ILancamento { @Column({ type: Boolean, nullable: false, default: false }) is_pago: boolean; + /** Uma forma de rastrear o estado atual do Lancamento */ + @Column({ enum: LancamentoStatus, nullable: false, default: LancamentoStatus._1_criado }) + status: LancamentoStatus; + @Exclude() @CreateDateColumn() createdAt: Date; @@ -142,18 +147,11 @@ export class Lancamento extends EntityHelper implements ILancamento { updatedAt: Date; /** Coluna virtual */ - @Transform(({ value }) => - value === null - ? null - : ((da = value as DetalheA) => ({ - id: da.id, - ocorrenciasCnab: da.ocorrenciasCnab, - numeroDocumentoEmpresa: da.numeroDocumentoEmpresa, - }))(), - ) + @Exclude() + @Transform(({ value }) => (value === null ? null : ((da = value as DetalheA) => ({ id: da.id, ocorrenciasCnab: da.ocorrenciasCnab, numeroDocumentoEmpresa: da.numeroDocumentoEmpresa }))())) detalheA: DetalheA | null = null; - /** Coluna virtual para consultar as ocorrências */ + /** Coluna virtual - para consultar as ocorrências */ ocorrencias: Ocorrencia[] = []; @AfterLoad() @@ -174,6 +172,9 @@ export class Lancamento extends EntityHelper implements ILancamento { addAutorizado(userId: number) { this.autorizacoes.push({ id: userId } as User); this.is_autorizado = this.autorizacoes.length >= 2; + if (this.status === LancamentoStatus._1_criado && this.is_autorizado) { + this.status = LancamentoStatus._2_aprovado; + } } removeAutorizado(userId: number) { @@ -213,7 +214,7 @@ export class Lancamento extends EntityHelper implements ILancamento { data_pgto: `${table ? `${table}.` : ''}"data_pgto"`, // Date | null, data_lancamento: `${table ? `${table}.` : ''}"data_lancamento"`, // Date, itemTransacao: `${table ? `${table}.` : ''}"itemTransacao"`, // ItemTransacao, - autorizacoes: `${table ? `${table}.` : ''}"autorizacoes"`, //User [], + autorizacoes: `${table ? `${table}.` : ''}"autorizacoes"`, // User[], autor: `${table ? `${table}.` : ''}"autor"`, // User, clienteFavorecido: `${table ? `${table}.` : ''}"clienteFavorecido"`, // ClienteFavorecido, algoritmo: `${table ? `${table}.` : ''}"algoritmo"`, // number, @@ -223,6 +224,7 @@ export class Lancamento extends EntityHelper implements ILancamento { numero_processo: `${table ? `${table}.` : ''}"numero_processo"`, // string, is_autorizado: `${table ? `${table}.` : ''}"is_autorizado"`, // boolean, is_pago: `${table ? `${table}.` : ''}"is_pago"`, // boolean, + status: `${table ? `${table}.` : ''}"status"`, // string, createdAt: `${table ? `${table}.` : ''}"createdAt"`, // Date, updatedAt: `${table ? `${table}.` : ''}"updatedAt"`, // Date, }; @@ -245,6 +247,7 @@ export class Lancamento extends EntityHelper implements ILancamento { numero_processo: 'VARCHAR', is_pago: 'BOOLEAN', is_autorizado: 'BOOLEAN', + status: 'VARCHAR', createdAt: 'TIMESTAMP', updatedAt: 'TIMESTAMP', }; diff --git a/src/lancamento/enums/lancamento-status.enum.ts b/src/lancamento/enums/lancamento-status.enum.ts new file mode 100644 index 00000000..f62df159 --- /dev/null +++ b/src/lancamento/enums/lancamento-status.enum.ts @@ -0,0 +1,7 @@ +export enum LancamentoStatus { + _1_criado = 'criado', + _2_aprovado = 'aprovado', + _3_remessa = 'remessa', + _4_pago = 'pago', + _5_erro = 'erro', +} diff --git a/src/lancamento/lancamento.controller.ts b/src/lancamento/lancamento.controller.ts index 1bae8751..0e5e1d7a 100644 --- a/src/lancamento/lancamento.controller.ts +++ b/src/lancamento/lancamento.controller.ts @@ -11,6 +11,9 @@ import { AutorizaLancamentoDto } from './dtos/AutorizaLancamentoDto'; import { LancamentoInputDto } from './dtos/lancamento-input.dto'; import { Lancamento } from './entities/lancamento.entity'; import { LancamentoService } from './lancamento.service'; +import { LancamentoStatus } from './enums/lancamento-status.enum'; +import { ParseEnumPipe } from 'src/utils/pipes/parse-enum.pipe'; +import { ParseArrayPipe } from 'src/utils/pipes/parse-array.pipe'; @ApiTags('Lancamento') @Controller({ @@ -34,14 +37,18 @@ export class LancamentoController { @ApiQuery({ name: 'mes', type: Number, required: false, description: ApiDescription({ _: 'Mês do lançamento', conditions: "periodo, mes, ano muest be filled. Otherwise it won't filter by date" }) }) @ApiQuery({ name: 'ano', type: Number, required: false, description: ApiDescription({ _: 'Ano do lançamento.', conditions: "periodo, mes, ano muest be filled. Otherwise it won't filter by date" }) }) @ApiQuery({ name: 'autorizado', type: Boolean, required: false, description: 'Fitra se foi autorizado ou não.' }) + @ApiQuery({ name: 'pago', type: Boolean, required: false, description: 'Fitra se foi autorizado ou não.' }) + @ApiQuery({ name: 'status', enum: LancamentoStatus, required: false, description: 'Fitra por status.' }) async get( @Request() request, // @Query('periodo', new ParseNumberPipe({ min: 1, max: 2, optional: true })) periodo: number | undefined, @Query('mes', new ParseNumberPipe({ min: 1, max: 12, optional: true })) mes: number | undefined, @Query('ano') ano: number | undefined, @Query('autorizado', new ParseBooleanPipe({ optional: true })) autorizado: boolean | undefined, + @Query('pago', new ParseBooleanPipe({ optional: true })) pago: boolean | undefined, + @Query('status', new ParseEnumPipe(LancamentoStatus, { optional: true })) status: LancamentoStatus | undefined, ): Promise { - return await this.lancamentoService.find({ mes, periodo, ano, autorizado }); + return await this.lancamentoService.find({ mes, periodo, ano, autorizado, pago, status }); } @Get('/getbystatus') @@ -147,7 +154,7 @@ export class LancamentoController { @Body() lancamentoDto: LancamentoInputDto, // It was ItfLancamento ) { lancamentoDto.author = { id: req.user.id }; - return await this.lancamentoService.update(lancamentoId, lancamentoDto); + return await this.lancamentoService.updateDto(lancamentoId, lancamentoDto); } @Get('/:id') diff --git a/src/lancamento/lancamento.repository.ts b/src/lancamento/lancamento.repository.ts index cc070a7e..3ba96308 100644 --- a/src/lancamento/lancamento.repository.ts +++ b/src/lancamento/lancamento.repository.ts @@ -1,7 +1,14 @@ import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { DeepPartial, DeleteResult, FindManyOptions, FindOneOptions, Repository, SaveOptions } from 'typeorm'; +import { Brackets, DeepPartial, DeleteResult, FindManyOptions, FindOneOptions, FindOptionsWhere, ObjectLiteral, QueryRunner, Repository, SaveOptions, UpdateResult } from 'typeorm'; import { Lancamento } from './entities/lancamento.entity'; +import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; +import { LancamentoStatus } from './enums/lancamento-status.enum'; +import { compactQuery } from 'src/utils/console-utils'; + +export interface UpdateLancamentoWhere { + transacaoAgrupadoId: number; +} @Injectable() export class LancamentoRepository { @@ -18,21 +25,45 @@ export class LancamentoRepository { return this.lancamentoRepository.save(entity, options); } + async updateRaw(set: DeepPartial, where: UpdateLancamentoWhere): Promise { + return await this.lancamentoRepository + .createQueryBuilder('lancamento') + .update() + .set(set) + .where( + compactQuery(` + id IN ( + SELECT l1.id FROM lancamento l1 + LEFT JOIN item_transacao it ON it.id = l1."itemTransacaoId" + LEFT JOIN item_transacao_agrupado ita ON ita.id = it."itemTransacaoAgrupadoId" + LEFT JOIN transacao_agrupado ta ON ta.id = ita."transacaoAgrupadoId" + WHERE ta.id = :transacaoAgrupadoId + ) + `), + where, + ) + .execute(); + } + + update(criteria: FindOptionsWhere, partialEntity: QueryDeepPartialEntity, queryRunner?: QueryRunner): Promise { + return (queryRunner?.manager?.getRepository(Lancamento) || this.lancamentoRepository).update(criteria, partialEntity); + } + async findOne(options: FindOneOptions): Promise { let qb = this.lancamentoRepository .createQueryBuilder('lancamento') // .leftJoinAndSelect('lancamento.autorizacoes', 'autorizacoes') .leftJoinAndSelect('lancamento.autor', 'autor') + .leftJoinAndSelect('lancamento.clienteFavorecido', 'clienteFavorecido') .leftJoinAndSelect('lancamento.itemTransacao', 'itemTransacao') .leftJoinAndSelect('itemTransacao.itemTransacaoAgrupado', 'itemTransacaoAgrupado') - // .leftJoin('detalhe_a', 'detalheA', 'detalheA.itemTransacaoAgrupadoId = itemTransacaoAgrupado.id') .leftJoinAndMapOne('lancamento.detalheA', 'detalhe_a', 'detalheA', 'detalheA.itemTransacaoAgrupadoId = itemTransacaoAgrupado.id') .leftJoinAndMapMany('lancamento.ocorrencias', 'ocorrencia', 'ocorrencia', 'ocorrencia.detalheAId = detalheA.id'); - - if (options?.where) { - qb = qb.where(options.where); + + if (options?.where) { + qb = qb.where(options.where); } - + return await qb.getOne(); } @@ -50,13 +81,13 @@ export class LancamentoRepository { .createQueryBuilder('lancamento') // .leftJoinAndSelect('lancamento.autorizacoes', 'autorizacoes') .leftJoinAndSelect('lancamento.autor', 'autor') + .leftJoinAndSelect('lancamento.clienteFavorecido', 'clienteFavorecido') .leftJoinAndSelect('lancamento.itemTransacao', 'itemTransacao') .leftJoinAndSelect('itemTransacao.itemTransacaoAgrupado', 'itemTransacaoAgrupado') - // .leftJoin('detalhe_a', 'detalheA', 'detalheA.itemTransacaoAgrupadoId = itemTransacaoAgrupado.id') .leftJoinAndMapOne('lancamento.detalheA', 'detalhe_a', 'detalheA', 'detalheA.itemTransacaoAgrupadoId = itemTransacaoAgrupado.id') .leftJoinAndMapMany('lancamento.ocorrencias', 'ocorrencia', 'ocorrencia', 'ocorrencia.detalheAId = detalheA.id'); - - if (options?.where) { + + if (options?.where) { qb = qb[!whereCount ? 'where' : 'andWhere'](options?.where); whereCount += 1; } diff --git a/src/lancamento/lancamento.service.ts b/src/lancamento/lancamento.service.ts index b0a195b5..a1ec4fe9 100644 --- a/src/lancamento/lancamento.service.ts +++ b/src/lancamento/lancamento.service.ts @@ -6,13 +6,15 @@ import { ClienteFavorecidoService } from 'src/cnab/service/cliente-favorecido.se import { UsersService } from 'src/users/users.service'; import { CustomLogger } from 'src/utils/custom-logger'; import { CommonHttpException } from 'src/utils/http-exception/common-http-exception'; -import { Between, DeepPartial, IsNull, QueryRunner } from 'typeorm'; +import { Between, DeepPartial, FindOptionsWhere, IsNull, ObjectLiteral, QueryRunner, UpdateResult } from 'typeorm'; import { AutorizaLancamentoDto } from './dtos/AutorizaLancamentoDto'; import { LancamentoInputDto } from './dtos/lancamento-input.dto'; import { ILancamento, Lancamento } from './entities/lancamento.entity'; -import { LancamentoRepository } from './lancamento.repository'; +import { LancamentoRepository, UpdateLancamentoWhere } from './lancamento.repository'; import { EntityHelper } from 'src/utils/entity-helper'; import { compactQuery } from 'src/utils/console-utils'; +import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; +import { LancamentoStatus } from './enums/lancamento-status.enum'; @Injectable() export class LancamentoService { @@ -37,7 +39,15 @@ export class LancamentoService { }); } - async find(args?: { mes?: number; periodo?: number; ano?: number; autorizado?: boolean; detalheA?: { id: number[] } }): Promise { + async find(args?: { + mes?: number; // + periodo?: number; + ano?: number; + autorizado?: boolean; + pago?: boolean; + detalheA?: { id: number[] }; + status?: LancamentoStatus; + }): Promise { /** [startDate, endDate] */ let dateRange: [Date, Date] | null = null; if (args?.mes && args?.periodo && args?.ano) { @@ -49,6 +59,8 @@ export class LancamentoService { where: { ...(dateRange ? { data_lancamento: Between(...dateRange) } : {}), ...(args?.autorizado !== undefined ? { is_autorizado: args.autorizado } : {}), + ...(args?.pago !== undefined ? { is_pago: args.pago } : {}), + ...(args?.status ? { status: args.status } : {}), }, relations: ['autorizacoes'] as (keyof ILancamento)[], }, @@ -113,7 +125,11 @@ export class LancamentoService { return await this.lancamentoRepository.save(lancamento); } - async update(id: number, updateDto: LancamentoInputDto): Promise { + updateRaw(set: DeepPartial, where: UpdateLancamentoWhere): Promise { + return this.lancamentoRepository.updateRaw(set, where); + } + + async updateDto(id: number, updateDto: LancamentoInputDto): Promise { const lancamento = await this.lancamentoRepository.findOne({ where: { id } }); if (!lancamento) { throw new NotFoundException(`Lançamento com ID ${id} não encontrado.`); diff --git a/src/utils/pipes/parse-list.pipe.ts b/src/utils/pipes/parse-array.pipe.ts similarity index 73% rename from src/utils/pipes/parse-list.pipe.ts rename to src/utils/pipes/parse-array.pipe.ts index 095116a7..0e4f04c4 100644 --- a/src/utils/pipes/parse-list.pipe.ts +++ b/src/utils/pipes/parse-array.pipe.ts @@ -1,19 +1,22 @@ import { BadRequestException, Injectable, PipeTransform, ArgumentMetadata } from '@nestjs/common'; import { isString } from 'class-validator'; import { isArrayUnique } from '../array-utils'; +import { Enum } from '../enum'; /** * Valida se uma lista inserida contém apenas valores de outra lista * @param args.unique (opcional) Verifica se tem apenas valores únicos */ @Injectable() -export class ParseListPipe implements PipeTransform { +export class ParseArrayPipe implements PipeTransform { /** * * @param args.transform default: true */ constructor( private readonly args?: { + /** Check if list items are valid enum */ + enumType?: any; /** If must have unique values */ compareList?: any[]; unique?: boolean; @@ -24,6 +27,7 @@ export class ParseListPipe implements PipeTransform { ) {} transform(value: any | undefined, metadata: ArgumentMetadata): any { + const enumType = this.args?.enumType; const list = this.args?.compareList; const field = metadata.data; const unique = this.args?.unique; @@ -42,6 +46,9 @@ export class ParseListPipe implements PipeTransform { const valueList = value.split(','); + if (enumType && !valueList.every((e) => this.isEnumValue(e, enumType))) { + throw new BadRequestException(`${field}: ${value} must have valid enum items (${Enum.getKeys(enumType).join(',')}).`); + } if (list && !valueList.every((i) => list.includes(i))) { throw new BadRequestException(`${field}: ${value} must have valid items.`); } @@ -53,4 +60,8 @@ export class ParseListPipe implements PipeTransform { return transformed; } + private isEnumValue(value: any, enumType: Record): boolean { + const enumValues = Object.values(enumType); + return enumValues.includes(value); + } } diff --git a/src/utils/pipes/validate-enum.pipe.ts b/src/utils/pipes/parse-enum.pipe.ts similarity index 100% rename from src/utils/pipes/validate-enum.pipe.ts rename to src/utils/pipes/parse-enum.pipe.ts From 1b80cf6eee791d7a18fdc8fb0a337a822b923a3e Mon Sep 17 00:00:00 2001 From: Raphael Rivas Date: Tue, 10 Sep 2024 13:15:22 -0300 Subject: [PATCH 21/29] fix: uncomment testing code --- src/cnab/cnab.service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cnab/cnab.service.ts b/src/cnab/cnab.service.ts index 6dad3235..df441f71 100644 --- a/src/cnab/cnab.service.ts +++ b/src/cnab/cnab.service.ts @@ -610,7 +610,7 @@ export class CnabService { const retorno104 = parseCnab240Pagamento(cnabString); cnabs.push(cnabName); await this.remessaRetornoService.saveRetorno(retorno104); - // await this.sftpService.moveToBackup(cnabName, SftpBackupFolder.RetornoSuccess, cnabString, folder); + 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); @@ -622,7 +622,7 @@ export class CnabService { this.logger.log('Retorno não encontrado, abortando tarefa.', METHOD); return; } - // await this.sftpService.moveToBackup(cnabName, SftpBackupFolder.RetornoFailure, cnabString, folder); + await this.sftpService.moveToBackup(cnabName, SftpBackupFolder.RetornoFailure, cnabString, folder); failed.push(cnabName); } const cnab = await this.sftpService.getFirstCnabRetorno(folder); From 7e8d0cf3ae87f497250f2d3a2b7b840a38547c10 Mon Sep 17 00:00:00 2001 From: Raphael Rivas Date: Tue, 10 Sep 2024 13:27:45 -0300 Subject: [PATCH 22/29] fix: block authorize lancamento if status is not created --- src/lancamento/lancamento.service.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lancamento/lancamento.service.ts b/src/lancamento/lancamento.service.ts index a1ec4fe9..22daab60 100644 --- a/src/lancamento/lancamento.service.ts +++ b/src/lancamento/lancamento.service.ts @@ -106,6 +106,10 @@ export class LancamentoService { throw new HttpException('Lançamento não encontrado.', HttpStatus.NOT_FOUND); } + if (lancamento.status !== LancamentoStatus._1_criado) { + throw new HttpException(`Apenas lançamentos com status '${LancamentoStatus._1_criado}' podem ser aprovados. Status encontrado: '${lancamento.status}'.)`, HttpStatus.PRECONDITION_FAILED); + } + const user = await this.usersService.findOne({ id: userId }); if (!user) { throw new HttpException('Usuário não encontrado', HttpStatus.UNAUTHORIZED); From 14aa82ccf60bbd1ec095ee0a84494fd7d0ab065a Mon Sep 17 00:00:00 2001 From: Raphael Rivas Date: Tue, 10 Sep 2024 14:01:23 -0300 Subject: [PATCH 23/29] =?UTF-8?q?feat:=20soft=20delete=20lan=C3=A7amento?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../1725986922881-LancamentoSoftDelete.ts | 14 ++++++++++++++ src/lancamento/entities/lancamento.entity.ts | 10 +++++++++- src/lancamento/lancamento.controller.ts | 4 ++-- src/lancamento/lancamento.repository.ts | 4 ++-- src/lancamento/lancamento.service.ts | 11 +++++++++-- 5 files changed, 36 insertions(+), 7 deletions(-) create mode 100644 src/database/migrations/1725986922881-LancamentoSoftDelete.ts diff --git a/src/database/migrations/1725986922881-LancamentoSoftDelete.ts b/src/database/migrations/1725986922881-LancamentoSoftDelete.ts new file mode 100644 index 00000000..bc664666 --- /dev/null +++ b/src/database/migrations/1725986922881-LancamentoSoftDelete.ts @@ -0,0 +1,14 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class LancamentoSoftDelete1725986922881 implements MigrationInterface { + name = 'LancamentoSoftDelete1725986922881' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "lancamento" ADD "deletedAt" TIMESTAMP`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "lancamento" DROP COLUMN "deletedAt"`); + } + +} diff --git a/src/lancamento/entities/lancamento.entity.ts b/src/lancamento/entities/lancamento.entity.ts index e3485a4b..540f6235 100644 --- a/src/lancamento/entities/lancamento.entity.ts +++ b/src/lancamento/entities/lancamento.entity.ts @@ -7,7 +7,7 @@ import { Ocorrencia } from 'src/cnab/entity/pagamento/ocorrencia.entity'; import { User } from 'src/users/entities/user.entity'; import { EntityHelper } from 'src/utils/entity-helper'; import { asStringOrNumber } from 'src/utils/pipe-utils'; -import { AfterLoad, Column, CreateDateColumn, DeepPartial, Entity, JoinColumn, JoinTable, ManyToMany, ManyToOne, OneToOne, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'; +import { AfterLoad, Column, CreateDateColumn, DeepPartial, DeleteDateColumn, Entity, JoinColumn, JoinTable, ManyToMany, ManyToOne, OneToOne, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'; import { LancamentoStatus } from '../enums/lancamento-status.enum'; import { LancamentoInputDto } from '../dtos/lancamento-input.dto'; import { LancamentoAutorizacao } from './lancamento-autorizacao.entity'; @@ -32,6 +32,7 @@ export interface ILancamento { status: LancamentoStatus; createdAt: Date; updatedAt: Date; + deletedAt: Date; } @Entity('lancamento') @@ -146,6 +147,11 @@ export class Lancamento extends EntityHelper implements ILancamento { @UpdateDateColumn() updatedAt: Date; + /** Como são dados de pagamento, caso se delete um Lançamento o dado permanece no banco para auditoria */ + @DeleteDateColumn() + @Exclude() + deletedAt: Date; + /** Coluna virtual */ @Exclude() @Transform(({ value }) => (value === null ? null : ((da = value as DetalheA) => ({ id: da.id, ocorrenciasCnab: da.ocorrenciasCnab, numeroDocumentoEmpresa: da.numeroDocumentoEmpresa }))())) @@ -227,6 +233,7 @@ export class Lancamento extends EntityHelper implements ILancamento { status: `${table ? `${table}.` : ''}"status"`, // string, createdAt: `${table ? `${table}.` : ''}"createdAt"`, // Date, updatedAt: `${table ? `${table}.` : ''}"updatedAt"`, // Date, + deletedAt: `${table ? `${table}.` : ''}"deletedAt"`, // Date, }; } @@ -250,5 +257,6 @@ export class Lancamento extends EntityHelper implements ILancamento { status: 'VARCHAR', createdAt: 'TIMESTAMP', updatedAt: 'TIMESTAMP', + deletedAt: 'TIMESTAMP', }; } diff --git a/src/lancamento/lancamento.controller.ts b/src/lancamento/lancamento.controller.ts index 0e5e1d7a..77809934 100644 --- a/src/lancamento/lancamento.controller.ts +++ b/src/lancamento/lancamento.controller.ts @@ -172,7 +172,7 @@ export class LancamentoController { } @Delete('/:id') - @HttpCode(HttpStatus.NO_CONTENT) + @HttpCode(HttpStatus.OK) @UseGuards(AuthGuard('jwt'), RolesGuard) @Roles( RoleEnum.master, // @@ -182,6 +182,6 @@ export class LancamentoController { ) @ApiBearerAuth() async deleteId(@Param('id') id: number) { - return await this.lancamentoService.delete(id); + return await this.lancamentoService.deleteId(id); } } diff --git a/src/lancamento/lancamento.repository.ts b/src/lancamento/lancamento.repository.ts index 3ba96308..5d4106b2 100644 --- a/src/lancamento/lancamento.repository.ts +++ b/src/lancamento/lancamento.repository.ts @@ -104,7 +104,7 @@ export class LancamentoRepository { return this.findMany(); } - delete(id: number): Promise { - return this.lancamentoRepository.delete(id); + softDelete(id: number): Promise { + return this.lancamentoRepository.softDelete(id); } } diff --git a/src/lancamento/lancamento.service.ts b/src/lancamento/lancamento.service.ts index 22daab60..3f2a5eab 100644 --- a/src/lancamento/lancamento.service.ts +++ b/src/lancamento/lancamento.service.ts @@ -164,8 +164,15 @@ export class LancamentoService { return lancamento; } - async delete(id: number): Promise { - await this.lancamentoRepository.delete(id); + async deleteId(id: number): Promise { + const toDelete = await this.lancamentoRepository.findOne({ where: { id } }); + if (!toDelete) { + throw new HttpException('Lançamento a ser deletado não existe.', HttpStatus.NOT_FOUND); + } + if (toDelete.status !== LancamentoStatus._1_criado) { + throw new HttpException('Apenas é permitido deletar Lançamentos com status criado.', HttpStatus.NOT_ACCEPTABLE); + } + await this.lancamentoRepository.softDelete(id); } getMonthDateRange(year: number, month: number, period: number): [Date, Date] { From f6d6b385431dfb8d9c9a9e01a4c0e7537c089927 Mon Sep 17 00:00:00 2001 From: Raphael Rivas Date: Tue, 10 Sep 2024 14:12:24 -0300 Subject: [PATCH 24/29] feat: (commented) cronjob for generate remessa lancamento --- src/cnab/cnab.service.ts | 2 +- src/cron-jobs/cron-jobs.service.ts | 41 +++++++++++++++++++++++++++++- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/cnab/cnab.service.ts b/src/cnab/cnab.service.ts index df441f71..2d033a3b 100644 --- a/src/cnab/cnab.service.ts +++ b/src/cnab/cnab.service.ts @@ -459,7 +459,7 @@ export class CnabService { // #endregion public async saveTransacoesLancamento(dataOrdemInicial?: Date, dataOrdemFinal?: Date) { - const dataOrdem: [Date, Date] | undefined = dataOrdemInicial && dataOrdemFinal ? [dataOrdemInicial, dataOrdemFinal] : undefined; + const dataOrdem: [Date, Date] | undefined = dataOrdemInicial && dataOrdemFinal ? [startOfDay(dataOrdemInicial), endOfDay(dataOrdemFinal)] : undefined; await this.updateAllFavorecidosFromUsers(); const newLancamentos = await this.lancamentoService.findToPay(dataOrdem); const ordens = newLancamentos.map((l) => OrdemPagamentoDto.fromLancamento(l)); diff --git a/src/cron-jobs/cron-jobs.service.ts b/src/cron-jobs/cron-jobs.service.ts index ba8467cf..0aef1f3a 100644 --- a/src/cron-jobs/cron-jobs.service.ts +++ b/src/cron-jobs/cron-jobs.service.ts @@ -39,6 +39,7 @@ export enum CronJobsEnum { generateRemessaVLT = 'generateRemessaVLT', generateRemessaEmpresa = 'generateRemessaEmpresa', generateRemessaVan = 'generateRemessaVan', + generateRemessaLancamento = 'generateRemessaLancamento', } interface ICronjobDebug { today?: Date; @@ -72,7 +73,7 @@ export class CronJobsService { }); } - async onModuleLoad() { + async onModuleLoad() { const THIS_CLASS_WITH_METHOD = 'CronJobsService.onModuleLoad'; this.jobsConfig.push( @@ -162,6 +163,18 @@ export class CronJobsService { }, }, }, + // { + // /** + // * Gerar arquivo Remessa do Lançamento - todo dia, duração: 15 min + // */ + // name: CronJobsEnum.generateRemessaLancamento, + // cronJobParameters: { + // cronTime: '0 15 * * *', // Every day, 15:00 GMT = 14:00 BRT (GMT-3) + // onTick: async () => { + // await this.generateRemessaLancamento(); + // }, + // }, + // }, { /** Atualizar Transações Empresa - DLake para CCT - todo dia, 09:00, duração: 20 min */ name: CronJobsEnum.updateTransacaoViewEmpresa, @@ -341,6 +354,32 @@ export class CronJobsService { this.logger.log(`Tarefa finalizada - ${formatDateInterval(new Date(), startDateLog)}`, METHOD); } + /** + * Regras de negócio: + * - Todo dia gera remessa de Lançamentos com dataOrdem = hoje + */ + public async generateRemessaLancamento(debug?: ICronjobDebug) { + const METHOD = 'generateRemessaLancamento'; + try { + this.logger.log('Tarefa iniciada', METHOD); + const today = debug?.today || new Date(); + const startDateLog = new Date(); + const startDate = today; + const endDate = today; + await this.cnabService.saveTransacoesLancamento(startDate, endDate); + const listCnab = await this.cnabService.generateRemessa({ + tipo: PagadorContaEnum.CETT, + dataPgto: undefined, // data programada no Lançamento + isConference: false, + isCancelamento: false, + }); + await this.cnabService.sendRemessa(listCnab); + this.logger.log(`Tarefa finalizada - ${formatDateInterval(new Date(), startDateLog)}`, METHOD); + } catch (error) { + this.logger.error('Erro ao executar tarefa.', error?.stack, METHOD); + } + } + private async syncTransacaoViewOrdem(method = 'syncTransacaoViewOrdem') { try { const startDate = subDays(new Date(), 15); From 5d9741741de2f911aab0b08d9e18bc3bd279d52d Mon Sep 17 00:00:00 2001 From: Raphael Rivas Date: Tue, 10 Sep 2024 17:06:09 -0300 Subject: [PATCH 25/29] feat: validate create/update favorecido with valid favorecidos --- src/cnab/cnab.controller.ts | 13 +++-- src/cnab/enums/concessionaria-nome.enum.ts | 8 +++ .../cliente-favorecido-find-by.interface.ts | 7 --- .../cliente-favorecido.repository.ts | 28 ++++++++-- .../service/cliente-favorecido.service.ts | 5 +- src/lancamento/lancamento.controller.ts | 3 +- src/lancamento/lancamento.service.ts | 53 ++++++++++++++----- .../http-exception/common-http-exception.ts | 36 +++++-------- src/utils/pipes/parse-array.pipe.ts | 1 + 9 files changed, 100 insertions(+), 54 deletions(-) create mode 100644 src/cnab/enums/concessionaria-nome.enum.ts delete mode 100644 src/cnab/interfaces/cliente-favorecido-find-by.interface.ts diff --git a/src/cnab/cnab.controller.ts b/src/cnab/cnab.controller.ts index d2dbfe7d..caec33f5 100644 --- a/src/cnab/cnab.controller.ts +++ b/src/cnab/cnab.controller.ts @@ -1,4 +1,4 @@ -import { Controller, Get, HttpCode, HttpStatus, ParseArrayPipe, Query, UseGuards } from '@nestjs/common'; +import { Controller, Get, HttpCode, HttpStatus, Query, UseGuards } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; import { ApiBearerAuth, ApiOperation, ApiQuery, ApiTags } from '@nestjs/swagger'; import { Roles } from 'src/roles/roles.decorator'; @@ -15,6 +15,8 @@ import { ArquivoPublicacaoService } from './service/arquivo-publicacao.service'; import { ClienteFavorecidoService } from './service/cliente-favorecido.service'; import { ExtratoDto } from './service/dto/extrato.dto'; import { ExtratoHeaderArquivoService } from './service/extrato/extrato-header-arquivo.service'; +import { ParseArrayPipe } from 'src/utils/pipes/parse-array.pipe'; +import { ConcessionariaNomeEnum } from './enums/concessionaria-nome.enum'; @ApiTags('Cnab') @Controller({ @@ -35,16 +37,21 @@ export class CnabController { @Roles(RoleEnum.master, RoleEnum.admin, RoleEnum.admin_finan, RoleEnum.lancador_financeiro, RoleEnum.aprovador_financeiro) @ApiBearerAuth() @ApiQuery({ name: 'nome', description: 'Pesquisa por parte do nome, sem distinção de acento ou maiúsculas.', required: false, type: String }) + @ApiQuery({ name: 'nomeNot', description: 'Ignora nomes com parte do nome, sem distinção de acento ou maiúsculas.', required: false, type: String }) @ApiQuery({ name: 'consorcio', description: 'Nome do consorcio', required: false, enum: GetClienteFavorecidoConsorcioEnum }) @ApiQuery({ name: 'limit', description: ApiDescription({ _: 'Itens exibidos por página', min: 1 }), required: false, type: Number }) @ApiQuery({ name: 'page', description: ApiDescription({ _: 'Itens exibidos por página', min: 1 }), required: false, type: Number }) getClienteFavorecido( - @Query('nome', new ParseArrayPipe({ items: String, separator: ',', optional: true })) nome: string[], // + @Query('nome', new ParseArrayPipe({ optional: true })) nome: string[], // + @Query('nomeNot', new ParseArrayPipe({ optional: true, transformOptional: true })) nomeNot: string[], // @Query('consorcio', new ParseEnumPipe(GetClienteFavorecidoConsorcioEnum, { optional: true })) consorcio: GetClienteFavorecidoConsorcioEnum | undefined, @Query('limit', new ParseNumberPipe({ min: 0, optional: true })) limit: number | undefined, @Query('page', new ParseNumberPipe({ min: 1, optional: true })) page: number | undefined, ): Promise { - return this.clienteFavorecidoService.findBy({ nome, limit, page, consorcio }); + if (consorcio === GetClienteFavorecidoConsorcioEnum.Empresa) { + nomeNot.push(ConcessionariaNomeEnum.VLT); + } + return this.clienteFavorecidoService.getFindBy({ nome: { in: nome, not: nomeNot }, limit, page, consorcio }); } @Get('extratoLancamento') diff --git a/src/cnab/enums/concessionaria-nome.enum.ts b/src/cnab/enums/concessionaria-nome.enum.ts new file mode 100644 index 00000000..a572a1a5 --- /dev/null +++ b/src/cnab/enums/concessionaria-nome.enum.ts @@ -0,0 +1,8 @@ +export enum ConcessionariaNomeEnum { + VLT = 'CONCESSIONARIA DO VLT CARIOCA S.A.', + Intersul = 'Consórcio Intersul Transportes', + Internorte = 'Consórcio Internorte de Transportes', + Transcarioca = 'Consórcio Transcarioca de Transportes', + SantaCruz = 'Consórcio Santa Cruz Transportes', + CMTC = 'Companhia Municipal de Transportes Coletivos CMTC Rio', +} diff --git a/src/cnab/interfaces/cliente-favorecido-find-by.interface.ts b/src/cnab/interfaces/cliente-favorecido-find-by.interface.ts deleted file mode 100644 index 599fcdda..00000000 --- a/src/cnab/interfaces/cliente-favorecido-find-by.interface.ts +++ /dev/null @@ -1,7 +0,0 @@ -export interface IClienteFavorecidoFindBy { - /** ILIKE unaccent */ - nome?: string[]; - consorcio?: string; - limit?: number; - page?: number; -} diff --git a/src/cnab/repository/cliente-favorecido.repository.ts b/src/cnab/repository/cliente-favorecido.repository.ts index c4e3b035..4c177fd8 100644 --- a/src/cnab/repository/cliente-favorecido.repository.ts +++ b/src/cnab/repository/cliente-favorecido.repository.ts @@ -6,7 +6,6 @@ import { EntityCondition } from 'src/utils/types/entity-condition.type'; 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 { compactQuery } from 'src/utils/console-utils'; export interface IClienteFavorecidoRawWhere { @@ -17,6 +16,15 @@ export interface IClienteFavorecidoRawWhere { detalheANumeroDocumento?: number[]; } +export interface IClienteFavorecidoFindBy { + /** ILIKE unaccent */ + nome?: { in?: string[]; not?: string[] }; + consorcio?: string; + cpfCnpj?: { not: string[] }; + limit?: number; + page?: number; +} + @Injectable() export class ClienteFavorecidoRepository { private logger = new CustomLogger(ClienteFavorecidoRepository.name, { timestamp: true }); @@ -107,22 +115,32 @@ export class ClienteFavorecidoRepository { } } - if (where?.nome?.length) { - for (const nome of where.nome) { + if (where?.nome?.in?.length) { + for (const nome of where.nome?.in) { qb = qb[cmd()]('favorecido."nome" ILIKE UNACCENT(UPPER(:nome))', { nome: `%${nome}%`, }); } } + if (where?.nome?.not?.length) { + for (const nome of where.nome.not as string[]) { + qb = qb[cmd()]('favorecido."nome" NOT ILIKE UNACCENT(UPPER(:nome))', { + nome: `%${nome}%`, + }); + } + } if (where?.consorcio) { const consorcio = where.consorcio; if (consorcio === 'Van') { - qb = qb[cmd()](' favorecido.tipo = :tipo', { tipo: 'vanzeiro' }); + qb = qb[cmd()]('favorecido.tipo = :tipo', { tipo: 'vanzeiro' }); } else if (consorcio === 'Empresa') { - qb = qb[cmd()](' favorecido.tipo = :tipo', { tipo: 'consorcio'}); + qb = qb[cmd()]('favorecido.tipo = :tipo', { tipo: 'consorcio' }); } } + if (where?.cpfCnpj) { + qb = qb[cmd()](`favorecido.nome NOT IN (${where.cpfCnpj.not.map((i) => `'${i}'`).join(',')})`); + } if (where?.limit && where?.page) { const skip = where?.limit * (where?.page - 1); diff --git a/src/cnab/service/cliente-favorecido.service.ts b/src/cnab/service/cliente-favorecido.service.ts index 42d38e3d..46cfa6ee 100644 --- a/src/cnab/service/cliente-favorecido.service.ts +++ b/src/cnab/service/cliente-favorecido.service.ts @@ -12,8 +12,7 @@ import { validateDTO } from 'src/utils/validation-utils'; import { FindOneOptions, In } 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 { ClienteFavorecidoRepository, IClienteFavorecidoRawWhere } from '../repository/cliente-favorecido.repository'; +import { ClienteFavorecidoRepository, IClienteFavorecidoFindBy, IClienteFavorecidoRawWhere } from '../repository/cliente-favorecido.repository'; @Injectable() export class ClienteFavorecidoService { @@ -40,7 +39,7 @@ export class ClienteFavorecidoService { await this.clienteFavorecidoRepository.upsert(newFavorecidos); } - public async findBy(where?: IClienteFavorecidoFindBy): Promise { + public async getFindBy(where?: IClienteFavorecidoFindBy): Promise { return await this.clienteFavorecidoRepository.findManyBy(where); } diff --git a/src/lancamento/lancamento.controller.ts b/src/lancamento/lancamento.controller.ts index 77809934..6bf27b2d 100644 --- a/src/lancamento/lancamento.controller.ts +++ b/src/lancamento/lancamento.controller.ts @@ -14,6 +14,7 @@ import { LancamentoService } from './lancamento.service'; import { LancamentoStatus } from './enums/lancamento-status.enum'; import { ParseEnumPipe } from 'src/utils/pipes/parse-enum.pipe'; import { ParseArrayPipe } from 'src/utils/pipes/parse-array.pipe'; +import { IRequest } from 'src/utils/interfaces/request.interface'; @ApiTags('Lancamento') @Controller({ @@ -149,7 +150,7 @@ export class LancamentoController { @ApiBody({ type: LancamentoInputDto }) @ApiQuery({ name: 'lancamentoId', required: true, description: 'Id do lançamento' }) async putLancamento( - @Request() req, + @Request() req: IRequest, @Query('lancamentoId', new ParseNumberPipe({ min: 1 })) lancamentoId: number, @Body() lancamentoDto: LancamentoInputDto, // It was ItfLancamento ) { diff --git a/src/lancamento/lancamento.service.ts b/src/lancamento/lancamento.service.ts index 3f2a5eab..6c95dcc0 100644 --- a/src/lancamento/lancamento.service.ts +++ b/src/lancamento/lancamento.service.ts @@ -1,20 +1,29 @@ import { HttpException, HttpStatus, Injectable, NotFoundException } from '@nestjs/common'; import * as bcrypt from 'bcryptjs'; -import { endOfDay, endOfMonth, isFriday, nextFriday, startOfDay, subDays } from 'date-fns'; +import { endOfDay, endOfMonth } from 'date-fns'; import { ClienteFavorecido } from 'src/cnab/entity/cliente-favorecido.entity'; import { ClienteFavorecidoService } from 'src/cnab/service/cliente-favorecido.service'; import { UsersService } from 'src/users/users.service'; +import { compactQuery } from 'src/utils/console-utils'; import { CustomLogger } from 'src/utils/custom-logger'; +import { EntityHelper } from 'src/utils/entity-helper'; import { CommonHttpException } from 'src/utils/http-exception/common-http-exception'; -import { Between, DeepPartial, FindOptionsWhere, IsNull, ObjectLiteral, QueryRunner, UpdateResult } from 'typeorm'; +import { Between, DeepPartial, IsNull, QueryRunner, UpdateResult } from 'typeorm'; import { AutorizaLancamentoDto } from './dtos/AutorizaLancamentoDto'; import { LancamentoInputDto } from './dtos/lancamento-input.dto'; import { ILancamento, Lancamento } from './entities/lancamento.entity'; -import { LancamentoRepository, UpdateLancamentoWhere } from './lancamento.repository'; -import { EntityHelper } from 'src/utils/entity-helper'; -import { compactQuery } from 'src/utils/console-utils'; -import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; import { LancamentoStatus } from './enums/lancamento-status.enum'; +import { LancamentoRepository, UpdateLancamentoWhere } from './lancamento.repository'; +import { ConcessionariaNomeEnum } from 'src/cnab/enums/concessionaria-nome.enum'; + +const validFavorecidos = [ + String(ConcessionariaNomeEnum.CMTC), // + String(ConcessionariaNomeEnum.Internorte), + String(ConcessionariaNomeEnum.Intersul), + String(ConcessionariaNomeEnum.SantaCruz), + String(ConcessionariaNomeEnum.Transcarioca), + // ConcessionariaNomeEnum.VLT, // DESABILITADO ATÉ O MOMENTO +]; @Injectable() export class LancamentoService { @@ -84,17 +93,20 @@ export class LancamentoService { } async create(dto: LancamentoInputDto): Promise { - const lancamento = await this.validateLancamentoDto(dto); + const lancamento = await this.validateCreate(dto); const created = await this.lancamentoRepository.save(this.lancamentoRepository.create(lancamento)); const getCreated = await this.lancamentoRepository.getOne({ where: { id: created.id } }); return getCreated; } - async validateLancamentoDto(dto: LancamentoInputDto): Promise { + async validateCreate(dto: LancamentoInputDto): Promise { const favorecido = await this.clienteFavorecidoService.findOne({ where: { id: dto.id_cliente_favorecido } }); if (!favorecido) { throw CommonHttpException.message(`id_cliente_favorecido: Favorecido não encontrado no sistema`); } + if (!validFavorecidos.includes(favorecido.nome)) { + throw CommonHttpException.messageArgs(`id_cliente_favorecido: Favorecido não permitido para Lançamento.`, { validFavorecidos }); + } const lancamento = Lancamento.fromInputDto(dto); lancamento.clienteFavorecido = new ClienteFavorecido({ id: favorecido.id }); return lancamento; @@ -134,10 +146,7 @@ export class LancamentoService { } async updateDto(id: number, updateDto: LancamentoInputDto): Promise { - const lancamento = await this.lancamentoRepository.findOne({ where: { id } }); - if (!lancamento) { - throw new NotFoundException(`Lançamento com ID ${id} não encontrado.`); - } + const lancamento = await this.validateUpdateDto(id, updateDto); lancamento.updateFromInputDto(updateDto); await this.lancamentoRepository.save(lancamento); const updated = await this.lancamentoRepository.getOne({ where: { id: lancamento.id } }); @@ -145,7 +154,25 @@ export class LancamentoService { return updated; } - public async updateManyRaw(dtos: DeepPartial[], fields: (keyof ILancamento)[], queryRunner: QueryRunner): Promise { + async validateUpdateDto(id: number, updateDto: LancamentoInputDto): Promise { + const lancamento = await this.lancamentoRepository.findOne({ where: { id } }); + if (!lancamento) { + throw new NotFoundException(`Lançamento com ID ${id} não encontrado.`); + } + if (lancamento.status !== LancamentoStatus._1_criado) { + throw new HttpException('Apenas é permitido alterar Lançamentos com status criado.', HttpStatus.NOT_ACCEPTABLE); + } + const favorecido = await this.clienteFavorecidoService.findOne({ where: { id: updateDto.id_cliente_favorecido } }); + if (!favorecido) { + throw CommonHttpException.message('id_cliente_favorecido: Favorecido não encontrado no sistema'); + } + if (!validFavorecidos.includes(favorecido.nome)) { + throw CommonHttpException.messageArgs('id_cliente_favorecido: Favorecido não permitido para Lançamento.', { validFavorecidos }); + } + return lancamento; + } + + async updateManyRaw(dtos: DeepPartial[], fields: (keyof ILancamento)[], queryRunner: QueryRunner): Promise { if (!dtos.length) { return []; } diff --git a/src/utils/http-exception/common-http-exception.ts b/src/utils/http-exception/common-http-exception.ts index 4a89c734..b2631815 100644 --- a/src/utils/http-exception/common-http-exception.ts +++ b/src/utils/http-exception/common-http-exception.ts @@ -2,11 +2,7 @@ import { HttpException, HttpStatus } from '@nestjs/common'; import { getHttpStatusMessage } from './http-exception-utils'; export const CommonHttpException = { - detailField: ( - field: string, - message: string, - httpStatusCode: HttpStatus = 500, - ) => + detailField: (field: string, message: string, httpStatusCode: HttpStatus = 500) => new HttpException( { error: getHttpStatusMessage(httpStatusCode), @@ -25,6 +21,16 @@ export const CommonHttpException = { }, httpStatusCode || HttpStatus.INTERNAL_SERVER_ERROR, ), + messageArgs: (errorMessage: string, args: object, httpStatusCode?: HttpStatus) => + new HttpException( + { + error: { + message: errorMessage, + ...args, + }, + }, + httpStatusCode || HttpStatus.INTERNAL_SERVER_ERROR, + ), invalidField: ( entity: string, fieldName: string, @@ -44,11 +50,7 @@ export const CommonHttpException = { }, args?.httpStatusCode || HttpStatus.UNPROCESSABLE_ENTITY, ), - errorDetails: ( - error: string, - details: object | string, - httpStatusCode: HttpStatus = 500, - ) => + errorDetails: (error: string, details: object | string, httpStatusCode: HttpStatus = 500) => new HttpException( { error: error || getHttpStatusMessage(httpStatusCode), @@ -72,11 +74,7 @@ export const CommonHttpException = { }, HttpStatus.UNPROCESSABLE_ENTITY, ), - notFound: ( - notFoundProp: string, - httpStatusCode: HttpStatus = HttpStatus.NOT_FOUND, - error?: string, - ) => + notFound: (notFoundProp: string, httpStatusCode: HttpStatus = HttpStatus.NOT_FOUND, error?: string) => new HttpException( { error: error || getHttpStatusMessage(httpStatusCode), @@ -90,13 +88,7 @@ export const CommonHttpException = { }, httpStatusCode, ), - argNotType: ( - field: string, - expectedType: string, - value: any, - httpStatusCode: HttpStatus = HttpStatus.UNPROCESSABLE_ENTITY, - detailsOnly = true, - ) => + argNotType: (field: string, expectedType: string, value: any, httpStatusCode: HttpStatus = HttpStatus.UNPROCESSABLE_ENTITY, detailsOnly = true) => new HttpException( { code: 'arg-not-type', diff --git a/src/utils/pipes/parse-array.pipe.ts b/src/utils/pipes/parse-array.pipe.ts index 0e4f04c4..02022275 100644 --- a/src/utils/pipes/parse-array.pipe.ts +++ b/src/utils/pipes/parse-array.pipe.ts @@ -21,6 +21,7 @@ export class ParseArrayPipe implements PipeTransform { compareList?: any[]; unique?: boolean; transform?: boolean; + /** Return empty list if not value given */ transformOptional?: boolean; optional?: boolean; }, From af05113744b80ba256bd15e4b8b29f4683afeeca Mon Sep 17 00:00:00 2001 From: Raphael Rivas Date: Tue, 10 Sep 2024 17:40:59 -0300 Subject: [PATCH 26/29] fix: block invalid favorecido CpfCnpj --- src/cnab/cnab.controller.ts | 11 ++--- src/cnab/cnab.service.ts | 2 +- src/cnab/enums/concessionaria-nome.enum.ts | 8 ---- src/cnab/enums/favorecido-empresa.enum.ts | 17 ++++++++ .../cliente-favorecido.repository.ts | 6 +-- .../service/cliente-favorecido.service.ts | 2 +- .../cliente-favorecido-seed-data.ts | 41 ++++++------------ src/lancamento/lancamento.service.ts | 43 +++++++++++++------ 8 files changed, 71 insertions(+), 59 deletions(-) delete mode 100644 src/cnab/enums/concessionaria-nome.enum.ts create mode 100644 src/cnab/enums/favorecido-empresa.enum.ts diff --git a/src/cnab/cnab.controller.ts b/src/cnab/cnab.controller.ts index caec33f5..f2f732fb 100644 --- a/src/cnab/cnab.controller.ts +++ b/src/cnab/cnab.controller.ts @@ -6,17 +6,17 @@ import { RoleEnum } from 'src/roles/roles.enum'; import { RolesGuard } from 'src/roles/roles.guard'; import { ApiDescription } from 'src/utils/api-param/description-api-param'; import { CustomLogger } from 'src/utils/custom-logger'; +import { ParseArrayPipe } from 'src/utils/pipes/parse-array.pipe'; import { ParseDatePipe } from 'src/utils/pipes/parse-date.pipe'; -import { ParseNumberPipe } from 'src/utils/pipes/parse-number.pipe'; import { ParseEnumPipe } from 'src/utils/pipes/parse-enum.pipe'; +import { ParseNumberPipe } from 'src/utils/pipes/parse-number.pipe'; import { ClienteFavorecido } from './entity/cliente-favorecido.entity'; +import { FavorecidoEmpresaCpfCnpjEnum } from './enums/favorecido-empresa.enum'; import { GetClienteFavorecidoConsorcioEnum } from './enums/get-cliente-favorecido-consorcio.enum'; import { ArquivoPublicacaoService } from './service/arquivo-publicacao.service'; import { ClienteFavorecidoService } from './service/cliente-favorecido.service'; import { ExtratoDto } from './service/dto/extrato.dto'; import { ExtratoHeaderArquivoService } from './service/extrato/extrato-header-arquivo.service'; -import { ParseArrayPipe } from 'src/utils/pipes/parse-array.pipe'; -import { ConcessionariaNomeEnum } from './enums/concessionaria-nome.enum'; @ApiTags('Cnab') @Controller({ @@ -48,10 +48,11 @@ export class CnabController { @Query('limit', new ParseNumberPipe({ min: 0, optional: true })) limit: number | undefined, @Query('page', new ParseNumberPipe({ min: 1, optional: true })) page: number | undefined, ): Promise { + const cpfCnpjNot: string[] = []; if (consorcio === GetClienteFavorecidoConsorcioEnum.Empresa) { - nomeNot.push(ConcessionariaNomeEnum.VLT); + cpfCnpjNot.push(FavorecidoEmpresaCpfCnpjEnum.VLT); } - return this.clienteFavorecidoService.getFindBy({ nome: { in: nome, not: nomeNot }, limit, page, consorcio }); + return this.clienteFavorecidoService.getFindBy({ consorcio, cpfCnpj: { not: cpfCnpjNot }, nome: { in: nome, not: nomeNot }, limit, page }); } @Get('extratoLancamento') diff --git a/src/cnab/cnab.service.ts b/src/cnab/cnab.service.ts index 2d033a3b..9f1ed971 100644 --- a/src/cnab/cnab.service.ts +++ b/src/cnab/cnab.service.ts @@ -462,7 +462,7 @@ export class CnabService { const dataOrdem: [Date, Date] | undefined = dataOrdemInicial && dataOrdemFinal ? [startOfDay(dataOrdemInicial), endOfDay(dataOrdemFinal)] : undefined; await this.updateAllFavorecidosFromUsers(); const newLancamentos = await this.lancamentoService.findToPay(dataOrdem); - const ordens = newLancamentos.map((l) => OrdemPagamentoDto.fromLancamento(l)); + const ordens = newLancamentos.cett.map((l) => OrdemPagamentoDto.fromLancamento(l)); await this.saveOrdens(ordens, 'cett'); } diff --git a/src/cnab/enums/concessionaria-nome.enum.ts b/src/cnab/enums/concessionaria-nome.enum.ts deleted file mode 100644 index a572a1a5..00000000 --- a/src/cnab/enums/concessionaria-nome.enum.ts +++ /dev/null @@ -1,8 +0,0 @@ -export enum ConcessionariaNomeEnum { - VLT = 'CONCESSIONARIA DO VLT CARIOCA S.A.', - Intersul = 'Consórcio Intersul Transportes', - Internorte = 'Consórcio Internorte de Transportes', - Transcarioca = 'Consórcio Transcarioca de Transportes', - SantaCruz = 'Consórcio Santa Cruz Transportes', - CMTC = 'Companhia Municipal de Transportes Coletivos CMTC Rio', -} diff --git a/src/cnab/enums/favorecido-empresa.enum.ts b/src/cnab/enums/favorecido-empresa.enum.ts new file mode 100644 index 00000000..c3792b7c --- /dev/null +++ b/src/cnab/enums/favorecido-empresa.enum.ts @@ -0,0 +1,17 @@ +export enum FavorecidoEmpresaNomeEnum { + VLT = 'Concessionária do VLT Carioca S.A.', + Intersul = 'Consórcio Intersul Transportes', + Internorte = 'Consórcio Internorte de Transportes', + Transcarioca = 'Consórcio Transcarioca de Transportes', + SantaCruz = 'Consórcio Santa Cruz Transportes', + CMTC = 'Companhia Municipal de Transportes Coletivos CMTC Rio', +} + +export enum FavorecidoEmpresaCpfCnpjEnum { + VLT = '18201378000119', + Intersul = '12464869000176', + Internorte = '12464539000180', + Transcarioca = '12464553000184', + SantaCruz = '12464577000133', + CMTC = '44520687000161', +} \ No newline at end of file diff --git a/src/cnab/repository/cliente-favorecido.repository.ts b/src/cnab/repository/cliente-favorecido.repository.ts index 4c177fd8..c3f49f39 100644 --- a/src/cnab/repository/cliente-favorecido.repository.ts +++ b/src/cnab/repository/cliente-favorecido.repository.ts @@ -1,12 +1,12 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; +import { compactQuery } from 'src/utils/console-utils'; 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, InsertResult, Repository, UpdateResult } from 'typeorm'; import { SaveClienteFavorecidoDTO } from '../dto/cliente-favorecido.dto'; import { ClienteFavorecido } from '../entity/cliente-favorecido.entity'; -import { compactQuery } from 'src/utils/console-utils'; export interface IClienteFavorecidoRawWhere { id?: number[]; @@ -138,8 +138,8 @@ export class ClienteFavorecidoRepository { qb = qb[cmd()]('favorecido.tipo = :tipo', { tipo: 'consorcio' }); } } - if (where?.cpfCnpj) { - qb = qb[cmd()](`favorecido.nome NOT IN (${where.cpfCnpj.not.map((i) => `'${i}'`).join(',')})`); + if (where?.cpfCnpj?.not?.length) { + qb = qb[cmd()](`favorecido."cpfCnpj" NOT IN (${where.cpfCnpj.not.map((i) => `'${i}'`).join(',')})`); } if (where?.limit && where?.page) { diff --git a/src/cnab/service/cliente-favorecido.service.ts b/src/cnab/service/cliente-favorecido.service.ts index 46cfa6ee..93d69efc 100644 --- a/src/cnab/service/cliente-favorecido.service.ts +++ b/src/cnab/service/cliente-favorecido.service.ts @@ -1,7 +1,7 @@ import { HttpStatus, Injectable, Logger } from '@nestjs/common'; import { BigqueryOrdemPagamentoDTO } from 'src/bigquery/dtos/bigquery-ordem-pagamento.dto'; -import { Lancamento } from 'src/lancamento/entities/lancamento.entity'; import { TipoFavorecidoEnum } from 'src/cnab/enums/tipo-favorecido.enum'; +import { Lancamento } from 'src/lancamento/entities/lancamento.entity'; import { User } from 'src/users/entities/user.entity'; import { CustomLogger } from 'src/utils/custom-logger'; import { CommonHttpException } from 'src/utils/http-exception/common-http-exception'; diff --git a/src/database/seeds/cliente-favorecido/cliente-favorecido-seed-data.ts b/src/database/seeds/cliente-favorecido/cliente-favorecido-seed-data.ts index 0193a5fb..c2d3042a 100644 --- a/src/database/seeds/cliente-favorecido/cliente-favorecido-seed-data.ts +++ b/src/database/seeds/cliente-favorecido/cliente-favorecido-seed-data.ts @@ -1,23 +1,11 @@ import { ClienteFavorecido } from 'src/cnab/entity/cliente-favorecido.entity'; +import { FavorecidoEmpresaCpfCnpjEnum, FavorecidoEmpresaNomeEnum } from 'src/cnab/enums/favorecido-empresa.enum'; import { TipoFavorecidoEnum } from 'src/cnab/enums/tipo-favorecido.enum'; -/** - * Favorecidos from - */ -export enum FavorecidoCpfCnpjEnum { - /** Pagador is CB */ - VLT = '18201378000119', - Intersul = '12464869000176', - Internorte = '12464539000180', - Transcarioca = '12464553000184', - SantaCruz = '12464577000133', - CMTC = '12464577000133', -} - export const ClienteFavorecidoSeedData: ClienteFavorecido[] = [ new ClienteFavorecido({ - nome: 'Concessionária do VLT Carioca S.A.', - cpfCnpj: '18201378000119', + nome: FavorecidoEmpresaNomeEnum.VLT, + cpfCnpj: FavorecidoEmpresaCpfCnpjEnum.VLT, codigoBanco: '033', agencia: '2271', dvAgencia: '', @@ -26,8 +14,8 @@ export const ClienteFavorecidoSeedData: ClienteFavorecido[] = [ tipo: TipoFavorecidoEnum.empresa, }), new ClienteFavorecido({ - nome: 'Consórcio Intersul Transportes', - cpfCnpj: '12464869000176', + nome: FavorecidoEmpresaNomeEnum.Intersul, + cpfCnpj: FavorecidoEmpresaCpfCnpjEnum.Intersul, codigoBanco: '033', agencia: '3003', dvAgencia: '', @@ -36,8 +24,8 @@ export const ClienteFavorecidoSeedData: ClienteFavorecido[] = [ tipo: TipoFavorecidoEnum.empresa, }), new ClienteFavorecido({ - nome: 'Consórcio Internorte de Transportes', - cpfCnpj: '12464539000180', + nome: FavorecidoEmpresaNomeEnum.Internorte, + cpfCnpj: FavorecidoEmpresaCpfCnpjEnum.Internorte, codigoBanco: '033', agencia: '3403', dvAgencia: '', @@ -45,10 +33,10 @@ export const ClienteFavorecidoSeedData: ClienteFavorecido[] = [ dvContaCorrente: '9', tipo: TipoFavorecidoEnum.empresa, }), - + new ClienteFavorecido({ - nome: 'Consórcio Transcarioca de Transportes', - cpfCnpj: '12464553000184', + nome: FavorecidoEmpresaNomeEnum.Transcarioca, + cpfCnpj: FavorecidoEmpresaCpfCnpjEnum.Transcarioca, codigoBanco: '033', agencia: '3403', dvAgencia: '', @@ -57,9 +45,8 @@ export const ClienteFavorecidoSeedData: ClienteFavorecido[] = [ tipo: TipoFavorecidoEnum.empresa, }), new ClienteFavorecido({ - nome: 'Consórcio Santa Cruz Transportes', - cpfCnpj: '12464577000133', - codigoBanco: '033', + nome: FavorecidoEmpresaNomeEnum.SantaCruz, + cpfCnpj: FavorecidoEmpresaCpfCnpjEnum.SantaCruz, agencia: '3403', dvAgencia: '', contaCorrente: '13004442', @@ -67,8 +54,8 @@ export const ClienteFavorecidoSeedData: ClienteFavorecido[] = [ tipo: TipoFavorecidoEnum.empresa, }), new ClienteFavorecido({ - nome: 'Companhia Municipal de Transportes Coletivos CMTC Rio', - cpfCnpj: '44520687000161', + nome: FavorecidoEmpresaNomeEnum.SantaCruz, + cpfCnpj: FavorecidoEmpresaCpfCnpjEnum.SantaCruz, codigoBanco: '001', agencia: '2234', dvAgencia: '9', diff --git a/src/lancamento/lancamento.service.ts b/src/lancamento/lancamento.service.ts index 6c95dcc0..1530c0f1 100644 --- a/src/lancamento/lancamento.service.ts +++ b/src/lancamento/lancamento.service.ts @@ -2,6 +2,7 @@ import { HttpException, HttpStatus, Injectable, NotFoundException } from '@nestj import * as bcrypt from 'bcryptjs'; import { endOfDay, endOfMonth } from 'date-fns'; import { ClienteFavorecido } from 'src/cnab/entity/cliente-favorecido.entity'; +import { FavorecidoEmpresaCpfCnpjEnum, FavorecidoEmpresaNomeEnum } from 'src/cnab/enums/favorecido-empresa.enum'; import { ClienteFavorecidoService } from 'src/cnab/service/cliente-favorecido.service'; import { UsersService } from 'src/users/users.service'; import { compactQuery } from 'src/utils/console-utils'; @@ -14,14 +15,22 @@ import { LancamentoInputDto } from './dtos/lancamento-input.dto'; import { ILancamento, Lancamento } from './entities/lancamento.entity'; import { LancamentoStatus } from './enums/lancamento-status.enum'; import { LancamentoRepository, UpdateLancamentoWhere } from './lancamento.repository'; -import { ConcessionariaNomeEnum } from 'src/cnab/enums/concessionaria-nome.enum'; - -const validFavorecidos = [ - String(ConcessionariaNomeEnum.CMTC), // - String(ConcessionariaNomeEnum.Internorte), - String(ConcessionariaNomeEnum.Intersul), - String(ConcessionariaNomeEnum.SantaCruz), - String(ConcessionariaNomeEnum.Transcarioca), + +/** Usado para exibição no erro */ +const validFavorecidoNames = [ + String(FavorecidoEmpresaNomeEnum.CMTC), // + String(FavorecidoEmpresaNomeEnum.Internorte), + String(FavorecidoEmpresaNomeEnum.Intersul), + String(FavorecidoEmpresaNomeEnum.SantaCruz), + String(FavorecidoEmpresaNomeEnum.Transcarioca), + // ConcessionariaNomeEnum.VLT, // DESABILITADO ATÉ O MOMENTO +]; +const validFavorecidoCpfCnpjs = [ + String(FavorecidoEmpresaCpfCnpjEnum.CMTC), // + String(FavorecidoEmpresaCpfCnpjEnum.Internorte), + String(FavorecidoEmpresaCpfCnpjEnum.Intersul), + String(FavorecidoEmpresaCpfCnpjEnum.SantaCruz), + String(FavorecidoEmpresaCpfCnpjEnum.Transcarioca), // ConcessionariaNomeEnum.VLT, // DESABILITADO ATÉ O MOMENTO ]; @@ -38,14 +47,20 @@ export class LancamentoService { /** * Procura itens não usados ainda (sem Transacao Id) from current payment week (sex-qui). */ - async findToPay(dataOrdemBetween?: [Date, Date]): Promise { - return await this.lancamentoRepository.findMany({ + async findToPay(dataOrdemBetween?: [Date, Date]) { + const found = await this.lancamentoRepository.findMany({ where: { ...(dataOrdemBetween ? { data_lancamento: Between(...dataOrdemBetween) } : {}), itemTransacao: IsNull(), is_autorizado: true, }, }); + return { + /** Usado para Lançamento Financeiro */ + cett: found.filter((l) => l.clienteFavorecido.cpfCnpj == String(FavorecidoEmpresaCpfCnpjEnum.VLT)), + /** Usado majoritariamente para Jaé */ + contaBilhetagem: found.filter((l) => l.clienteFavorecido.cpfCnpj != String(FavorecidoEmpresaCpfCnpjEnum.VLT)), + }; } async find(args?: { @@ -104,8 +119,8 @@ export class LancamentoService { if (!favorecido) { throw CommonHttpException.message(`id_cliente_favorecido: Favorecido não encontrado no sistema`); } - if (!validFavorecidos.includes(favorecido.nome)) { - throw CommonHttpException.messageArgs(`id_cliente_favorecido: Favorecido não permitido para Lançamento.`, { validFavorecidos }); + if (!validFavorecidoCpfCnpjs.includes(favorecido.cpfCnpj)) { + throw CommonHttpException.messageArgs(`id_cliente_favorecido: Favorecido não permitido para Lançamento.`, { validFavorecidos: validFavorecidoNames }); } const lancamento = Lancamento.fromInputDto(dto); lancamento.clienteFavorecido = new ClienteFavorecido({ id: favorecido.id }); @@ -166,8 +181,8 @@ export class LancamentoService { if (!favorecido) { throw CommonHttpException.message('id_cliente_favorecido: Favorecido não encontrado no sistema'); } - if (!validFavorecidos.includes(favorecido.nome)) { - throw CommonHttpException.messageArgs('id_cliente_favorecido: Favorecido não permitido para Lançamento.', { validFavorecidos }); + if (!validFavorecidoNames.includes(favorecido.nome)) { + throw CommonHttpException.messageArgs('id_cliente_favorecido: Favorecido não permitido para Lançamento.', { validFavorecidos: validFavorecidoNames }); } return lancamento; } From 09850d8108e71ace4bc759994f2d775bad2923fe Mon Sep 17 00:00:00 2001 From: Raphael Rivas Date: Tue, 10 Sep 2024 18:02:07 -0300 Subject: [PATCH 27/29] doc: Lancamento version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 140e091c..610f8099 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "api-cct", - "version": "0.12.8", + "version": "0.13.0", "description": "", "author": "", "private": true, From a769b0c4f3f2457a50de38a08ef8d12f569b7681 Mon Sep 17 00:00:00 2001 From: Raphael Rivas Date: Tue, 10 Sep 2024 18:14:22 -0300 Subject: [PATCH 28/29] refactor: remove unused bqOrdem.dataPagamento --- src/bigquery/dtos/bigquery-ordem-pagamento.dto.ts | 10 ---------- .../bigquery-ordem-pagamento.repository.ts | 8 +++----- 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/src/bigquery/dtos/bigquery-ordem-pagamento.dto.ts b/src/bigquery/dtos/bigquery-ordem-pagamento.dto.ts index 2fa17e53..ff8ca7d7 100644 --- a/src/bigquery/dtos/bigquery-ordem-pagamento.dto.ts +++ b/src/bigquery/dtos/bigquery-ordem-pagamento.dto.ts @@ -24,16 +24,6 @@ export class BigqueryOrdemPagamentoDTO { @IsDateString() dataOrdem: string; - /** - * Data de pagamento da ordem - * - * Se a dataPagamento for nula, iremos efetuar o pagamento. - * Senão, ignoramos o item. - */ - @ValidateIf((o, v) => v !== null) - @IsDateString() - // dataPagamento: string | null; - /** * Id de cadastro.consorcios * diff --git a/src/bigquery/repositories/bigquery-ordem-pagamento.repository.ts b/src/bigquery/repositories/bigquery-ordem-pagamento.repository.ts index 9848aa5c..6e338257 100644 --- a/src/bigquery/repositories/bigquery-ordem-pagamento.repository.ts +++ b/src/bigquery/repositories/bigquery-ordem-pagamento.repository.ts @@ -1,10 +1,9 @@ -import { Injectable, Logger } from '@nestjs/common'; -import { SettingsService } from 'src/settings/settings.service'; +import { Injectable } from '@nestjs/common'; +import { CustomLogger } from 'src/utils/custom-logger'; import { bigToNumber } from 'src/utils/pipe-utils'; -import { BigquerySource, BigqueryService } from '../bigquery.service'; +import { BigqueryService, BigquerySource } 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 { @@ -12,7 +11,6 @@ export class BigqueryOrdemPagamentoRepository { constructor( private readonly bigqueryService: BigqueryService, - private readonly settingsService: SettingsService, ) {} public async findMany( From ec8bdef43acf1a8fab638e578a41549163fa258e Mon Sep 17 00:00:00 2001 From: Raphael Rivas Date: Tue, 10 Sep 2024 18:21:09 -0300 Subject: [PATCH 29/29] fix: use dataPagamento again --- src/bigquery/dtos/bigquery-ordem-pagamento.dto.ts | 10 ++++++++++ .../bigquery-ordem-pagamento.repository.ts | 1 + 2 files changed, 11 insertions(+) diff --git a/src/bigquery/dtos/bigquery-ordem-pagamento.dto.ts b/src/bigquery/dtos/bigquery-ordem-pagamento.dto.ts index ff8ca7d7..35434693 100644 --- a/src/bigquery/dtos/bigquery-ordem-pagamento.dto.ts +++ b/src/bigquery/dtos/bigquery-ordem-pagamento.dto.ts @@ -24,6 +24,16 @@ export class BigqueryOrdemPagamentoDTO { @IsDateString() dataOrdem: string; + /** + * Data de pagamento da ordem + * + * Se a dataPagamento for nula, iremos efetuar o pagamento. + * Senão, ignoramos o item. + */ + @ValidateIf((o, v) => v !== null) + @IsDateString() + dataPagamento: string | null; + /** * Id de cadastro.consorcios * diff --git a/src/bigquery/repositories/bigquery-ordem-pagamento.repository.ts b/src/bigquery/repositories/bigquery-ordem-pagamento.repository.ts index 6e338257..1f9ad544 100644 --- a/src/bigquery/repositories/bigquery-ordem-pagamento.repository.ts +++ b/src/bigquery/repositories/bigquery-ordem-pagamento.repository.ts @@ -85,6 +85,7 @@ export class BigqueryOrdemPagamentoRepository { const select = ` SELECT CAST(t.data_ordem AS STRING) AS dataOrdem, + CAST(t.data_pagamento AS STRING) AS dataPagamento, t.id_consorcio AS idConsorcio, t.consorcio, t.id_operadora AS idOperadora,