refactor: tenant proxy providers

This commit is contained in:
Ahmed Bouhuolia
2025-02-15 23:52:12 +02:00
parent 36851d3209
commit 5c0bb52b59
302 changed files with 2396 additions and 1677 deletions

View File

@@ -3,7 +3,10 @@ import * as moment from 'moment';
import { first, sumBy } from 'lodash';
import { PromisePool } from '@supercharge/promise-pool';
import { Inject, Injectable } from '@nestjs/common';
import { GetMatchedTransactionsFilter, MatchedTransactionsPOJO } from '../types';
import {
GetMatchedTransactionsFilter,
MatchedTransactionsPOJO,
} from '../types';
import { GetMatchedTransactionsByExpenses } from './GetMatchedTransactionsByExpenses';
import { GetMatchedTransactionsByBills } from './GetMatchedTransactionsByBills.service';
import { GetMatchedTransactionsByManualJournals } from './GetMatchedTransactionsByManualJournals.service';
@@ -11,6 +14,7 @@ import { GetMatchedTransactionsByCashflow } from './GetMatchedTransactionsByCash
import { GetMatchedTransactionsByInvoices } from './GetMatchedTransactionsByInvoices.service';
import { UncategorizedBankTransaction } from '@/modules/BankingTransactions/models/UncategorizedBankTransaction';
import { sortClosestMatchTransactions } from '../_utils';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class GetMatchedTransactions {
@@ -22,7 +26,9 @@ export class GetMatchedTransactions {
private readonly getMatchedCashflowService: GetMatchedTransactionsByCashflow,
@Inject(UncategorizedBankTransaction.name)
private readonly uncategorizedBankTransactionModel: typeof UncategorizedBankTransaction,
private readonly uncategorizedBankTransactionModel: TenantModelProxy<
typeof UncategorizedBankTransaction
>,
) {}
/**
@@ -46,10 +52,11 @@ export class GetMatchedTransactions {
*/
public async getMatchedTransactions(
uncategorizedTransactionIds: Array<number>,
filter: GetMatchedTransactionsFilter
filter: GetMatchedTransactionsFilter,
): Promise<MatchedTransactionsPOJO> {
const uncategorizedTransactions =
await this.uncategorizedBankTransactionModel.query()
await this.uncategorizedBankTransactionModel()
.query()
.whereIn('id', uncategorizedTransactionIds)
.throwIfNotFound();
@@ -66,7 +73,7 @@ export class GetMatchedTransactions {
});
const { perfectMatches, possibleMatches } = this.groupMatchedResults(
uncategorizedTransactions,
matchedTransactions
matchedTransactions,
);
return {
perfectMatches,
@@ -84,7 +91,7 @@ export class GetMatchedTransactions {
*/
private groupMatchedResults(
uncategorizedTransactions: Array<any>,
matchedTransactions
matchedTransactions,
): MatchedTransactionsPOJO {
const results = R.compose(R.flatten)(matchedTransactions?.results);
@@ -97,7 +104,7 @@ export class GetMatchedTransactions {
const perfectMatches = R.filter(
(match) =>
match.amount === amount && moment(match.date).isSame(date, 'day'),
closestResullts
closestResullts,
);
const possibleMatches = R.difference(closestResullts, perfectMatches);
const totalPending = sumBy(uncategorizedTransactions, 'amount');

View File

@@ -13,6 +13,7 @@ import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectab
import { IBillPaymentDTO } from '@/modules/BillPayments/types/BillPayments.types';
import { Bill } from '@/modules/Bills/models/Bill';
import { UncategorizedBankTransaction } from '@/modules/BankingTransactions/models/UncategorizedBankTransaction';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class GetMatchedTransactionsByBills extends GetMatchedTransactionsByType {
@@ -21,10 +22,12 @@ export class GetMatchedTransactionsByBills extends GetMatchedTransactionsByType
private readonly transformer: TransformerInjectable,
@Inject(Bill.name)
private readonly billModel: typeof Bill,
private readonly billModel: TenantModelProxy<typeof Bill>,
@Inject(UncategorizedBankTransaction.name)
private readonly uncategorizedBankTransactionModel: typeof UncategorizedBankTransaction,
private readonly uncategorizedBankTransactionModel: TenantModelProxy<
typeof UncategorizedBankTransaction
>,
) {
super();
}
@@ -34,23 +37,23 @@ export class GetMatchedTransactionsByBills extends GetMatchedTransactionsByType
* @param {number} tenantId -
* @param {GetMatchedTransactionsFilter} filter -
*/
public async getMatchedTransactions(
filter: GetMatchedTransactionsFilter,
) {
public async getMatchedTransactions(filter: GetMatchedTransactionsFilter) {
// Retrieves the bill matches.
const bills = await Bill.query().onBuild((q) => {
q.withGraphJoined('matchedBankTransaction');
q.whereNull('matchedBankTransaction.id');
q.modify('published');
const bills = await this.billModel()
.query()
.onBuild((q) => {
q.withGraphJoined('matchedBankTransaction');
q.whereNull('matchedBankTransaction.id');
q.modify('published');
if (filter.fromDate) {
q.where('billDate', '>=', filter.fromDate);
}
if (filter.toDate) {
q.where('billDate', '<=', filter.toDate);
}
q.orderBy('billDate', 'DESC');
});
if (filter.fromDate) {
q.where('billDate', '>=', filter.fromDate);
}
if (filter.toDate) {
q.where('billDate', '<=', filter.toDate);
}
q.orderBy('billDate', 'DESC');
});
return this.transformer.transform(
bills,
@@ -67,7 +70,7 @@ export class GetMatchedTransactionsByBills extends GetMatchedTransactionsByType
public async getMatchedTransaction(
transactionId: number,
): Promise<MatchedTransactionPOJO> {
const bill = await this.billModel
const bill = await this.billModel()
.query()
.findById(transactionId)
.throwIfNotFound();
@@ -97,12 +100,12 @@ export class GetMatchedTransactionsByBills extends GetMatchedTransactionsByType
);
const uncategorizedTransactionId = first(uncategorizedTransactionIds);
const uncategorizedTransaction =
await this.uncategorizedBankTransactionModel
await this.uncategorizedBankTransactionModel()
.query(trx)
.findById(uncategorizedTransactionId)
.throwIfNotFound();
const bill = await this.billModel
const bill = await this.billModel()
.query(trx)
.findById(matchTransactionDTO.referenceId)
.throwIfNotFound();

View File

@@ -4,6 +4,7 @@ import { GetMatchedTransactionsFilter } from '../types';
import { BankTransaction } from '@/modules/BankingTransactions/models/BankTransaction';
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
import { Inject, Injectable } from '@nestjs/common';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class GetMatchedTransactionsByCashflow extends GetMatchedTransactionsByType {
@@ -11,7 +12,9 @@ export class GetMatchedTransactionsByCashflow extends GetMatchedTransactionsByTy
private readonly transformer: TransformerInjectable,
@Inject(BankTransaction.name)
private readonly bankTransactionModel: typeof BankTransaction,
private readonly bankTransactionModel: TenantModelProxy<
typeof BankTransaction
>,
) {
super();
}
@@ -25,7 +28,7 @@ export class GetMatchedTransactionsByCashflow extends GetMatchedTransactionsByTy
async getMatchedTransactions(
filter: Omit<GetMatchedTransactionsFilter, 'transactionType'>,
) {
const transactions = await this.bankTransactionModel
const transactions = await this.bankTransactionModel()
.query()
.onBuild((q) => {
// Not matched to bank transaction.
@@ -60,7 +63,7 @@ export class GetMatchedTransactionsByCashflow extends GetMatchedTransactionsByTy
* @returns
*/
async getMatchedTransaction(transactionId: number) {
const transactions = await this.bankTransactionModel
const transactions = await this.bankTransactionModel()
.query()
.findById(transactionId)
.withGraphJoined('matchedBankTransaction')

View File

@@ -4,6 +4,7 @@ import { GetMatchedTransactionsByType } from './GetMatchedTransactionsByType';
import { GetMatchedTransactionExpensesTransformer } from './GetMatchedTransactionExpensesTransformer';
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
import { Expense } from '@/modules/Expenses/models/Expense.model';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class GetMatchedTransactionsByExpenses extends GetMatchedTransactionsByType {
@@ -11,7 +12,7 @@ export class GetMatchedTransactionsByExpenses extends GetMatchedTransactionsByTy
protected readonly transformer: TransformerInjectable,
@Inject(Expense.name)
protected readonly expenseModel: typeof Expense,
protected readonly expenseModel: TenantModelProxy<typeof Expense>,
) {
super();
}
@@ -24,28 +25,30 @@ export class GetMatchedTransactionsByExpenses extends GetMatchedTransactionsByTy
*/
async getMatchedTransactions(filter: GetMatchedTransactionsFilter) {
// Retrieve the expense matches.
const expenses = await this.expenseModel.query().onBuild((query) => {
// Filter out the not matched to bank transactions.
query.withGraphJoined('matchedBankTransaction');
query.whereNull('matchedBankTransaction.id');
const expenses = await this.expenseModel()
.query()
.onBuild((query) => {
// Filter out the not matched to bank transactions.
query.withGraphJoined('matchedBankTransaction');
query.whereNull('matchedBankTransaction.id');
// Filter the published onyl
query.modify('filterByPublished');
// Filter the published onyl
query.modify('filterByPublished');
if (filter.fromDate) {
query.where('paymentDate', '>=', filter.fromDate);
}
if (filter.toDate) {
query.where('paymentDate', '<=', filter.toDate);
}
if (filter.minAmount) {
query.where('totalAmount', '>=', filter.minAmount);
}
if (filter.maxAmount) {
query.where('totalAmount', '<=', filter.maxAmount);
}
query.orderBy('paymentDate', 'DESC');
});
if (filter.fromDate) {
query.where('paymentDate', '>=', filter.fromDate);
}
if (filter.toDate) {
query.where('paymentDate', '<=', filter.toDate);
}
if (filter.minAmount) {
query.where('totalAmount', '>=', filter.minAmount);
}
if (filter.maxAmount) {
query.where('totalAmount', '<=', filter.maxAmount);
}
query.orderBy('paymentDate', 'DESC');
});
return this.transformer.transform(
expenses,
new GetMatchedTransactionExpensesTransformer(),
@@ -61,7 +64,7 @@ export class GetMatchedTransactionsByExpenses extends GetMatchedTransactionsByTy
public async getMatchedTransaction(
transactionId: number,
): Promise<MatchedTransactionPOJO> {
const expense = await this.expenseModel
const expense = await this.expenseModel()
.query()
.findById(transactionId)
.throwIfNotFound();

View File

@@ -14,6 +14,7 @@ import { SaleInvoice } from '@/modules/SaleInvoices/models/SaleInvoice';
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
import { UncategorizedBankTransaction } from '@/modules/BankingTransactions/models/UncategorizedBankTransaction';
import { IPaymentReceivedCreateDTO } from '@/modules/PaymentReceived/types/PaymentReceived.types';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class GetMatchedTransactionsByInvoices extends GetMatchedTransactionsByType {
@@ -22,10 +23,12 @@ export class GetMatchedTransactionsByInvoices extends GetMatchedTransactionsByTy
private readonly createPaymentReceivedService: CreatePaymentReceivedService,
@Inject(SaleInvoice.name)
private readonly saleInvoiceModel: typeof SaleInvoice,
private readonly saleInvoiceModel: TenantModelProxy<typeof SaleInvoice>,
@Inject(UncategorizedBankTransaction.name)
private readonly uncategorizedBankTransactionModel: typeof UncategorizedBankTransaction,
private readonly uncategorizedBankTransactionModel: TenantModelProxy<
typeof UncategorizedBankTransaction
>,
) {
super();
}
@@ -36,27 +39,29 @@ export class GetMatchedTransactionsByInvoices extends GetMatchedTransactionsByTy
* @returns {Promise<MatchedTransactionsPOJO>}
*/
public async getMatchedTransactions(
filter: GetMatchedTransactionsFilter
filter: GetMatchedTransactionsFilter,
): Promise<MatchedTransactionsPOJO> {
// Retrieve the invoices that not matched, unpaid.
const invoices = await this.saleInvoiceModel.query().onBuild((q) => {
q.withGraphJoined('matchedBankTransaction');
q.whereNull('matchedBankTransaction.id');
q.modify('unpaid');
q.modify('published');
const invoices = await this.saleInvoiceModel()
.query()
.onBuild((q) => {
q.withGraphJoined('matchedBankTransaction');
q.whereNull('matchedBankTransaction.id');
q.modify('unpaid');
q.modify('published');
if (filter.fromDate) {
q.where('invoiceDate', '>=', filter.fromDate);
}
if (filter.toDate) {
q.where('invoiceDate', '<=', filter.toDate);
}
q.orderBy('invoiceDate', 'DESC');
});
if (filter.fromDate) {
q.where('invoiceDate', '>=', filter.fromDate);
}
if (filter.toDate) {
q.where('invoiceDate', '<=', filter.toDate);
}
q.orderBy('invoiceDate', 'DESC');
});
return this.transformer.transform(
invoices,
new GetMatchedTransactionInvoicesTransformer()
new GetMatchedTransactionInvoicesTransformer(),
);
}
@@ -67,13 +72,15 @@ export class GetMatchedTransactionsByInvoices extends GetMatchedTransactionsByTy
* @returns {Promise<MatchedTransactionPOJO>}
*/
public async getMatchedTransaction(
transactionId: number
transactionId: number,
): Promise<MatchedTransactionPOJO> {
const invoice = await this.saleInvoiceModel.query().findById(transactionId);
const invoice = await this.saleInvoiceModel()
.query()
.findById(transactionId);
return this.transformer.transform(
invoice,
new GetMatchedTransactionInvoicesTransformer()
new GetMatchedTransactionInvoicesTransformer(),
);
}
@@ -87,16 +94,17 @@ export class GetMatchedTransactionsByInvoices extends GetMatchedTransactionsByTy
public async createMatchedTransaction(
uncategorizedTransactionIds: Array<number>,
matchTransactionDTO: IMatchTransactionDTO,
trx?: Knex.Transaction
trx?: Knex.Transaction,
) {
await super.createMatchedTransaction(
uncategorizedTransactionIds,
matchTransactionDTO,
trx
trx,
);
const uncategorizedTransactionId = first(uncategorizedTransactionIds);
const uncategorizedTransaction =
await this.uncategorizedBankTransactionModel.query(trx)
await this.uncategorizedBankTransactionModel()
.query(trx)
.findById(uncategorizedTransactionId)
.throwIfNotFound();
@@ -119,14 +127,19 @@ export class GetMatchedTransactionsByInvoices extends GetMatchedTransactionsByTy
branchId: invoice.branchId,
};
// Create a payment received associated to the matched invoice.
const paymentReceived = await this.createPaymentReceivedService.createPaymentReceived(
createPaymentReceivedDTO,
trx
);
const paymentReceived =
await this.createPaymentReceivedService.createPaymentReceived(
createPaymentReceivedDTO,
trx,
);
// Link the create payment received with matched invoice transaction.
await super.createMatchedTransaction(uncategorizedTransactionIds, {
referenceType: 'PaymentReceive',
referenceId: paymentReceived.id,
}, trx)
await super.createMatchedTransaction(
uncategorizedTransactionIds,
{
referenceType: 'PaymentReceive',
referenceId: paymentReceived.id,
},
trx,
);
}
}

View File

@@ -4,6 +4,7 @@ import { GetMatchedTransactionsByType } from './GetMatchedTransactionsByType';
import { GetMatchedTransactionsFilter } from '../types';
import { ManualJournal } from '@/modules/ManualJournals/models/ManualJournal';
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class GetMatchedTransactionsByManualJournals extends GetMatchedTransactionsByType {
@@ -11,14 +12,13 @@ export class GetMatchedTransactionsByManualJournals extends GetMatchedTransactio
private readonly transformer: TransformerInjectable,
@Inject(ManualJournal.name)
private readonly manualJournalModel: typeof ManualJournal,
private readonly manualJournalModel: TenantModelProxy<typeof ManualJournal>,
) {
super();
}
/**
* Retrieve the matched transactions of manual journals.
* @param {number} tenantId
* @param {GetMatchedTransactionsFilter} filter
* @returns
*/
@@ -28,27 +28,29 @@ export class GetMatchedTransactionsByManualJournals extends GetMatchedTransactio
// @todo: get the account id from the filter
const accountId = 1000;
const manualJournals = await this.manualJournalModel.query().onBuild((query) => {
query.withGraphJoined('matchedBankTransaction');
query.whereNull('matchedBankTransaction.id');
const manualJournals = await this.manualJournalModel()
.query()
.onBuild((query) => {
query.withGraphJoined('matchedBankTransaction');
query.whereNull('matchedBankTransaction.id');
query.withGraphJoined('entries');
query.where('entries.accountId', accountId);
query.modify('filterByPublished');
query.withGraphJoined('entries');
query.where('entries.accountId', accountId);
query.modify('filterByPublished');
if (filter.fromDate) {
query.where('date', '>=', filter.fromDate);
}
if (filter.toDate) {
query.where('date', '<=', filter.toDate);
}
if (filter.minAmount) {
query.where('amount', '>=', filter.minAmount);
}
if (filter.maxAmount) {
query.where('amount', '<=', filter.maxAmount);
}
});
if (filter.fromDate) {
query.where('date', '>=', filter.fromDate);
}
if (filter.toDate) {
query.where('date', '<=', filter.toDate);
}
if (filter.minAmount) {
query.where('amount', '>=', filter.minAmount);
}
if (filter.maxAmount) {
query.where('amount', '<=', filter.maxAmount);
}
});
return this.transformer.transform(
manualJournals,
new GetMatchedTransactionManualJournalsTransformer(),
@@ -62,7 +64,8 @@ export class GetMatchedTransactionsByManualJournals extends GetMatchedTransactio
* @returns
*/
public async getMatchedTransaction(transactionId: number) {
const manualJournal = await this.manualJournalModel.query()
const manualJournal = await this.manualJournalModel()
.query()
.findById(transactionId)
.whereNotExists(ManualJournal.relatedQuery('matchedBankTransaction'))
.throwIfNotFound();

View File

@@ -8,10 +8,13 @@ import {
import PromisePool from '@supercharge/promise-pool';
import { MatchedBankTransaction } from '../models/MatchedBankTransaction';
import { Inject } from '@nestjs/common';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
export abstract class GetMatchedTransactionsByType {
@Inject(MatchedBankTransaction.name)
private readonly matchedBankTransactionModel: typeof MatchedBankTransaction;
private readonly matchedBankTransactionModel: TenantModelProxy<
typeof MatchedBankTransaction
>;
/**
* Retrieves the matched transactions.
@@ -20,10 +23,10 @@ export abstract class GetMatchedTransactionsByType {
* @returns {Promise<MatchedTransactionsPOJO>}
*/
public async getMatchedTransactions(
filter: GetMatchedTransactionsFilter
filter: GetMatchedTransactionsFilter,
): Promise<MatchedTransactionsPOJO> {
throw new Error(
'The `getMatchedTransactions` method is not defined for the transaction type.'
'The `getMatchedTransactions` method is not defined for the transaction type.',
);
}
@@ -34,10 +37,10 @@ export abstract class GetMatchedTransactionsByType {
* @returns {Promise<MatchedTransactionPOJO>}
*/
public async getMatchedTransaction(
transactionId: number
transactionId: number,
): Promise<MatchedTransactionPOJO> {
throw new Error(
'The `getMatchedTransaction` method is not defined for the transaction type.'
'The `getMatchedTransaction` method is not defined for the transaction type.',
);
}
@@ -51,12 +54,12 @@ export abstract class GetMatchedTransactionsByType {
public async createMatchedTransaction(
uncategorizedTransactionIds: Array<number>,
matchTransactionDTO: IMatchTransactionDTO,
trx?: Knex.Transaction
trx?: Knex.Transaction,
) {
await PromisePool.withConcurrency(2)
.for(uncategorizedTransactionIds)
.process(async (uncategorizedTransactionId) => {
await this.matchedBankTransactionModel.query(trx).insert({
await this.matchedBankTransactionModel().query(trx).insert({
uncategorizedTransactionId,
referenceType: matchTransactionDTO.referenceType,
referenceId: matchTransactionDTO.referenceId,