eust-conciliation
Version:
142 lines (112 loc) • 5.38 kB
text/typescript
/* eslint-disable no-await-in-loop */
/* eslint-disable no-restricted-syntax */
import { Inject } from '@severo-tech/injection-decorator';
import { IConciliationClient, IInvoiceClient, ILogger } from '../../../contracts';
import { Invoice } from '../../../../domain/invoice';
import { CreateEustIssueUseCase } from '../..';
import { Debit } from '../../../../domain/debit';
import { InvoiceInCancellationPeriod, MultipleInvoices } from '../../../../domain/errors';
import { EustIssueType, EustIssueUnit } from '../../../../domain/eust-issue';
import { CombinationsUtil } from '../../../utils';
export abstract class ConciliationBase {
('ZenviaLogger')
protected readonly logger!: ILogger;
('Invoice01EUSTClient')
private readonly innerInvoiceClient!: IInvoiceClient;
('Conciliation01EUSTClient')
private readonly innerConciliationClient!: IConciliationClient;
('CreateEustIssueUseCase')
private readonly innerCreateEustIssueUseCase!: CreateEustIssueUseCase;
('CombinationsUtil')
private readonly innerCombinationsUtil!: CombinationsUtil;
protected async tryToConciliateDebits(startDate: Date, endDate: Date, debits: Debit[], emitterDocument: string, recipientDocument: string, value: number): Promise<void> {
try {
const invoices = await this.getInvoicesToConciliate(startDate, endDate, emitterDocument, recipientDocument, value);
if (invoices?.length) {
this.logger.debug(`"ConciliationBase.tryToConciliateDebits" - Invoice Conciliated! UserCNPJ [${recipientDocument}] | TransmitterCNPJ [${emitterDocument}] | Invoice [${JSON.stringify(invoices)}] | Value [${value}]`);
await this.conciliateDebits(invoices, debits, false);
}
} catch (error) {
await this.handleError(error, debits);
}
}
protected async tryToConciliateInvoice(startDate: Date, endDate: Date, debits: Debit[], emitterDocument: string, recipientDocument: string): Promise<void> {
const invoices = await this.innerInvoiceClient.getUnreconciled({
startDate,
endDate,
transmitterCNPJ: emitterDocument,
userCNPJ: recipientDocument,
});
let innerDebits = [...debits];
for (const invoice of invoices) {
const combinations = this.innerCombinationsUtil.byValue(innerDebits, +invoice.value);
if (combinations.length === 1) {
const [match] = combinations;
try {
await this.validateInvoicesIssueDate([invoice]);
await this.conciliateDebits([invoice], match, false);
} catch (error) {
await this.handleError(error, debits);
}
innerDebits = innerDebits
.filter((debit: Debit): boolean => !match.some((el: Debit): boolean => el.id === debit.id));
}
}
}
protected async conciliateDebits(invoices: Invoice[], debits: Debit[], handleError = true): Promise<void> {
try {
await this.innerConciliationClient.conciliate(debits, invoices);
} catch (error) {
if (handleError) await this.handleError(error, debits);
throw error;
}
}
protected async getInvoicesToConciliate(startDate: Date, endDate: Date, emitterDocument: string, recipientDocument: string, value: number): Promise<Invoice[]> {
const candidates = await this.innerInvoiceClient.getUnreconciled({
startDate,
endDate,
transmitterCNPJ: emitterDocument,
userCNPJ: recipientDocument,
});
if (!candidates?.length) {
return []; // Cannot conciliate
}
const invoices = this.processCandidates(candidates, value);
await this.validateInvoicesIssueDate(invoices);
return invoices;
}
protected async validateInvoicesIssueDate(invoices: Invoice[]): Promise<void> {
const notOlderEnough = invoices.filter(({ createdAt }: Invoice): boolean => !this.isOlderThan24Hours(createdAt));
if (notOlderEnough?.length) {
const numbers = notOlderEnough.map(({ number }: Invoice): string => number);
throw new InvoiceInCancellationPeriod(`There is(are) ${numbers.length} invoice(s) that is(are) under the Cancellation Period. [${numbers.join(', ')}]`);
}
}
private isOlderThan24Hours(date: Date): boolean {
const currentDate = new Date();
const diff = (currentDate.getTime() - date.getTime()) / 1000 / 60 / 60;
return diff >= 24;
}
private processCandidates(candidates: Invoice[], value: number): Invoice[] {
const lazyCandidates = candidates.map((el: Invoice) => ({ id: el.id, value: el.value }));
const invoiceCombinations = this.innerCombinationsUtil.byValue(lazyCandidates, value, true);
if (!invoiceCombinations?.length) {
return [];
}
if (invoiceCombinations.length > 1) {
throw new MultipleInvoices(`Found ${invoiceCombinations.length} invoice combinations. User must to do manual conciliation.`);
}
return invoiceCombinations[0]
.map(({ id }): Invoice => candidates.find((el: Invoice): boolean => el.id === id) as Invoice);
}
protected async handleError(error: any, debits: Debit[]): Promise<void> {
await this.innerCreateEustIssueUseCase.execute({
unit: EustIssueUnit.Conciliation,
type: error?.code ?? EustIssueType.InternalError,
debitIds: debits.map(({ id }: Debit): string => id),
description: error?.message,
causedBy: error,
resolved: !!error?.resolved,
});
}
}