From ea3d0f05b89df872db5c0fb1bfd216197c225af3 Mon Sep 17 00:00:00 2001 From: williamfl2007 Date: Wed, 3 Jul 2024 13:19:44 -0300 Subject: [PATCH] =?UTF-8?q?Cria=C3=A7=C3=A3o=20de=20rotina=20para=20confer?= =?UTF-8?q?encia=20de=20envio=20remessa?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bigquery-ordem-pagamento.service.ts | 23 ++- .../services/bigquery-transacao.service.ts | 23 ++- src/cnab/cnab.module.ts | 36 ++++ src/cnab/cnab.service.ts | 28 ++- .../conference/detalhe-a-conf.entity.ts | 174 +++++++++++++++++ .../conference/detalhe-b-conf.entity.ts | 44 +++++ .../conference/header-arquivo-conf.entity.ts | 116 ++++++++++++ .../conference/header-lote-conf.entity.ts | 60 ++++++ .../pagamento/detalhe-a-conf.repository.ts | 167 +++++++++++++++++ .../pagamento/detalhe-b-conf.repository.ts | 97 ++++++++++ .../header-arquivo-conf.repository.ts | 138 ++++++++++++++ .../pagamento/header-lote-conf.repository.ts | 133 +++++++++++++ .../pagamento/detalhe-a-conf.service.ts | 177 ++++++++++++++++++ .../service/pagamento/detalhe-a.service.ts | 4 +- .../pagamento/detalhe-b-conf.service.ts | 65 +++++++ .../pagamento/header-arquivo-conf.service.ts | 174 +++++++++++++++++ .../pagamento/header-lote-conf.service.ts | 110 +++++++++++ .../pagamento/remessa-retorno.service.ts | 147 ++++++++++----- src/cron-jobs/cron-jobs.service.ts | 22 +-- .../migrations/1719949229037-conference.ts | 40 ++++ 20 files changed, 1683 insertions(+), 95 deletions(-) create mode 100644 src/cnab/entity/conference/detalhe-a-conf.entity.ts create mode 100644 src/cnab/entity/conference/detalhe-b-conf.entity.ts create mode 100644 src/cnab/entity/conference/header-arquivo-conf.entity.ts create mode 100644 src/cnab/entity/conference/header-lote-conf.entity.ts create mode 100644 src/cnab/repository/pagamento/detalhe-a-conf.repository.ts create mode 100644 src/cnab/repository/pagamento/detalhe-b-conf.repository.ts create mode 100644 src/cnab/repository/pagamento/header-arquivo-conf.repository.ts create mode 100644 src/cnab/repository/pagamento/header-lote-conf.repository.ts create mode 100644 src/cnab/service/pagamento/detalhe-a-conf.service.ts create mode 100644 src/cnab/service/pagamento/detalhe-b-conf.service.ts create mode 100644 src/cnab/service/pagamento/header-arquivo-conf.service.ts create mode 100644 src/cnab/service/pagamento/header-lote-conf.service.ts create mode 100644 src/database/migrations/1719949229037-conference.ts diff --git a/src/bigquery/services/bigquery-ordem-pagamento.service.ts b/src/bigquery/services/bigquery-ordem-pagamento.service.ts index fd8bfad7..3830b3ed 100644 --- a/src/bigquery/services/bigquery-ordem-pagamento.service.ts +++ b/src/bigquery/services/bigquery-ordem-pagamento.service.ts @@ -16,19 +16,26 @@ export class BigqueryOrdemPagamentoService { /** * Get data from current payment week (qui-qua). Also with older days. */ - public async getFromWeek(daysBefore = 0): Promise { + public async getFromWeek(daysBefore:number,dataPgto:Date | undefined): Promise { // Read - const today = new Date(); - const friday = isFriday(today) ? today : nextFriday(today); + let startDate; + let endDate; - const sex = subDays(friday, 7 + daysBefore); - const qui = subDays(friday, 1); + const today = new Date(); + if(dataPgto == undefined){ + const friday = isFriday(today) ? today : nextFriday(today); + startDate = subDays(friday, 7 + daysBefore); + endDate = subDays(friday, 1); + }else{ + startDate = dataPgto; + endDate = dataPgto; + } const ordemPgto = ( await this.bigqueryOrdemPagamentoRepository.findMany({ - startDate: sex, - endDate: qui, + startDate: startDate, + endDate: endDate }) ).map((i) => ({ ...i } as BigqueryOrdemPagamentoDTO)); return ordemPgto; } -} +} \ No newline at end of file diff --git a/src/bigquery/services/bigquery-transacao.service.ts b/src/bigquery/services/bigquery-transacao.service.ts index 7e1b510a..bd6fe232 100644 --- a/src/bigquery/services/bigquery-transacao.service.ts +++ b/src/bigquery/services/bigquery-transacao.service.ts @@ -18,19 +18,24 @@ export class BigqueryTransacaoService { * * @param [daysBack=0] Pega a semana atual ou N dias atrás. */ - public async getFromWeek( - daysBack = 0, - startDateOnly = false, - ): Promise { + public async getFromWeek(daysBack = 0,dataPgto: Date | undefined,startDateOnly = false): Promise { // Read + let startDate; + let endDate; + const today = new Date(); - const friday = isFriday(today) ? today : nextFriday(today); - const qui = subDays(friday, 8 + daysBack); - const qua = subDays(friday, 2 + (startDateOnly ? 0 : daysBack)); + if(dataPgto == undefined){ + const friday = isFriday(today) ? today : nextFriday(today); + startDate = subDays(friday, 8 + daysBack); + endDate = subDays(friday, 2 + (startDateOnly ? 0 : daysBack)); + }else{ + startDate = subDays(dataPgto,1); + endDate = subDays(dataPgto,1); + } const ordemPgto = ( await this.bigqueryTransacaoRepository.findMany({ - startDate: qui, - endDate: qua, + startDate: startDate, + endDate: endDate, }) ).map((i) => ({ ...i } as BigqueryTransacao)); return ordemPgto; diff --git a/src/cnab/cnab.module.ts b/src/cnab/cnab.module.ts index e2081ad7..79465a4a 100644 --- a/src/cnab/cnab.module.ts +++ b/src/cnab/cnab.module.ts @@ -55,6 +55,18 @@ import { PagadorService } from './service/pagamento/pagador.service'; import { RemessaRetornoService } from './service/pagamento/remessa-retorno.service'; import { TransacaoAgrupadoService } from './service/pagamento/transacao-agrupado.service'; import { TransacaoService } from './service/pagamento/transacao.service'; +import { DetalheAConfRepository } from './repository/pagamento/detalhe-a-conf.repository'; +import { DetalheBConfRepository } from './repository/pagamento/detalhe-b-conf.repository'; +import { HeaderArquivoConfRepository } from './repository/pagamento/header-arquivo-conf.repository'; +import { HeaderLoteConfRepository } from './repository/pagamento/header-lote-conf.repository'; +import { DetalheAConfService } from './service/pagamento/detalhe-a-conf.service'; +import { DetalheBConfService } from './service/pagamento/detalhe-b-conf.service'; +import { HeaderArquivoConfService } from './service/pagamento/header-arquivo-conf.service'; +import { HeaderLoteConfService } from './service/pagamento/header-lote-conf.service'; +import { DetalheAConf } from './entity/conference/detalhe-a-conf.entity'; +import { DetalheBConf } from './entity/conference/detalhe-b-conf.entity'; +import { HeaderArquivoConf } from './entity/conference/header-arquivo-conf.entity'; +import { HeaderLoteConf } from './entity/conference/header-lote-conf.entity'; @Module({ imports: [ @@ -66,6 +78,10 @@ import { TransacaoService } from './service/pagamento/transacao.service'; SettingsModule, TransacaoViewModule, TypeOrmModule.forFeature([ + HeaderArquivoConf, + HeaderLoteConf, + DetalheAConf, + DetalheBConf, HeaderArquivo, HeaderLote, DetalheA, @@ -93,6 +109,16 @@ import { TransacaoService } from './service/pagamento/transacao.service'; DetalheAService, DetalheBRepository, DetalheBService, + + HeaderArquivoConfRepository, + HeaderArquivoConfService, + HeaderLoteConfRepository, + HeaderLoteConfService, + DetalheAConfRepository, + DetalheAConfService, + DetalheBConfRepository, + DetalheBConfService, + ClienteFavorecidoRepository, ClienteFavorecidoService, PagadorRepository, @@ -127,6 +153,16 @@ import { TransacaoService } from './service/pagamento/transacao.service'; DetalheAService, DetalheBRepository, DetalheBService, + + HeaderArquivoConfRepository, + HeaderArquivoConfService, + HeaderLoteConfRepository, + HeaderLoteConfService, + DetalheAConfRepository, + DetalheAConfService, + DetalheBConfRepository, + DetalheBConfService, + ClienteFavorecidoRepository, ClienteFavorecidoService, PagadorRepository, diff --git a/src/cnab/cnab.service.ts b/src/cnab/cnab.service.ts index a1aebc8b..c05c7ab5 100644 --- a/src/cnab/cnab.service.ts +++ b/src/cnab/cnab.service.ts @@ -95,17 +95,17 @@ export class CnabService { * * Requirement: **Salvar novas transações Jaé** - {@link https://github.com/RJ-SMTR/api-cct/issues/207#issuecomment-1984421700 #207, items 3} */ - public async saveTransacoesJae(daysBefore = 0,consorcio='Todos') { + public async saveTransacoesJae(daysBefore:number,consorcio:string,dataPgto: Date | undefined) { const METHOD = this.saveTransacoesJae.name; // 1. Update cliente favorecido await this.updateAllFavorecidosFromUsers(); // 2. Update TransacaoView - await this.updateTransacaoViewBigquery(daysBefore); + await this.updateTransacaoViewBigquery(daysBefore,dataPgto); // 3. Update ordens - const ordens = await this.bigqueryOrdemPagamentoService.getFromWeek(daysBefore); + const ordens = await this.bigqueryOrdemPagamentoService.getFromWeek(daysBefore,dataPgto); await this.saveOrdens(ordens,consorcio); //await this.compareTransacaoViewPublicacao(); @@ -123,11 +123,9 @@ export class CnabService { /** * Atualiza a tabela TransacaoView */ - async updateTransacaoViewBigquery(daysBack = 0) { - const transacoesBq = await this.bigqueryTransacaoService.getFromWeek( - daysBack, - false, - ); + async updateTransacaoViewBigquery(daysBack:number,dataPgto: Date | undefined) { + const transacoesBq = + await this.bigqueryTransacaoService.getFromWeek(daysBack,dataPgto,false); forChunk(transacoesBq, 1000, async (chunk) => { const transacoes = chunk.map((i) => @@ -505,7 +503,7 @@ export class CnabService { * * @throws `Error` if any subtask throws */ - public async saveRemessa(tipo: PagadorContaEnum) { + public async saveRemessa(tipo: PagadorContaEnum, dataPgto: Date | undefined, isConference: boolean) { const METHOD = this.sendRemessa.name; const transacoesAg = await this.transacaoAgService.findAllNewTransacao(tipo); @@ -522,11 +520,10 @@ export class CnabService { // Generate Remessas and send SFTP for (const transacaoAg of transacoesAg) { - // Get headerArquivo - const headerArquivoDTO = await this.remessaRetornoService.saveHeaderArquivoDTO(transacaoAg); + const headerArquivoDTO = await this.remessaRetornoService.saveHeaderArquivoDTO(transacaoAg,isConference); - const lotes = await this.remessaRetornoService.getLotes(transacaoAg.pagador,headerArquivoDTO); + const lotes = await this.remessaRetornoService.getLotes(transacaoAg.pagador,headerArquivoDTO,dataPgto,isConference); const cnab104 = this.remessaRetornoService.generateFile(headerArquivoDTO,lotes); @@ -544,9 +541,10 @@ export class CnabService { } // Update - await this.remessaRetornoService.updateHeaderArquivoDTOFrom104(headerArquivoDTO,processedCnab104.headerArquivo); - - await this.transacaoAgService.save({ id: transacaoAg.id, status: new TransacaoStatus(TransacaoStatusEnum.remessa) }); + if(isConference){ + await this.remessaRetornoService.updateHeaderArquivoDTOFrom104(headerArquivoDTO,processedCnab104.headerArquivo); + await this.transacaoAgService.save({ id: transacaoAg.id, status: new TransacaoStatus(TransacaoStatusEnum.remessa) }); + } if (!cnabStr) { this.logger.warn( diff --git a/src/cnab/entity/conference/detalhe-a-conf.entity.ts b/src/cnab/entity/conference/detalhe-a-conf.entity.ts new file mode 100644 index 00000000..0e997854 --- /dev/null +++ b/src/cnab/entity/conference/detalhe-a-conf.entity.ts @@ -0,0 +1,174 @@ +import { EntityHelper } from 'src/utils/entity-helper'; +import { + asNullableStringOrNumber, + asStringOrNumber, +} from 'src/utils/pipe-utils'; +import { + AfterLoad, + Column, + CreateDateColumn, + Entity, + JoinColumn, + ManyToOne, + OneToMany, + OneToOne, + PrimaryGeneratedColumn, + UpdateDateColumn, +} from 'typeorm'; +import { ClienteFavorecido } from '../cliente-favorecido.entity'; +import { HeaderLoteConf } from './header-lote-conf.entity'; +import { Ocorrencia } from '../pagamento/ocorrencia.entity'; +import { ItemTransacaoAgrupado } from '../pagamento/item-transacao-agrupado.entity'; + +/** + * Pagamento.DetalheA + */ +@Entity() +export class DetalheAConf extends EntityHelper { + @PrimaryGeneratedColumn({ primaryKeyConstraintName: 'PK_DetalheAConf_id' }) + id: number; + + @ManyToOne(() => HeaderLoteConf, { eager: true }) + @JoinColumn({ foreignKeyConstraintName: 'FK_DetalheAConf_headerLote_ManyToOne' }) + headerLote: HeaderLoteConf; + + @ManyToOne(() => ClienteFavorecido, { eager: true }) + @JoinColumn({ + foreignKeyConstraintName: 'FK_DetalheAConf_clienteFavorecido_ManyToOne', + }) + clienteFavorecido: ClienteFavorecido; + + @OneToMany(() => Ocorrencia, (ocorrencia) => ocorrencia.detalheA, { + eager: true, + }) + @JoinColumn({ + foreignKeyConstraintName: 'FK_DetalheAConf_ocorrencias_OneToMany', + }) + ocorrencias: Ocorrencia[]; + + @Column({ type: String, unique: false, nullable: true, length: 30 }) + ocorrenciasCnab: string | null; + + @Column({ type: Number, unique: false, nullable: true }) + loteServico: number; + + @Column({ type: String, unique: false, nullable: true }) + finalidadeDOC: string | null; + + /** Atribuído pela empresa, sequencial */ + @Column({ type: Number, unique: false, nullable: false }) + numeroDocumentoEmpresa: number; + + @Column({ type: Date, unique: false, nullable: true }) + dataVencimento: Date; + + @Column({ type: String, unique: false, nullable: true }) + tipoMoeda: string | null; + + @Column({ + type: 'decimal', + unique: false, + nullable: true, + precision: 10, + scale: 5, + }) + quantidadeMoeda: number | null; + + @Column({ + type: 'decimal', + unique: false, + nullable: true, + precision: 13, + scale: 2, + }) + valorLancamento: number; + + @Column({ type: String, unique: false, nullable: true }) + numeroDocumentoBanco: string | null; + + @Column({ type: Number, unique: false, nullable: true }) + quantidadeParcelas: number | null; + + @Column({ type: String, unique: false, nullable: true }) + indicadorBloqueio: string | null; + + @Column({ type: String, unique: false, nullable: true }) + indicadorFormaParcelamento: string | null; + + @Column({ type: Date, unique: false, nullable: true }) + periodoVencimento: Date | null; + + @Column({ type: Number, unique: false, nullable: true }) + numeroParcela: number | null; + + @Column({ type: Date, unique: false, nullable: true }) + dataEfetivacao: Date | null; + + @Column({ + type: 'decimal', + unique: false, + nullable: true, + precision: 13, + scale: 2, + }) + valorRealEfetivado: number; + + /** + * Número Sequencial do Registro. + * + * Detalhe unique ID per lote + */ + @Column({ type: Number, unique: false, nullable: false }) + nsr: number; + + /** `UQ_DetalheA_itemTransacaoAgrupado` */ + @OneToOne(() => ItemTransacaoAgrupado, { eager: true, nullable: false }) + @JoinColumn({ + foreignKeyConstraintName: 'FK_DetalheAConf_itemTransacaoAgrupado_OneToOne', + }) + itemTransacaoAgrupado: ItemTransacaoAgrupado; + + @CreateDateColumn() + createdAt: Date; + + @UpdateDateColumn() + updatedAt: Date; + + getOcorrenciasCnab() { + return (this.ocorrenciasCnab || '').trim(); + } + + @AfterLoad() + setReadValues() { + this.quantidadeMoeda = asNullableStringOrNumber(this.quantidadeMoeda); + this.valorLancamento = asStringOrNumber(this.valorLancamento); + this.valorRealEfetivado = asStringOrNumber(this.valorRealEfetivado); + } + + public isPago() { + const errors = Ocorrencia.getErrorCodes(this.ocorrenciasCnab || ''); + return errors.length === 0; + } + + public static getOcorrenciaErrors(detalhes: DetalheAConf[]) { + return detalhes.reduce( + (l, i) => [ + ...l, + ...i.ocorrencias.filter((j) => !['00', 'BD'].includes(j.code)), + ], + [], + ); + } + + public static getItemTransacaoAgIds(detalhesA: DetalheAConf[]) { + return [...new Set(detalhesA.map((i) => i.itemTransacaoAgrupado.id))]; + } + + public static getTransacaoAgIds(detalhesA: DetalheAConf[]): number[] { + return [ + ...new Set( + detalhesA.map((i) => i.itemTransacaoAgrupado.transacaoAgrupado.id), + ), + ]; + } +} diff --git a/src/cnab/entity/conference/detalhe-b-conf.entity.ts b/src/cnab/entity/conference/detalhe-b-conf.entity.ts new file mode 100644 index 00000000..1b9313ea --- /dev/null +++ b/src/cnab/entity/conference/detalhe-b-conf.entity.ts @@ -0,0 +1,44 @@ +import { EntityHelper } from 'src/utils/entity-helper'; +import { Column, CreateDateColumn, DeepPartial, Entity, JoinColumn, OneToOne, PrimaryGeneratedColumn } from 'typeorm'; +import { DetalheBDTO } from '../../dto/pagamento/detalhe-b.dto'; +import { DetalheAConf } from './detalhe-a-conf.entity'; + + +/** + * Pagamento.DetalheB + */ +@Entity() +export class DetalheBConf extends EntityHelper { + constructor(detalheB?: DetalheBConf | DeepPartial | DetalheBDTO) { + super(); + if (detalheB !== undefined) { + Object.assign(this, detalheB); + } + } + + @PrimaryGeneratedColumn({ primaryKeyConstraintName: 'PK_DetalheBConf_id' }) + id: number; + + @OneToOne(() => DetalheAConf, { eager: true }) + @JoinColumn({ foreignKeyConstraintName: 'FK_DetalheBConf_detalheA_OneToOne' }) + detalheA: DetalheAConf; + + /** + * Número Sequencial do Registro. + * + * Detalhe unique ID per lote + */ + @Column({ type: Number, unique: false, nullable: false }) + nsr: number; + + @Column({ type: Date, unique: false, nullable: false }) + dataVencimento: Date; + + @CreateDateColumn() + createdAt: Date; + + public getLogInfo(): string { + const response = `#${this.id}`; + return response; + } +} diff --git a/src/cnab/entity/conference/header-arquivo-conf.entity.ts b/src/cnab/entity/conference/header-arquivo-conf.entity.ts new file mode 100644 index 00000000..d96442be --- /dev/null +++ b/src/cnab/entity/conference/header-arquivo-conf.entity.ts @@ -0,0 +1,116 @@ +import { EntityHelper } from 'src/utils/entity-helper'; +import { asStringOrDateTime } from 'src/utils/pipe-utils'; +import { + AfterLoad, + BeforeInsert, + Column, + CreateDateColumn, + DeepPartial, + Entity, + JoinColumn, + ManyToOne, + PrimaryGeneratedColumn, + UpdateDateColumn, +} from 'typeorm'; +import { Transacao } from '../pagamento/transacao.entity'; +import { TransacaoAgrupado } from '../pagamento/transacao-agrupado.entity'; + + +/** + * Pagamento.HeaderArquivo + */ +@Entity() +export class HeaderArquivoConf extends EntityHelper { + constructor(dto?: DeepPartial) { + super(); + if (dto) { + Object.assign(this, dto); + } + } + + @PrimaryGeneratedColumn({ primaryKeyConstraintName: 'PK_HeaderArquivoConf_id' }) + id: number; + + @Column({ type: Number, unique: false, nullable: false }) + tipoArquivo: number; + + @Column({ type: String, unique: false, nullable: true, length: 3 }) + codigoBanco: string | null; + + @Column({ type: String, unique: false, nullable: true, length: 2 }) + tipoInscricao: string | null; + + @Column({ type: String, unique: false, nullable: true, length: 14 }) + numeroInscricao: string | null; + + @Column({ type: String, unique: false, nullable: true, length: 6 }) + codigoConvenio: string | null; + + @Column({ type: String, unique: false, nullable: true, length: 2 }) + parametroTransmissao: string | null; + + @Column({ type: String, unique: false, nullable: true, length: 5 }) + agencia: string | null; + + @Column({ type: String, unique: false, nullable: true, length: 1 }) + dvAgencia: string | null; + + @Column({ type: String, unique: false, nullable: true, length: 12 }) + numeroConta: string | null; + + @Column({ type: String, unique: false, nullable: true, length: 1 }) + dvConta: string | null; + + @Column({ type: String, unique: false, nullable: true, length: 100 }) + nomeEmpresa: string | null; + + @Column({ type: Date, unique: false, nullable: true }) + dataGeracao: Date; + + @Column({ type: 'time', unique: false, nullable: true }) + horaGeracao: Date; + + @ManyToOne(() => Transacao, { eager: true }) + @JoinColumn({ + foreignKeyConstraintName: 'FK_HeaderArquivoConf_transacao_ManyToOne', + }) + transacao: Transacao | null; + + @ManyToOne(() => TransacaoAgrupado, { eager: true }) + @JoinColumn({ + foreignKeyConstraintName: 'FK_HeaderArquivoConf_transacaoAgrupado_ManyToOne', + }) + transacaoAgrupado: TransacaoAgrupado | null; + + @Column({ type: Number, unique: false, nullable: false }) + nsa: number; + + @CreateDateColumn() + createdAt: Date; + + @UpdateDateColumn() + updatedAt: Date; + + public getIdString(): string { + return `{ transacao: ${this.transacao?.id}, transacaoAg: ${this.transacaoAgrupado?.id}, nsa: ${this.nsa}, tipoArquivo: ${this.tipoArquivo}}`; + } + + @BeforeInsert() + setLoadValues() { + if (typeof this.codigoBanco === 'string') { + this.codigoBanco = this.codigoBanco.padStart(3, '0'); + } + if (typeof this.numeroConta === 'string') { + this.numeroConta = this.numeroConta.padStart(12, '0'); + } + } + + @AfterLoad() + setReadValues() { + this.horaGeracao = asStringOrDateTime(this.horaGeracao, this.dataGeracao); + } + + public static getUniqueId(item?: DeepPartial) { + return `${item?.nsa}|${item?.tipoArquivo}`; + } +} diff --git a/src/cnab/entity/conference/header-lote-conf.entity.ts b/src/cnab/entity/conference/header-lote-conf.entity.ts new file mode 100644 index 00000000..941b1375 --- /dev/null +++ b/src/cnab/entity/conference/header-lote-conf.entity.ts @@ -0,0 +1,60 @@ +import { EntityHelper } from 'src/utils/entity-helper'; +import { Column, CreateDateColumn, DeepPartial, Entity, JoinColumn, ManyToOne, PrimaryGeneratedColumn } from 'typeorm'; +import { HeaderArquivoConf } from './header-arquivo-conf.entity'; +import { Pagador } from '../pagamento/pagador.entity'; + + +/** + * Pagamento.HeaderLote + */ +@Entity() +export class HeaderLoteConf extends EntityHelper { + constructor(dto?: DeepPartial) { + super(); + if (dto) { + Object.assign(this, dto); + } + } + + @PrimaryGeneratedColumn({ primaryKeyConstraintName: 'PK_HeaderLoteConf_id' }) + id: number; + + @ManyToOne(() => HeaderArquivoConf, { eager: true }) + @JoinColumn({ + foreignKeyConstraintName: 'FK_HeaderLoteConf_headerArquivo_ManyToOne', + }) + headerArquivo: HeaderArquivoConf; + + /** + * Unique lote Id in HeaderArquivo, incremental. + * + * Each HeaderArquivo will have loteServico 1 for lote 1; loteServico = 2 for lote 2 etc. + */ + @Column({ type: Number, unique: false, nullable: true }) + loteServico: number | null; + + @Column({ type: String, unique: false, nullable: true }) + tipoInscricao: string | null; + + @Column({ type: String, unique: false, nullable: true }) + numeroInscricao: string | null; + + @Column({ type: String, unique: false, nullable: true }) + codigoConvenioBanco: string | null; + + @Column({ type: String, unique: false, nullable: true }) + tipoCompromisso: string; + + @Column({ type: String, unique: false, nullable: true }) + parametroTransmissao: string; + + @Column({ type: String, unique: false, nullable: true }) + formaLancamento: string; + + @ManyToOne(() => Pagador, { eager: true }) + @JoinColumn({ foreignKeyConstraintName: 'FK_HeaderLoteConf_pagador_ManyToOne' }) + pagador: Pagador; + + @CreateDateColumn() + createdAt: Date; +} diff --git a/src/cnab/repository/pagamento/detalhe-a-conf.repository.ts b/src/cnab/repository/pagamento/detalhe-a-conf.repository.ts new file mode 100644 index 00000000..d04403c0 --- /dev/null +++ b/src/cnab/repository/pagamento/detalhe-a-conf.repository.ts @@ -0,0 +1,167 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { endOfDay, startOfDay } from 'date-fns'; +import { logWarn } from 'src/utils/log-utils'; +import { asNumber } from 'src/utils/pipe-utils'; +import { EntityCondition } from 'src/utils/types/entity-condition.type'; +import { Nullable } from 'src/utils/types/nullable.type'; +import { + DeepPartial, + FindManyOptions, + FindOneOptions, + In, + InsertResult, + LessThanOrEqual, + MoreThanOrEqual, + Repository, +} from 'typeorm'; +import { ClienteFavorecido } from '../../entity/cliente-favorecido.entity'; +import { DetalheA } from '../../entity/pagamento/detalhe-a.entity'; +import { DetalheAConf } from 'src/cnab/entity/conference/detalhe-a-conf.entity'; + +@Injectable() +export class DetalheAConfRepository { + private logger: Logger = new Logger('DetalheAConfRepository', { + timestamp: true, + }); + + constructor( + @InjectRepository(DetalheAConf) + private DetalheAConfRepository: Repository, + ) {} + + /** + * Any DTO existing in db will be ignored. + * + * @param dtos DTOs that can exist or not in database + * @returns Saved objects not in database. + */ + public async saveManyIfNotExists( + dtos: DeepPartial[], + ): Promise { + // Existing + const existing = await this.findMany({ + where: dtos.reduce( + (l, i) => [ + ...l, + { + headerLote: { id: asNumber(i.headerLote?.id) }, + nsr: asNumber(i.nsr), + }, + ], + [], + ), + }); + const existingMap: Record> = existing.reduce( + (m, i) => ({ ...m, [DetalheAConf.getUniqueId(i)]: i }), + {}, + ); + // Check + if (existing.length === dtos.length) { + logWarn( + this.logger, + `${existing.length}/${dtos.length} DetalheAConf já existem, nada a fazer...`, + ); + } else if (existing.length) { + logWarn( + this.logger, + `${existing.length}/${dtos.length} DetalheAConf já existem, ignorando...`, + ); + return []; + } + // Save new + const newDTOs = dtos.reduce( + (l, i) => [...l, ...(!existingMap[DetalheAConf.getUniqueId(i)] ? [i] : [])], + [], + ); + const insert = await this.insert(newDTOs); + // Return saved + const insertIds = (insert.identifiers as { id: number }[]).reduce( + (l, i) => [...l, i.id], + [], + ); + const saved = await this.findMany({ where: { id: In(insertIds) } }); + return saved; + } + + public insert(dtos: DeepPartial[]): Promise { + return this.DetalheAConfRepository.insert(dtos); + } + + public async save(dto: DeepPartial): Promise { + const saved = await this.DetalheAConfRepository.save(dto); + return await this.DetalheAConfRepository.findOneOrFail({ + where: { id: saved.id }, + }); + } + + public async getOne(fields: EntityCondition): Promise { + const one = await this.DetalheAConfRepository.findOneOrFail({ + where: fields, + }); + if (one) { + await this.forceManyEager([one]); + } + return one; + } + + public async findOne( + options: FindOneOptions, + ): Promise> { + const one = await this.DetalheAConfRepository.findOne(options); + if (one) { + await this.forceManyEager([one]); + } + return one; + } + + public async findMany( + options?: FindManyOptions, + ): Promise { + const detalheA = await this.DetalheAConfRepository.find(options); + await this.forceManyEager(detalheA); + return detalheA; + } + + /** + * Obtém o próximo'Número Documento Atribuído pela Empresa' para o DetalheA. + * + * Baseado no mesmo dia. + */ + public async getNextNumeroDocumento(date: Date): Promise { + return ( + (await this.DetalheAConfRepository.count({ + where: [ + { createdAt: MoreThanOrEqual(startOfDay(date)) }, + { createdAt: LessThanOrEqual(endOfDay(date)) }, + ], + })) + 1 + ); + } + + /** + * For some reason the default eager of ClienteFavorecido doesnt get columns like cpfCnpj. + * + * So we query separately the Entity and use it. + */ + private async forceManyEager(detalhesA: DetalheAConf[]) { + const favorecidoIds = detalhesA.reduce( + (l, i) => [...l, i.clienteFavorecido.id], + [], + ); + if (favorecidoIds.length === 0) { + return; + } + const favorecidos: ClienteFavorecido[] = + await this.DetalheAConfRepository.query( + `SELECT * from cliente_favorecido c WHERE c.id IN (${favorecidoIds.join( + ',', + )})`, + ); + const favorecidosMap: Record = + favorecidos.reduce((m, i) => ({ ...m, [i.id]: i }), {}); + for (const one of detalhesA) { + one.clienteFavorecido = favorecidosMap[one.clienteFavorecido.id]; + } + } +} diff --git a/src/cnab/repository/pagamento/detalhe-b-conf.repository.ts b/src/cnab/repository/pagamento/detalhe-b-conf.repository.ts new file mode 100644 index 00000000..ee6f5353 --- /dev/null +++ b/src/cnab/repository/pagamento/detalhe-b-conf.repository.ts @@ -0,0 +1,97 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { EntityCondition } from 'src/utils/types/entity-condition.type'; +import { Nullable } from 'src/utils/types/nullable.type'; +import { DeepPartial, FindManyOptions, In, InsertResult, Repository } from 'typeorm'; +import { SaveIfNotExists } from 'src/utils/types/save-if-not-exists.type'; +import { asNumber } from 'src/utils/pipe-utils'; +import { logWarn } from 'src/utils/log-utils'; +import { DetalheBConf } from 'src/cnab/entity/conference/detalhe-b-conf.entity'; +import { DetalheBDTO } from 'src/cnab/dto/pagamento/detalhe-b.dto'; +import { DetalheAConf } from 'src/cnab/entity/conference/detalhe-a-conf.entity'; + +@Injectable() +export class DetalheBConfRepository { + private logger: Logger = new Logger('DetalheBConfRepository', { + timestamp: true, + }); + + constructor( + @InjectRepository(DetalheBConf) + private DetalheBConfRepository: Repository, + ) { } + + /** + * Any DTO existing in db will be ignored. + * + * @param dtos DTOs that can exist or not in database + * @returns Saved objects not in database. + */ + public async saveManyIfNotExists(dtos: DeepPartial[]): Promise { + // Existing + const existing = await this.findMany({ + where: dtos.reduce((l, i) => [...l, { + detalheA: { id: asNumber(i.detalheA?.id) }, + nsr: asNumber(i.nsr), + }], []) + }); + const existingMap: Record> = + existing.reduce((m, i) => ({ ...m, [DetalheAConf.getUniqueId(i)]: i }), {}); + // Check + if (existing.length === dtos.length) { + logWarn(this.logger, `${existing.length}/${dtos.length} DetalhesB já existem, nada a fazer...`); + } else if (existing.length) { + logWarn(this.logger, `${existing.length}/${dtos.length} DetalhesB já existem, ignorando...`); + return []; + } + // Save new + const newDTOs = + dtos.reduce((l, i) => [...l, ...!existingMap[DetalheAConf.getUniqueId(i)] ? [i] : []], []); + const insert = await this.insert(newDTOs); + // Return saved + const insertIds = (insert.identifiers as { id: number }[]).reduce((l, i) => [...l, i.id], []); + const saved = await this.findMany({ where: { id: In(insertIds) } }); + return saved; + } + + public insert(dtos: DeepPartial[]): Promise { + return this.DetalheBConfRepository.insert(dtos); + } + + public async saveIfNotExists(obj: DetalheBDTO): Promise> { + const existing = await this.DetalheBConfRepository.findOne({ + where: { + detalheA: { id: asNumber(obj.detalheA?.id) } + } + }); + const item = existing || await this.DetalheBConfRepository.save(obj); + return { + isNewItem: !Boolean(existing), + item: item, + } + } + + public async save(dto: DetalheBDTO): Promise { + return await this.DetalheBConfRepository.save(dto); + } + + public async getOne( + fields: EntityCondition, + ): Promise> { + return await this.DetalheBConfRepository.findOneOrFail({ + where: fields, + }); + } + + public async findOne( + fields: EntityCondition, + ): Promise> { + return await this.DetalheBConfRepository.findOne({ + where: fields, + }); + } + + public async findMany(options?: FindManyOptions): Promise { + return await this.DetalheBConfRepository.find(options); + } +} diff --git a/src/cnab/repository/pagamento/header-arquivo-conf.repository.ts b/src/cnab/repository/pagamento/header-arquivo-conf.repository.ts new file mode 100644 index 00000000..ac0763ad --- /dev/null +++ b/src/cnab/repository/pagamento/header-arquivo-conf.repository.ts @@ -0,0 +1,138 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { HeaderArquivoDTO } from 'src/cnab/dto/pagamento/header-arquivo.dto'; +import { HeaderArquivoConf } from 'src/cnab/entity/conference/header-arquivo-conf.entity'; +import { CommonHttpException } from 'src/utils/http-exception/common-http-exception'; +import { logWarn } from 'src/utils/log-utils'; +import { EntityCondition } from 'src/utils/types/entity-condition.type'; +import { SaveIfNotExists } from 'src/utils/types/save-if-not-exists.type'; +import { + DeepPartial, + FindManyOptions, + FindOneOptions, + FindOptionsOrder, + In, + Repository, +} from 'typeorm'; + + +@Injectable() +export class HeaderArquivoConfRepository { + [x: string]: any; + private logger: Logger = new Logger('HeaderArquivoConfRepository', { + timestamp: true, + }); + + constructor( + @InjectRepository(HeaderArquivoConf) + private HeaderArquivoConfRepository: Repository, + ) {} + + public async save(dto: DeepPartial): Promise { + return await this.HeaderArquivoConfRepository.save(dto); + } + + /** + * Any DTO existing in db will be ignored. + * + * @param dtos DTOs that can exist or not in database + * @returns Saved objects not in database. + */ + public async saveManyIfNotExists( + dtos: HeaderArquivoDTO[], + ): Promise { + // Existing + const existing = await this.HeaderArquivoConfRepository.find({ + where: dtos.reduce( + (l, i) => [ + ...l, + { + nsa: i.nsa, + tipoArquivo: i.tipoArquivo, + }, + ], + [], + ), + }); + const existingMap: Record = existing.reduce( + (m, i) => ({ ...m, [`${i.nsa}|${i.tipoArquivo}`]: i }), + {}, + ); + // Check + if (existing.length === dtos.length) { + logWarn( + this.logger, + `${existing.length}/${dtos.length} HeaderArquivoConfs já existem, nada a fazer...`, + ); + } else if (existing.length) { + logWarn( + this.logger, + `${existing.length}/${dtos.length} HeaderArquivoConfs já existem, ignorando...`, + ); + return []; + } + // Save new + const newDTOs = dtos.reduce( + (l, i) => [ + ...l, + ...(!existingMap[HeaderArquivoConf.getUniqueId(i)] ? [i] : []), + ], + [], + ); + const insert = await this.HeaderArquivoConfRepository.insert(newDTOs); + // Return saved + const insertIds = (insert.identifiers as { id: number }[]).reduce( + (l, i) => [...l, i.id], + [], + ); + const saved = await this.findMany({ where: { id: In(insertIds) } }); + return saved; + } + + public async saveIfNotExists( + dto: HeaderArquivoDTO, + ): Promise> { + const existing = await this.HeaderArquivoConfRepository.findOne({ + where: { + nsa: dto.nsa, + tipoArquivo: dto.tipoArquivo, + }, + }); + const item = existing || (await this.HeaderArquivoConfRepository.save(dto)); + return { + isNewItem: !Boolean(existing), + item: item, + }; + } + + public async getOne( + fields: EntityCondition, + order?: FindOptionsOrder, + ): Promise { + const headers = await this.HeaderArquivoConfRepository.find({ + where: fields, + order: order, + }); + const header = headers.pop() || null; + if (!header) { + const fieldsList = Object.entries(fields).map((i) => `(${i})`); + throw CommonHttpException.notFound( + `HeaderArquivoConf.${fieldsList.join(',')}`, + ); + } else { + return header; + } + } + + public async findOne( + options: FindOneOptions, + ): Promise { + return await this.HeaderArquivoConfRepository.findOne(options); + } + + public async findMany( + options: FindManyOptions, + ): Promise { + return await this.HeaderArquivoConfRepository.find(options); + } +} diff --git a/src/cnab/repository/pagamento/header-lote-conf.repository.ts b/src/cnab/repository/pagamento/header-lote-conf.repository.ts new file mode 100644 index 00000000..4d9bd210 --- /dev/null +++ b/src/cnab/repository/pagamento/header-lote-conf.repository.ts @@ -0,0 +1,133 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { HeaderLoteConf } from 'src/cnab/entity/conference/header-lote-conf.entity'; +import { logWarn } from 'src/utils/log-utils'; +import { asNumber } from 'src/utils/pipe-utils'; +import { EntityCondition } from 'src/utils/types/entity-condition.type'; +import { Nullable } from 'src/utils/types/nullable.type'; +import { SaveIfNotExists } from 'src/utils/types/save-if-not-exists.type'; +import { + DeepPartial, + FindManyOptions, + In, + InsertResult, + Repository, +} from 'typeorm'; + + +@Injectable() +export class HeaderLoteConfRepository { + private logger: Logger = new Logger('HeaderLoteConfRepository', { + timestamp: true, + }); + + constructor( + @InjectRepository(HeaderLoteConf) + private HeaderLoteConfRepository: Repository, + ) {} + + /** + * Any DTO existing in db will be ignored. + * + * @param dtos DTOs that can exist or not in database + * @returns Saved objects not in database. + */ + public async saveManyIfNotExists( + dtos: DeepPartial[], + ): Promise { + const existing = await this.findMany({ + // Existing + where: dtos.reduce( + (l, i) => [ + ...l, + { + headerArquivo: { id: asNumber(i.headerArquivo?.id) }, + loteServico: asNumber(i.loteServico), + }, + ], + [], + ), + }); + const existingMap: Record< + string, + DeepPartial + > = existing.reduce( + (m, i) => ({ ...m, [HeaderLoteConf.getUniqueId(i)]: i }), + {}, + ); + // Check + if (existing.length === dtos.length) { + logWarn( + this.logger, + `${existing.length}/${dtos.length} HeaderLoteConf já existem, nada a fazer...`, + ); + } else if (existing.length) { + logWarn( + this.logger, + `${existing.length}/${dtos.length} HeaderLoteConf já existem, ignorando...`, + ); + return []; + } + // Save new + const newDTOs = dtos.reduce( + (l, i) => [...l, ...(!existingMap[HeaderLoteConf.getUniqueId(i)] ? [i] : [])], + [], + ); + const insert = await this.insert(newDTOs); + // Return saved + const insertIds = (insert.identifiers as { id: number }[]).reduce( + (l, i) => [...l, i.id], + [], + ); + const saved = await this.findMany({ where: { id: In(insertIds) } }); + return saved; + } + + public async insert(dtos: DeepPartial[]): Promise { + return await this.HeaderLoteConfRepository.insert(dtos); + } + + public async saveIfNotExists( + dto: DeepPartial, + updateIfExists?: boolean, + ): Promise> { + const existing = await this.findOne({ + headerArquivo: { id: asNumber(dto.headerArquivo?.id) }, + loteServico: asNumber(dto.loteServico), + }); + const item = + !existing || (existing && updateIfExists) + ? await this.HeaderLoteConfRepository.save(dto) + : existing; + return { + isNewItem: !Boolean(existing), + item: item, + }; + } + + public async save(dto: DeepPartial): Promise { + return await this.HeaderLoteConfRepository.save(dto); + } + + public async getOne( + fields: EntityCondition, + ): Promise> { + return await this.HeaderLoteConfRepository.findOneOrFail({ + where: fields, + }); + } + + public async findOne( + fields: EntityCondition, + ): Promise> { + return await this.HeaderLoteConfRepository.findOne({ + where: fields, + }); + } + + public async findMany( + options?: FindManyOptions, + ): Promise { + return await this.HeaderLoteConfRepository.find(options); + } +} diff --git a/src/cnab/service/pagamento/detalhe-a-conf.service.ts b/src/cnab/service/pagamento/detalhe-a-conf.service.ts new file mode 100644 index 00000000..0eb79bc5 --- /dev/null +++ b/src/cnab/service/pagamento/detalhe-a-conf.service.ts @@ -0,0 +1,177 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { CnabRegistros104Pgto } from 'src/cnab/interfaces/cnab-240/104/pagamento/cnab-registros-104-pgto.interface'; +import { EntityCondition } from 'src/utils/types/entity-condition.type'; +import { Nullable } from 'src/utils/types/nullable.type'; +import { validateDTO } from 'src/utils/validation-utils'; +import { DeepPartial, FindOneOptions, ILike, In } from 'typeorm'; +import { DetalheADTO } from '../../dto/pagamento/detalhe-a.dto'; +import { DetalheA } from '../../entity/pagamento/detalhe-a.entity'; +import { ClienteFavorecidoService } from '../cliente-favorecido.service'; +import { startOfDay } from 'date-fns'; +import { TransacaoStatusEnum } from 'src/cnab/enums/pagamento/transacao-status.enum'; +import { CnabHeaderArquivo104 } from 'src/cnab/interfaces/cnab-240/104/cnab-header-arquivo-104.interface'; +import { CnabHeaderLote104Pgto } from 'src/cnab/interfaces/cnab-240/104/pagamento/cnab-header-lote-104-pgto.interface'; +import { TransacaoAgrupadoService } from './transacao-agrupado.service'; +import { CnabLote104Pgto } from 'src/cnab/interfaces/cnab-240/104/pagamento/cnab-lote-104-pgto.interface'; +import { DetalheAConfRepository } from 'src/cnab/repository/pagamento/detalhe-a-conf.repository'; + +@Injectable() +export class DetalheAConfService { + private logger: Logger = new Logger('DetalheAConfService', { timestamp: true }); + + constructor( + private detalheAConfRepository: DetalheAConfRepository, + private clienteFavorecidoService: ClienteFavorecidoService, + private transacaoAgrupadoService: TransacaoAgrupadoService + ) {} + + /** + * Assumimos que todos os detalheA do retorno têm dataVencimento na mesma semana. + */ + async updateDetalheAStatus(lote: CnabLote104Pgto) { + const dataVencimento = startOfDay( + lote.registros[0].detalheA.dataVencimento.convertedValue, + ); + const detalhesA = await this.detalheAConfRepository.findMany({ + where: { + dataVencimento: dataVencimento, + nsr: In(lote.registros.map((i) => i.detalheA.nsr.convertedValue)), + itemTransacaoAgrupado: { + transacaoAgrupado: { + status: { id: TransacaoStatusEnum.remessa }, + }, + }, + }, + }); + + // Update Transacao status + await this.transacaoAgrupadoService.updateMany( + DetalheA.getTransacaoAgIds(detalhesA), + { + status: { id: TransacaoStatusEnum.retorno }, + }, + ); + } + + /** + * Any DTO existing in db will be ignored. + * + * @param dtos DTOs that can exist or not in database + * @returns Saved objects not in database. + */ + public saveManyIfNotExists( + dtos: DeepPartial[], + ): Promise { + return this.detalheAConfRepository.saveManyIfNotExists(dtos); + } + + public async saveRetornoFrom104( + headerArq: CnabHeaderArquivo104, + headerLotePgto: CnabHeaderLote104Pgto, + registro: CnabRegistros104Pgto, + dataEfetivacao: Date, + ): Promise { + const r = registro; + const favorecido = await this.clienteFavorecidoService.findOne({ + where: { nome: ILike(`%${r.detalheA.nomeTerceiro.stringValue.trim()}%`) }, + }); + if (!favorecido) { + return null; + } + const dataVencimento = startOfDay( + registro.detalheA.dataVencimento.convertedValue, + ); + const detalheARem = await this.detalheAConfRepository.getOne({ + dataVencimento: dataVencimento, + nsr: r.detalheA.nsr.convertedValue, + itemTransacaoAgrupado: { + transacaoAgrupado: { + status: { id: TransacaoStatusEnum.remessa }, + }, + }, + }); + const detalheA = new DetalheADTO({ + id: detalheARem.id, + headerLote: { id: detalheARem.headerLote.id }, + loteServico: Number(r.detalheA.loteServico.value), + clienteFavorecido: { id: favorecido.id }, + finalidadeDOC: r.detalheA.finalidadeDOC.value, + numeroDocumentoEmpresa: Number(r.detalheA.numeroDocumentoEmpresa.value), + dataVencimento: startOfDay(r.detalheA.dataVencimento.convertedValue), + dataEfetivacao: dataEfetivacao, + tipoMoeda: r.detalheA.tipoMoeda.value, + quantidadeMoeda: Number(r.detalheA.quantidadeMoeda.value), + valorLancamento: r.detalheA.valorLancamento.convertedValue, + numeroDocumentoBanco: String( + r.detalheA.numeroDocumentoBanco.convertedValue, + ), + quantidadeParcelas: Number(r.detalheA.quantidadeParcelas.value), + indicadorBloqueio: r.detalheA.indicadorBloqueio.value, + indicadorFormaParcelamento: + r.detalheA.indicadorFormaParcelamento.stringValue, + periodoVencimento: startOfDay(r.detalheA.dataVencimento.convertedValue), + numeroParcela: r.detalheA.numeroParcela.convertedValue, + valorRealEfetivado: r.detalheA.valorRealEfetivado.convertedValue, + nsr: Number(r.detalheA.nsr.value), + ocorrenciasCnab: + headerArq.ocorrenciaCobrancaSemPapel.value.trim() || + headerLotePgto.ocorrencias.value.trim() || + r.detalheA.ocorrencias.value.trim(), + }); + const saved = await this.detalheAConfRepository.save(detalheA); + + return saved; + } + + public async save(dto: DetalheADTO): Promise { + await validateDTO(DetalheADTO, dto); + return await this.detalheAConfRepository.save(dto); + } + + public async getOne(fields: EntityCondition): Promise { + return await this.detalheAConfRepository.getOne(fields); + } + + public async findOne( + options: FindOneOptions, + ): Promise> { + return await this.detalheAConfRepository.findOne(options); + } + + public async findMany( + fields: EntityCondition, + ): Promise { + return await this.detalheAConfRepository.findMany({ where: fields }); + } + + /** + * numeroDocumento: + * + * - Começa com 000001 + * - Soma 1 para cada registro no arquivo + * - Reinicia para 1 para cada data de pagamento + * + * Usado nos Dertalhes: A, J, O, N + * + * @example + * 01/01/2024 + * - Cnab1 + * - DetalheA = 1 + * - DetalheA = 2 + * - Cnab2 + * - DetalheA = 3 + * - DetalheA = 4 + * + * 02/01/2024 + * - Cnab1 + * - DetalheA = 1 + * - DetalheA = 2 + * - Cnab2 + * - DetalheA = 3 + * - DetalheA = 4 + * + */ + public async getNextNumeroDocumento(date: Date): Promise { + return await this.detalheAConfRepository.getNextNumeroDocumento(date); + } +} diff --git a/src/cnab/service/pagamento/detalhe-a.service.ts b/src/cnab/service/pagamento/detalhe-a.service.ts index f0b76d85..aec54f2b 100644 --- a/src/cnab/service/pagamento/detalhe-a.service.ts +++ b/src/cnab/service/pagamento/detalhe-a.service.ts @@ -13,7 +13,6 @@ import { TransacaoStatusEnum } from 'src/cnab/enums/pagamento/transacao-status.e import { CnabHeaderArquivo104 } from 'src/cnab/interfaces/cnab-240/104/cnab-header-arquivo-104.interface'; import { CnabHeaderLote104Pgto } from 'src/cnab/interfaces/cnab-240/104/pagamento/cnab-header-lote-104-pgto.interface'; import { TransacaoAgrupadoService } from './transacao-agrupado.service'; -import { ItemTransacaoAgrupadoService } from './item-transacao-agrupado.service'; import { CnabLote104Pgto } from 'src/cnab/interfaces/cnab-240/104/pagamento/cnab-lote-104-pgto.interface'; @Injectable() @@ -23,8 +22,7 @@ export class DetalheAService { constructor( private detalheARepository: DetalheARepository, private clienteFavorecidoService: ClienteFavorecidoService, - private transacaoAgrupadoService: TransacaoAgrupadoService, - private itemTransacaoAgrupadoService: ItemTransacaoAgrupadoService, + private transacaoAgrupadoService: TransacaoAgrupadoService ) {} /** diff --git a/src/cnab/service/pagamento/detalhe-b-conf.service.ts b/src/cnab/service/pagamento/detalhe-b-conf.service.ts new file mode 100644 index 00000000..3ea6abd2 --- /dev/null +++ b/src/cnab/service/pagamento/detalhe-b-conf.service.ts @@ -0,0 +1,65 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { DetalheA } from 'src/cnab/entity/pagamento/detalhe-a.entity'; +import { CnabRegistros104Pgto } from 'src/cnab/interfaces/cnab-240/104/pagamento/cnab-registros-104-pgto.interface'; +import { asCnabFieldDate } from 'src/cnab/utils/cnab/cnab-field-pipe-utils'; +import { EntityCondition } from 'src/utils/types/entity-condition.type'; +import { Nullable } from 'src/utils/types/nullable.type'; +import { validateDTO } from 'src/utils/validation-utils'; +import { DeepPartial } from 'typeorm'; + +import { startOfDay } from 'date-fns'; +import { DetalheBConf } from 'src/cnab/entity/conference/detalhe-b-conf.entity'; +import { DetalheBDTO } from 'src/cnab/dto/pagamento/detalhe-b.dto'; +import { DetalheBConfRepository } from 'src/cnab/repository/pagamento/detalhe-b-conf.repository'; + +@Injectable() +export class DetalheBConfService { + private logger: Logger = new Logger('DetalheBConfService', { timestamp: true }); + + constructor(private detalheBConfRepository: DetalheBConfRepository) {} + + /** + * Any DTO existing in db will be ignored. + * + * @param dtos DTOs that can exist or not in database + * @returns Saved objects not in database. + */ + public saveManyIfNotExists( + dtos: DeepPartial[], + ): Promise { + return this.detalheBConfRepository.saveManyIfNotExists(dtos); + } + + public async saveFrom104( + registro: CnabRegistros104Pgto, + detalheAUpdated: DetalheA, + ): Promise { + const DetalheBConfRem = await this.detalheBConfRepository.getOne({ + detalheA: { id: detalheAUpdated.id }, + }); + const DetalheBConf = new DetalheBDTO({ + id: DetalheBConfRem?.id, + detalheA: { id: detalheAUpdated.id }, + nsr: registro.detalheB.nsr.convertedValue, + dataVencimento: startOfDay(asCnabFieldDate(registro.detalheB.dataVencimento)), + }); + return await this.detalheBConfRepository.save(DetalheBConf); + } + + public async save(dto: DetalheBDTO): Promise { + await validateDTO(DetalheBDTO, dto); + await this.detalheBConfRepository.save(dto); + } + + public async findOne( + fields: EntityCondition, + ): Promise> { + return await this.detalheBConfRepository.findOne(fields); + } + + public async findMany( + fields: EntityCondition, + ): Promise { + return await this.detalheBConfRepository.findMany({ where: fields }); + } +} diff --git a/src/cnab/service/pagamento/header-arquivo-conf.service.ts b/src/cnab/service/pagamento/header-arquivo-conf.service.ts new file mode 100644 index 00000000..73eb26b5 --- /dev/null +++ b/src/cnab/service/pagamento/header-arquivo-conf.service.ts @@ -0,0 +1,174 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { HeaderArquivoStatus } from 'src/cnab/entity/pagamento/header-arquivo-status.entity'; +import { TransacaoAgrupado } from 'src/cnab/entity/pagamento/transacao-agrupado.entity'; +import { HeaderArquivoStatusEnum } from 'src/cnab/enums/pagamento/header-arquivo-status.enum'; +import { TransacaoStatusEnum } from 'src/cnab/enums/pagamento/transacao-status.enum'; +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 { cnabSettings } from 'src/settings/cnab.settings'; +import { SettingsService } from 'src/settings/settings.service'; +import { getBRTFromUTC } from 'src/utils/date-utils'; +import { EntityCondition } from 'src/utils/types/entity-condition.type'; +import { SaveIfNotExists } from 'src/utils/types/save-if-not-exists.type'; +import { DeepPartial, FindOptionsWhere } from 'typeorm'; +import { PagadorService } from './pagador.service'; +import { HeaderArquivoConfRepository } from 'src/cnab/repository/pagamento/header-arquivo-conf.repository'; +import { HeaderArquivoTipoArquivo } from 'src/cnab/enums/pagamento/header-arquivo-tipo-arquivo.enum'; +import { HeaderArquivoDTO } from 'src/cnab/dto/pagamento/header-arquivo.dto'; +import { HeaderArquivoConf } from 'src/cnab/entity/conference/header-arquivo-conf.entity'; + +const PgtoRegistros = Cnab104PgtoTemplates.file104.registros; + +@Injectable() +export class HeaderArquivoConfService { + private logger: Logger = new Logger('HeaderArquivoConfService', { + timestamp: true, + }); + + constructor( + private headerArquivoRepository: HeaderArquivoConfRepository, + private pagadorService: PagadorService, + private settingsService: SettingsService, + ) {} + + /** + * Generate new HaderArquivo from Transacao + */ + public async getDTO( + tipo_arquivo: HeaderArquivoTipoArquivo, + transacaoAg: TransacaoAgrupado, + ): Promise { + const now = getBRTFromUTC(new Date()); + const pagador = await this.pagadorService.getOneByIdPagador( + transacaoAg?.pagador.id, + ); + const dto = new HeaderArquivoDTO({ + agencia: pagador.agencia, + codigoBanco: PgtoRegistros.headerArquivo.codigoBanco.value, + tipoInscricao: PgtoRegistros.headerArquivo.tipoInscricao.value, + numeroInscricao: String(pagador.cpfCnpj), + codigoConvenio: PgtoRegistros.headerArquivo.codigoConvenioBanco.value, + parametroTransmissao: + PgtoRegistros.headerArquivo.parametroTransmissao.value, + dataGeracao: now, + horaGeracao: now, + dvAgencia: pagador.dvAgencia, + dvConta: pagador.dvConta, + transacaoAgrupado: transacaoAg, + nomeEmpresa: pagador.nomeEmpresa, + numeroConta: pagador.conta, + tipoArquivo: tipo_arquivo, + nsa: await this.getNextNSA(), + status: new HeaderArquivoStatus(HeaderArquivoStatusEnum.remessa), + }); + return dto; + } + + public async saveRetornoFrom104( + cnab104: CnabFile104Pgto, + headerArquivoRemessa: HeaderArquivoConf, + ) { + const headerArquivoRem = await this.headerArquivoRepository.getOne({ + nsa: cnab104.headerArquivo.nsa.convertedValue, + }); + const headerArquivo = new HeaderArquivoDTO({ + id: headerArquivoRem.id, + tipoArquivo: HeaderArquivoTipoArquivo.Retorno, + codigoBanco: cnab104.headerArquivo.codigoBanco.stringValue, + tipoInscricao: cnab104.headerArquivo.tipoInscricao.stringValue, + numeroInscricao: cnab104.headerArquivo.numeroInscricao.stringValue, + codigoConvenio: cnab104.headerArquivo.codigoConvenioBanco.stringValue, + parametroTransmissao: + cnab104.headerArquivo.parametroTransmissao.stringValue, + agencia: cnab104.headerArquivo.agenciaContaCorrente.stringValue, + dvAgencia: cnab104.headerArquivo.dvAgencia.stringValue, + numeroConta: cnab104.headerArquivo.numeroConta.stringValue, + dvConta: cnab104.headerArquivo.dvConta.stringValue, + nomeEmpresa: cnab104.headerArquivo.nomeEmpresa.convertedValue, + dataGeracao: cnab104.headerArquivo.dataGeracaoArquivo.convertedValue, + horaGeracao: cnab104.headerArquivo.horaGeracaoArquivo.convertedValue, + transacaoAgrupado: headerArquivoRemessa.transacaoAgrupado, + transacao: headerArquivoRemessa.transacao, + nsa: cnab104.headerArquivo.nsa.convertedValue, + status: new HeaderArquivoStatus(HeaderArquivoStatusEnum.retorno), + }); + return await this.headerArquivoRepository.save(headerArquivo); + } + + /** + * Find HeaderArquivoConf Remessa ready to save in ArquivoPublicacao + */ + public async findRetornos(): Promise { + return this.headerArquivoRepository.findMany({ + where: [ + { + transacaoAgrupado: { status: { id: TransacaoStatusEnum.retorno } }, + }, + ], + }); + } + + public async findOne( + fields: FindOptionsWhere | FindOptionsWhere[], + ): Promise { + return ( + (await this.headerArquivoRepository.findOne({ where: fields })) || null + ); + } + + public async findMany( + fields: FindOptionsWhere | FindOptionsWhere[], + ): Promise { + return this.headerArquivoRepository.findMany({ where: fields }); + } + + /** + * key: HeaderArquivoConf unique id + */ + public saveManyIfNotExists( + dtos: HeaderArquivoDTO[], + ): Promise { + return this.headerArquivoRepository.saveManyIfNotExists(dtos); + } + + public async saveIfNotExists( + dto: HeaderArquivoDTO, + ): Promise> { + return await this.headerArquivoRepository.saveIfNotExists(dto); + } + + public async save(dto: DeepPartial): Promise { + return await this.headerArquivoRepository.save(dto); + } + + public async getNextNSA(): Promise { + const maxNsa = + ( + await this.headerArquivoRepository.findMany({ + order: { + nsa: 'DESC', + }, + take: 1, + }) + ).pop()?.nsa || 0; + const settingNSA = parseInt( + ( + await this.settingsService.getOneBySettingData( + cnabSettings.any__cnab_current_nsa, + ) + ).value, + ); + const nextNSA = (maxNsa > settingNSA ? maxNsa : settingNSA) + 1; + await this.settingsService.updateBySettingData( + cnabSettings.any__cnab_current_nsa, + String(nextNSA), + ); + return nextNSA; + } + + public async getOne( + fields: EntityCondition, + ): Promise { + return await this.headerArquivoRepository.getOne(fields); + } +} diff --git a/src/cnab/service/pagamento/header-lote-conf.service.ts b/src/cnab/service/pagamento/header-lote-conf.service.ts new file mode 100644 index 00000000..d005c585 --- /dev/null +++ b/src/cnab/service/pagamento/header-lote-conf.service.ts @@ -0,0 +1,110 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { HeaderArquivoDTO } from 'src/cnab/dto/pagamento/header-arquivo.dto'; +import { HeaderArquivo } from 'src/cnab/entity/pagamento/header-arquivo.entity'; +import { Pagador } from 'src/cnab/entity/pagamento/pagador.entity'; +import { CnabLote104Pgto } from 'src/cnab/interfaces/cnab-240/104/pagamento/cnab-lote-104-pgto.interface'; +import { Cnab104PgtoTemplates } from 'src/cnab/templates/cnab-240/104/pagamento/cnab-104-pgto-templates.const'; +import { EntityCondition } from 'src/utils/types/entity-condition.type'; +import { Nullable } from 'src/utils/types/nullable.type'; +import { SaveIfNotExists } from 'src/utils/types/save-if-not-exists.type'; +import { DeepPartial } from 'typeorm'; +import { PagadorService } from './pagador.service'; +import { Cnab104FormaLancamento } from 'src/cnab/enums/104/cnab-104-forma-lancamento.enum'; +import { HeaderLoteConfRepository } from 'src/cnab/repository/pagamento/header-lote-conf.repository'; +import { HeaderLoteDTO } from 'src/cnab/dto/pagamento/header-lote.dto'; +import { HeaderLoteConf } from 'src/cnab/entity/conference/header-lote-conf.entity'; + +const PgtoRegistros = Cnab104PgtoTemplates.file104.registros; + +@Injectable() +export class HeaderLoteConfService { + private logger: Logger = new Logger('HeaderLoteConfService', { timestamp: true }); + + constructor( + private headerLoteConfRepository: HeaderLoteConfRepository, + private pagadorService: PagadorService, + ) {} + + /** + * From Transacao, HeaderArquivo transforms into HeaderLoteConf. + * + * `loteServico` será atualizado com o valor gerado automaticamente em `updateHeaderLoteConfDTOFrom104()`! + */ + public getDTO( + headerArquivo: HeaderArquivoDTO, + pagador: Pagador, + formaLancamento: Cnab104FormaLancamento, + ): HeaderLoteDTO { + const dto = new HeaderLoteDTO({ + codigoConvenioBanco: headerArquivo.codigoConvenio, + pagador: pagador, + numeroInscricao: headerArquivo.numeroInscricao, + parametroTransmissao: headerArquivo.parametroTransmissao, + tipoCompromisso: String(PgtoRegistros.headerLote.tipoCompromisso.value), + tipoInscricao: headerArquivo.tipoInscricao, + headerArquivo: headerArquivo, + loteServico: 1, + formaLancamento, + }); + return dto; + } + + public async saveFrom104( + lote: CnabLote104Pgto, + headerArquivoUpdated: HeaderArquivo, + ): Promise> { + const pagador = await this.pagadorService.getByConta( + lote.headerLote.numeroConta.value, + ); + const headerLoteRem = await this.headerLoteConfRepository.findOne({ + headerArquivo: { id: headerArquivoUpdated.id }, + loteServico: lote.headerLote.loteServico.convertedValue, + }); + const headerLote = new HeaderLoteConf({ + id: headerLoteRem?.id, + headerArquivo: { id: headerArquivoUpdated.id }, + loteServico: lote.headerLote.loteServico.convertedValue, + codigoConvenioBanco: lote.headerLote.codigoConvenioBanco.stringValue, + numeroInscricao: lote.headerLote.numeroInscricao.stringValue, + parametroTransmissao: lote.headerLote.parametroTransmissao.stringValue, + tipoCompromisso: lote.headerLote.tipoCompromisso.stringValue, + formaLancamento: lote.headerLote.formaLancamento.stringValue, + tipoInscricao: lote.headerLote.tipoInscricao.stringValue, + pagador: { id: pagador.id }, + }); + return await this.headerLoteConfRepository.saveIfNotExists(headerLote); + } + + /** + * Any DTO existing in db will be ignored. + * + * @param dtos DTOs that can exist or not in database + * @returns Saved objects not in database. + */ + public saveManyIfNotExists( + dtos: DeepPartial[], + ): Promise { + return this.headerLoteConfRepository.saveManyIfNotExists(dtos); + } + + public async save(dto: DeepPartial): Promise { + // await validateDTO(HeaderLoteConfDTO, dto); + return await this.headerLoteConfRepository.save(dto); + } + + public async saveDto(dto: HeaderLoteDTO): Promise { + return await this.headerLoteConfRepository.save(dto); + } + + public async findOne( + fields: EntityCondition, + ): Promise> { + return await this.headerLoteConfRepository.findOne(fields); + } + + public async findMany( + fields: EntityCondition, + ): Promise { + return await this.headerLoteConfRepository.findMany({ where: fields }); + } +} diff --git a/src/cnab/service/pagamento/remessa-retorno.service.ts b/src/cnab/service/pagamento/remessa-retorno.service.ts index bd282f1a..bb806af2 100644 --- a/src/cnab/service/pagamento/remessa-retorno.service.ts +++ b/src/cnab/service/pagamento/remessa-retorno.service.ts @@ -7,14 +7,11 @@ import { ClienteFavorecido } from 'src/cnab/entity/cliente-favorecido.entity'; import { ItemTransacaoAgrupado } from 'src/cnab/entity/pagamento/item-transacao-agrupado.entity'; import { Pagador } from 'src/cnab/entity/pagamento/pagador.entity'; import { TransacaoAgrupado } from 'src/cnab/entity/pagamento/transacao-agrupado.entity'; -import { TransacaoStatus } from 'src/cnab/entity/pagamento/transacao-status.entity'; import { Cnab104FormaLancamento } from 'src/cnab/enums/104/cnab-104-forma-lancamento.enum'; -import { TransacaoStatusEnum } from 'src/cnab/enums/pagamento/transacao-status.enum'; import { CnabTrailerArquivo104 } from 'src/cnab/interfaces/cnab-240/104/cnab-trailer-arquivo-104.interface'; 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 { SettingsService } from 'src/settings/settings.service'; import { CustomLogger } from 'src/utils/custom-logger'; import { asNumber, asString } from 'src/utils/pipe-utils'; import { DeepPartial } from 'typeorm'; @@ -26,7 +23,6 @@ import { CnabDetalheA_104 } from '../../interfaces/cnab-240/104/pagamento/cnab-d import { CnabDetalheB_104 } from '../../interfaces/cnab-240/104/pagamento/cnab-detalhe-b-104.interface'; import { CnabHeaderLote104Pgto } from '../../interfaces/cnab-240/104/pagamento/cnab-header-lote-104-pgto.interface'; import { CnabRegistros104Pgto } from '../../interfaces/cnab-240/104/pagamento/cnab-registros-104-pgto.interface'; -import { stringifyCnab104File } from '../../utils/cnab/cnab-104-utils'; import { getTipoInscricao } from '../../utils/cnab/cnab-utils'; import { DetalheAService } from './detalhe-a.service'; import { DetalheBService } from './detalhe-b.service'; @@ -34,7 +30,10 @@ import { HeaderArquivoService } from './header-arquivo.service'; import { HeaderLoteService } from './header-lote.service'; import { ItemTransacaoAgrupadoService } from './item-transacao-agrupado.service'; import { ItemTransacaoService } from './item-transacao.service'; -import { TransacaoAgrupadoService } from './transacao-agrupado.service'; +import { HeaderArquivoConfService } from './header-arquivo-conf.service'; +import { HeaderLoteConfService } from './header-lote-conf.service'; +import { DetalheAConfService } from './detalhe-a-conf.service'; +import { DetalheBConfService } from './detalhe-b-conf.service'; const sc = structuredClone; const PgtoRegistros = Cnab104PgtoTemplates.file104.registros; @@ -45,21 +44,30 @@ export class RemessaRetornoService { timestamp: true, }); - constructor( - private headerArquivoService: HeaderArquivoService, - private headerLoteService: HeaderLoteService, - private transacaoAgService: TransacaoAgrupadoService, + constructor( private itemTransacaoService: ItemTransacaoService, private itemTransacaoAgService: ItemTransacaoAgrupadoService, + private headerArquivoService: HeaderArquivoService, + private headerLoteService: HeaderLoteService, private detalheAService: DetalheAService, private detalheBService: DetalheBService, - private settingsService: SettingsService, + private headerArquivoConfService: HeaderArquivoConfService, + private headerLoteConfService: HeaderLoteConfService, + private detalheAConfService: DetalheAConfService, + private detalheBConfService: DetalheBConfService ) {} - public async saveHeaderArquivoDTO(transacaoAg: TransacaoAgrupado): Promise{ - const headerArquivoDTO = await this.headerArquivoService.getDTO(HeaderArquivoTipoArquivo.Remessa,transacaoAg); - const headerArquivo = await this.headerArquivoService.save(headerArquivoDTO); - headerArquivoDTO.id = headerArquivo.id; + public async saveHeaderArquivoDTO(transacaoAg: TransacaoAgrupado,isConference): Promise{ + let headerArquivoDTO; + if(!isConference){ + headerArquivoDTO = await this.headerArquivoService.getDTO(HeaderArquivoTipoArquivo.Remessa,transacaoAg); + const headerArquivo = await this.headerArquivoService.save(headerArquivoDTO); + headerArquivoDTO.id = headerArquivo.id; + }else{ + headerArquivoDTO = await this.headerArquivoConfService.getDTO(HeaderArquivoTipoArquivo.Remessa,transacaoAg); + const headerArquivo = await this.headerArquivoConfService.save(headerArquivoDTO); + headerArquivoDTO.id = headerArquivo.id; + } return headerArquivoDTO; } @@ -76,9 +84,8 @@ export class RemessaRetornoService { * (se banco do favorecido = banco do pagador - pagador é sempre Caixa) * - senão, TED (41) */ - public async getLotes( - pagador: Pagador, - headerArquivoDTO: HeaderArquivoDTO, + public async getLotes(pagador: Pagador,headerArquivoDTO: HeaderArquivoDTO, + dataPgto: Date | undefined,isConference:boolean ) { const transacaoAg = headerArquivoDTO.transacaoAgrupado as TransacaoAgrupado; const itemTransacaoAgs = @@ -94,20 +101,30 @@ export class RemessaRetornoService { for (const item of itemTransacaoAgs) { if(item.clienteFavorecido.codigoBanco !== '104'){ //TED nsrTed ++; - if(loteTed == undefined){ - loteTed = this.headerLoteService.getDTO(headerArquivoDTO,pagador,Cnab104FormaLancamento.TED) - loteTed = await this.headerLoteService.saveDto(loteTed); + if(loteTed == undefined){ + if(!isConference){ + loteTed = this.headerLoteService.getDTO(headerArquivoDTO,pagador,Cnab104FormaLancamento.TED) + loteTed = await this.headerLoteService.saveDto(loteTed); + }else{ + loteTed = this.headerLoteConfService.getDTO(headerArquivoDTO,pagador,Cnab104FormaLancamento.TED) + loteTed = await this.headerLoteConfService.saveDto(loteTed); + } } - const detalhes104 = await this.saveListDetalhes(loteTed,[item],nsrTed); + const detalhes104 = await this.saveListDetalhes(loteTed,[item],nsrTed,dataPgto,isConference); nsrTed ++; loteTed.registros104.push(...detalhes104); }else{ //Credito em Conta nsrCC++; if(loteCC == undefined){ - loteCC = this.headerLoteService.getDTO(headerArquivoDTO,pagador,Cnab104FormaLancamento.CreditoContaCorrente) - loteCC = await this.headerLoteService.saveDto(loteCC); + if(!isConference){ + loteCC = this.headerLoteService.getDTO(headerArquivoDTO,pagador,Cnab104FormaLancamento.CreditoContaCorrente) + loteCC = await this.headerLoteService.saveDto(loteCC); + }else{ + loteCC = this.headerLoteConfService.getDTO(headerArquivoDTO,pagador,Cnab104FormaLancamento.CreditoContaCorrente) + loteCC = await this.headerLoteConfService.saveDto(loteCC); + } } - const detalhes104 = await this.saveListDetalhes(loteCC, [item],nsrCC); + const detalhes104 = await this.saveListDetalhes(loteCC, [item],nsrCC,dataPgto,isConference); nsrCC++; loteCC.registros104.push(...detalhes104); } @@ -125,13 +142,25 @@ export class RemessaRetornoService { detalheA: CnabDetalheA_104, headerLoteId: number, itemTransacaoAg: ItemTransacaoAgrupado, + isConference:boolean ) { - const existing = await this.detalheAService.findOne({ - where: { - nsr: Number(detalheA.nsr.value), - itemTransacaoAgrupado: { id: itemTransacaoAg?.id }, - }, - }); + let existing; + if(!isConference){ + existing = await this.detalheAService.findOne({ + where: { + nsr: Number(detalheA.nsr.value), + itemTransacaoAgrupado: { id: itemTransacaoAg?.id }, + }, + }); + }else{ + existing = await this.detalheAConfService.findOne({ + where: { + nsr: Number(detalheA.nsr.value), + itemTransacaoAgrupado: { id: itemTransacaoAg?.id }, + }, + }); + } + const favorecidoId = itemTransacaoAg.clienteFavorecido.id as number; return new DetalheADTO({ ...(existing ? { id: existing.id } : {}), @@ -197,14 +226,15 @@ export class RemessaRetornoService { * * @returns Detalhes104 gerados a partir dos ItemTransacaoAg */ - async saveListDetalhes(headerLoteDto: HeaderLoteDTO,itemTransacoes: ItemTransacaoAgrupado[], nsr): Promise { + async saveListDetalhes(headerLoteDto: HeaderLoteDTO, itemTransacoes: ItemTransacaoAgrupado[], + nsr: number, dataPgto: Date | undefined, isConference: boolean): Promise { let numeroDocumento = await this.detalheAService.getNextNumeroDocumento(new Date()); // Para cada itemTransacao, cria detalhe const detalhes: CnabRegistros104Pgto[] = []; let itemTransacaoAgAux: ItemTransacaoAgrupado | undefined; for (const itemTransacao of itemTransacoes) { itemTransacaoAgAux = itemTransacao as ItemTransacaoAgrupado; - const detalhe = await this.saveDetalhes104(numeroDocumento,headerLoteDto, itemTransacaoAgAux,nsr); + const detalhe = await this.saveDetalhes104(numeroDocumento,headerLoteDto, itemTransacaoAgAux,nsr,dataPgto,isConference); if (detalhe) { detalhes.push(detalhe); } @@ -322,7 +352,8 @@ export class RemessaRetornoService { * * @param numeroDocumento Managed by company. It must be a new number. * @returns null if failed ItemTransacao to CNAB */ - public async saveDetalhes104(numeroDocumento: number, headerLote: HeaderLoteDTO, itemTransacaoAg: ItemTransacaoAgrupado, nsr: number) + public async saveDetalhes104(numeroDocumento: number, headerLote: HeaderLoteDTO, + itemTransacaoAg: ItemTransacaoAgrupado, nsr: number, dataPgto: Date | undefined, isConference: boolean) : Promise { const METHOD = 'getDetalhes104()'; const favorecido = itemTransacaoAg.clienteFavorecido as ClienteFavorecido; @@ -340,8 +371,13 @@ export class RemessaRetornoService { return null; } - // Save detalheA - + if(dataPgto){ + if(dataPgto < new Date()){ + dataPgto = new Date(); + } + } + + // Save detalheA const itemTransacaoAgAux = itemTransacaoAg; const fridayOrdem = itemTransacaoAgAux.dataOrdem; const detalheA: CnabDetalheA_104 = sc(PgtoRegistros.detalheA); @@ -352,15 +388,16 @@ export class RemessaRetornoService { detalheA.dvContaDestino.value = favorecido.dvContaCorrente; detalheA.nomeTerceiro.value = favorecido.nome; detalheA.numeroDocumentoEmpresa.value = numeroDocumento; - detalheA.dataVencimento.value = fridayOrdem; + if(dataPgto == undefined) { + detalheA.dataVencimento.value = fridayOrdem; + }else{ + detalheA.dataVencimento.value = dataPgto; + } detalheA.valorLancamento.value = itemTransacaoAgAux.valor; detalheA.nsr.value = nsr; const savedDetalheA = await this.saveDetalheA( - detalheA, - asNumber(headerLote.id), - itemTransacaoAg, - ); + detalheA, asNumber(headerLote.id),itemTransacaoAg,isConference); // DetalheB const detalheB: CnabDetalheB_104 = sc(PgtoRegistros.detalheB); @@ -368,7 +405,11 @@ export class RemessaRetornoService { asString(favorecido.cpfCnpj), ); detalheB.numeroInscricao.value = asString(favorecido.cpfCnpj); - detalheB.dataVencimento.value = detalheA.dataVencimento.value; + if(dataPgto == undefined){ + detalheB.dataVencimento.value = detalheA.dataVencimento.value; + }else{ + detalheB.dataVencimento.value = dataPgto; + } // Favorecido address detalheB.logradouro.value = favorecido.logradouro; detalheB.numeroLocal.value = favorecido.numero; @@ -380,7 +421,7 @@ export class RemessaRetornoService { detalheB.siglaEstado.value = favorecido.uf; detalheB.nsr.value = nsr + 1; - await this.saveDetalheB(detalheB, savedDetalheA.id); + await this.saveDetalheB(detalheB,savedDetalheA.id,isConference); return { detalheA: detalheA, @@ -389,25 +430,33 @@ export class RemessaRetornoService { } async saveDetalheA( - detalheA104: CnabDetalheA_104, - savedHeaderLoteId: number, - itemTransacaoAg: ItemTransacaoAgrupado, +detalheA104: CnabDetalheA_104, savedHeaderLoteId: number, itemTransacaoAg: ItemTransacaoAgrupado, isConference: boolean, ) { const detalheADTO = await this.convertCnabDetalheAToDTO( detalheA104, savedHeaderLoteId, itemTransacaoAg, + isConference ); - const saved = await this.detalheAService.save(detalheADTO); - return await this.detalheAService.getOne({ id: saved.id }); + if(!isConference){ + const saved = await this.detalheAService.save(detalheADTO); + return await this.detalheAService.getOne({ id: saved.id }); + }else{ + const saved = await this.detalheAConfService.save(detalheADTO); + return await this.detalheAConfService.getOne({ id: saved.id }); + } } - async saveDetalheB(detalheB104: CnabDetalheB_104, savedDetalheAId: number) { + async saveDetalheB(detalheB104: CnabDetalheB_104, savedDetalheAId: number, isConference: boolean) { const detalheBDTO = await this.convertCnabDetalheBToDTO( detalheB104, savedDetalheAId, ); - await this.detalheBService.save(detalheBDTO); + if(!isConference){ + await this.detalheBService.save(detalheBDTO); + }else{ + await this.detalheBConfService.save(detalheBDTO); + } } // #endregion diff --git a/src/cron-jobs/cron-jobs.service.ts b/src/cron-jobs/cron-jobs.service.ts index bc8ac855..66a6ef95 100644 --- a/src/cron-jobs/cron-jobs.service.ts +++ b/src/cron-jobs/cron-jobs.service.ts @@ -95,10 +95,10 @@ export class CronJobsService implements OnModuleInit, OnModuleLoad { async onModuleLoad() { const THIS_CLASS_WITH_METHOD = 'CronJobsService.onModuleLoad'; - await this.saveTransacoesJae1(0,'Todos'); - await this.saveAndSendRemessa(); + //await this.saveTransacoesJae1(0,'Todos',new Date()); + //await this.saveAndSendRemessa(new Date(),true); // await this.cnabService.updateTransacaoViewBigquery(); - // await this.cnabService.compareTransacaoViewPublicacao(14); + //await this.cnabService.compareTransacaoViewPublicacao(14); this.jobsConfig.push( { @@ -154,7 +154,7 @@ export class CronJobsService implements OnModuleInit, OnModuleLoad { cronJobParameters: { cronTime: '0 3 * * 5', // Every friday, 00:00 BRT = 03:00 GMT onTick: async () => { - await this.saveTransacoesJae1(); + await this.saveTransacoesJae1(0,'Todos',undefined); }, }, }, @@ -163,7 +163,7 @@ export class CronJobsService implements OnModuleInit, OnModuleLoad { cronJobParameters: { cronTime: '0 4 * * 5', // Every friday, 01:00 BRT = 04:00 GMT onTick: async () => { - await this.saveAndSendRemessa(); + await this.saveAndSendRemessa(undefined); }, }, }, @@ -201,8 +201,8 @@ export class CronJobsService implements OnModuleInit, OnModuleLoad { job.start(); } - public async saveAndSendRemessa(){ - const listCnabStr = await this.cnabService.saveRemessa(PagadorContaEnum.ContaBilhetagem); + public async saveAndSendRemessa(dataPgto: Date | undefined,isConference=false){ + const listCnabStr = await this.cnabService.saveRemessa(PagadorContaEnum.ContaBilhetagem,dataPgto,isConference); if(listCnabStr) await this.sendRemessa(listCnabStr); } @@ -735,7 +735,7 @@ export class CronJobsService implements OnModuleInit, OnModuleLoad { return cnabJobEnabled.getValueAsBoolean(); } - async saveTransacoesJae1(daysBefore = 0,consorcio='Todos') { + async saveTransacoesJae1(daysBefore = 0,consorcio='Todos',dataPgto: Date | undefined) { const METHOD = this.saveTransacoesJae1.name; if (!(await this.getIsCnabJobEnabled(METHOD))) { @@ -744,7 +744,7 @@ export class CronJobsService implements OnModuleInit, OnModuleLoad { try { this.logger.log('Iniciando tarefa.', METHOD); - await this.cnabService.saveTransacoesJae(daysBefore,consorcio); + await this.cnabService.saveTransacoesJae(daysBefore,consorcio,dataPgto); this.logger.log('Tabelas para o Jaé atualizados com sucesso.', METHOD); } catch (error) { this.logger.error(`ERRO CRÍTICO - ${error}`, error?.stack, METHOD); @@ -762,13 +762,13 @@ export class CronJobsService implements OnModuleInit, OnModuleLoad { try { this.logger.log('Iniciando tarefa.', METHOD); - await this.cnabService.saveTransacoesJae(); + await this.cnabService.saveTransacoesJae(0,'Todos',undefined); this.logger.log('Tabelas para o Jaé atualizados com sucesso.', METHOD); } catch (error) { this.logger.error( `ERRO CRÍTICO (TENTATIVA 2) = ${error}`, error.stack, - METHOD, + METHOD ); this.deleteCron(CrobJobsEnum.saveTransacoesJae2); } diff --git a/src/database/migrations/1719949229037-conference.ts b/src/database/migrations/1719949229037-conference.ts new file mode 100644 index 00000000..90233403 --- /dev/null +++ b/src/database/migrations/1719949229037-conference.ts @@ -0,0 +1,40 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class Conference1719949229037 implements MigrationInterface { + name = 'Conference1719949229037' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`CREATE TABLE "header_arquivo_conf" ("id" SERIAL NOT NULL, "tipoArquivo" integer NOT NULL, "codigoBanco" character varying(3), "tipoInscricao" character varying(2), "numeroInscricao" character varying(14), "codigoConvenio" character varying(6), "parametroTransmissao" character varying(2), "agencia" character varying(5), "dvAgencia" character varying(1), "numeroConta" character varying(12), "dvConta" character varying(1), "nomeEmpresa" character varying(100), "dataGeracao" TIMESTAMP, "horaGeracao" TIME, "nsa" integer NOT NULL, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "transacaoId" integer, "transacaoAgrupadoId" integer, CONSTRAINT "PK_HeaderArquivoConf_id" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE TABLE "header_lote_conf" ("id" SERIAL NOT NULL, "loteServico" integer, "tipoInscricao" character varying, "numeroInscricao" character varying, "codigoConvenioBanco" character varying, "tipoCompromisso" character varying, "parametroTransmissao" character varying, "formaLancamento" character varying, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "headerArquivoId" integer, "pagadorId" integer, CONSTRAINT "PK_HeaderLoteConf_id" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE TABLE "detalhe_a_conf" ("id" SERIAL NOT NULL, "ocorrenciasCnab" character varying(30), "loteServico" integer, "finalidadeDOC" character varying, "numeroDocumentoEmpresa" integer NOT NULL, "dataVencimento" TIMESTAMP, "tipoMoeda" character varying, "quantidadeMoeda" numeric(10,5), "valorLancamento" numeric(13,2), "numeroDocumentoBanco" character varying, "quantidadeParcelas" integer, "indicadorBloqueio" character varying, "indicadorFormaParcelamento" character varying, "periodoVencimento" TIMESTAMP, "numeroParcela" integer, "dataEfetivacao" TIMESTAMP, "valorRealEfetivado" numeric(13,2), "nsr" integer NOT NULL, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "headerLoteId" integer, "clienteFavorecidoId" integer, "itemTransacaoAgrupadoId" integer NOT NULL, CONSTRAINT "REL_5a006e496627c740a941d4b48a" UNIQUE ("itemTransacaoAgrupadoId"), CONSTRAINT "PK_DetalheAConf_id" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE TABLE "detalhe_b_conf" ("id" SERIAL NOT NULL, "nsr" integer NOT NULL, "dataVencimento" TIMESTAMP NOT NULL, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "detalheAId" integer, CONSTRAINT "REL_ed5fc663251f22dd7bb9bd9c18" UNIQUE ("detalheAId"), CONSTRAINT "PK_DetalheBConf_id" PRIMARY KEY ("id"))`); + await queryRunner.query(`ALTER TABLE "file" ALTER COLUMN "id" SET DEFAULT uuid_generate_v4()`); + await queryRunner.query(`ALTER TABLE "user" ADD CONSTRAINT "FK_User_photo_ManyToOne" FOREIGN KEY ("photoId") REFERENCES "file"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "header_arquivo_conf" ADD CONSTRAINT "FK_HeaderArquivoConf_transacao_ManyToOne" FOREIGN KEY ("transacaoId") REFERENCES "transacao"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "header_arquivo_conf" ADD CONSTRAINT "FK_HeaderArquivoConf_transacaoAgrupado_ManyToOne" FOREIGN KEY ("transacaoAgrupadoId") REFERENCES "transacao_agrupado"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "header_lote_conf" ADD CONSTRAINT "FK_HeaderLoteConf_headerArquivo_ManyToOne" FOREIGN KEY ("headerArquivoId") REFERENCES "header_arquivo_conf"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "header_lote_conf" ADD CONSTRAINT "FK_HeaderLoteConf_pagador_ManyToOne" FOREIGN KEY ("pagadorId") REFERENCES "pagador"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "detalhe_a_conf" ADD CONSTRAINT "FK_DetalheAConf_headerLote_ManyToOne" FOREIGN KEY ("headerLoteId") REFERENCES "header_lote_conf"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "detalhe_a_conf" ADD CONSTRAINT "FK_DetalheAConf_clienteFavorecido_ManyToOne" FOREIGN KEY ("clienteFavorecidoId") REFERENCES "cliente_favorecido"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "detalhe_a_conf" ADD CONSTRAINT "FK_DetalheAConf_itemTransacaoAgrupado_OneToOne" FOREIGN KEY ("itemTransacaoAgrupadoId") REFERENCES "item_transacao_agrupado"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "detalhe_b_conf" ADD CONSTRAINT "FK_DetalheBConf_detalheA_OneToOne" FOREIGN KEY ("detalheAId") REFERENCES "detalhe_a_conf"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "detalhe_b_conf" DROP CONSTRAINT "FK_DetalheBConf_detalheA_OneToOne"`); + await queryRunner.query(`ALTER TABLE "detalhe_a_conf" DROP CONSTRAINT "FK_DetalheAConf_itemTransacaoAgrupado_OneToOne"`); + await queryRunner.query(`ALTER TABLE "detalhe_a_conf" DROP CONSTRAINT "FK_DetalheAConf_clienteFavorecido_ManyToOne"`); + await queryRunner.query(`ALTER TABLE "detalhe_a_conf" DROP CONSTRAINT "FK_DetalheAConf_headerLote_ManyToOne"`); + await queryRunner.query(`ALTER TABLE "header_lote_conf" DROP CONSTRAINT "FK_HeaderLoteConf_pagador_ManyToOne"`); + await queryRunner.query(`ALTER TABLE "header_lote_conf" DROP CONSTRAINT "FK_HeaderLoteConf_headerArquivo_ManyToOne"`); + await queryRunner.query(`ALTER TABLE "header_arquivo_conf" DROP CONSTRAINT "FK_HeaderArquivoConf_transacaoAgrupado_ManyToOne"`); + await queryRunner.query(`ALTER TABLE "header_arquivo_conf" DROP CONSTRAINT "FK_HeaderArquivoConf_transacao_ManyToOne"`); + await queryRunner.query(`ALTER TABLE "user" DROP CONSTRAINT "FK_User_photo_ManyToOne"`); + await queryRunner.query(`ALTER TABLE "file" ALTER COLUMN "id" DROP DEFAULT`); + await queryRunner.query(`DROP TABLE "detalhe_b_conf"`); + await queryRunner.query(`DROP TABLE "detalhe_a_conf"`); + await queryRunner.query(`DROP TABLE "header_lote_conf"`); + await queryRunner.query(`DROP TABLE "header_arquivo_conf"`); + } + +}