feat(nestjs): migrate to NestJS

This commit is contained in:
Ahmed Bouhuolia
2025-04-07 11:51:24 +02:00
parent f068218a16
commit 55fcc908ef
3779 changed files with 631 additions and 195332 deletions

View File

@@ -0,0 +1,66 @@
import { Account } from '../../Accounts/models/Account.model';
import { Transformer } from '../../Transformer/Transformer';
export class CashflowAccountTransformer extends Transformer {
/**
* Include these attributes to sale invoice object.
* @returns {string[]}
*/
public includeAttributes = (): string[] => {
return [
'formattedAmount',
'lastFeedsUpdatedAt',
'lastFeedsUpdatedAtFormatted',
'lastFeedsUpdatedFromNow',
];
};
/**
* Exclude these attributes to sale invoice object.
* @returns {string[]}
*/
public excludeAttributes = (): string[] => {
return [
'predefined',
'index',
'accountRootType',
'accountTypeLabel',
'accountParentType',
'isBalanceSheetAccount',
'isPlSheet',
];
};
/**
* Retrieve formatted account amount.
* @param {IAccount} invoice
* @returns {string}
*/
protected formattedAmount = (account: Account): string => {
return this.formatNumber(account.amount, {
currencyCode: account.currencyCode,
});
};
/**
* Retrieves the last feeds update at formatted date.
* @param {IAccount} account
* @returns {string}
*/
protected lastFeedsUpdatedAtFormatted(account: Account): string {
return account.lastFeedsUpdatedAt
? this.formatDate(account.lastFeedsUpdatedAt)
: '';
}
/**
* Retrieves the last feeds updated from now.
* @param {IAccount} account
* @returns {string}
*/
protected lastFeedsUpdatedFromNow(account: Account): string {
return account.lastFeedsUpdatedAt
? this.formatDateFromNow(account.lastFeedsUpdatedAt)
: '';
}
}

View File

@@ -0,0 +1,55 @@
import { Transformer } from '../../Transformer/Transformer';
export class BankTransactionTransformer extends Transformer {
/**
* Include these attributes to sale invoice object.
* @returns {string[]}
*/
public includeAttributes = (): string[] => {
return [
'formattedAmount',
'transactionTypeFormatted',
'formattedDate',
'formattedCreatedAt',
];
};
/**
* Formatted amount.
* @param {} transaction
* @returns {string}
*/
protected formattedAmount = (transaction) => {
return this.formatNumber(transaction.amount, {
currencyCode: transaction.currencyCode,
excerptZero: true,
});
};
/**
* Formatted transaction type.
* @param transaction
* @returns {string}
*/
protected transactionTypeFormatted = (transaction) => {
return this.context.i18n.t(transaction.transactionType);
};
/**
* Retrieve the formatted transaction date.
* @param invoice
* @returns {string}
*/
protected formattedDate = (invoice): string => {
return this.formatDate(invoice.date);
};
/**
* Retrieve the formatted created at date.
* @param invoice
* @returns {string}
*/
protected formattedCreatedAt = (invoice): string => {
return this.formatDate(invoice.createdAt);
};
}

View File

@@ -0,0 +1,70 @@
import { Transformer } from "@/modules/Transformer/Transformer";
export class BankTransactionsTransformer extends Transformer {
/**
* Include these attributes to sale invoice object.
* @returns {string[]}
*/
public includeAttributes = (): string[] => {
return ['deposit', 'withdrawal', 'formattedDeposit', 'formattedWithdrawal'];
};
/**
* Exclude these attributes.
* @returns {string[]}
*/
public excludeAttributes = (): string[] => {
return [
'credit',
'debit',
'index',
'index_group',
'item_id',
'item_quantity',
'contact_type',
'contact_id',
];
};
/**
* Deposit amount attribute.
* @param transaction
* @returns
*/
protected deposit = (transaction) => {
return transaction.debit;
};
/**
* Withdrawal amount attribute.
* @param transaction
* @returns
*/
protected withdrawal = (transaction) => {
return transaction.credit;
};
/**
* Formatted withdrawal amount.
* @param transaction
* @returns
*/
protected formattedWithdrawal = (transaction) => {
return this.formatNumber(transaction.credit, {
currencyCode: transaction.currencyCode,
excerptZero: true,
});
};
/**
* Formatted deposit account.
* @param transaction
* @returns
*/
protected formattedDeposit = (transaction) => {
return this.formatNumber(transaction.debit, {
currencyCode: transaction.currencyCode,
excerptZero: true,
});
};
}

View File

@@ -0,0 +1,54 @@
// @ts-nocheck
import { Injectable, Inject } from '@nestjs/common';
import { BankAccount } from '../models/BankAccount';
import { DynamicListService } from '@/modules/DynamicListing/DynamicList.service';
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
import { CashflowAccountTransformer } from './BankAccountTransformer';
import { ACCOUNT_TYPE } from '@/constants/accounts';
import { IBankAccountsFilter } from '../types/BankingTransactions.types';
@Injectable()
export class GetBankAccountsService {
constructor(
private readonly dynamicListService: DynamicListService,
private readonly transformer: TransformerInjectable,
@Inject(BankAccount.name)
private readonly bankAccountModel: typeof BankAccount,
) {}
/**
* Retrieve the cash flow accounts.
* @param {ICashflowAccountsFilter} filterDTO - Filter DTO.
* @returns {ICashflowAccount[]}
*/
public async getBankAccounts(
filterDTO: IBankAccountsFilter,
): Promise<BankAccount[]> {
// Parsees accounts list filter DTO.
const filter = this.dynamicListService.parseStringifiedFilter(filterDTO);
// Dynamic list service.
const dynamicList = await this.dynamicListService.dynamicList(
this.bankAccountModel,
filter,
);
// Retrieve accounts model based on the given query.
const accounts = await this.bankAccountModel.query().onBuild((builder) => {
dynamicList.buildQuery()(builder);
builder.whereIn('account_type', [
ACCOUNT_TYPE.BANK,
ACCOUNT_TYPE.CASH,
ACCOUNT_TYPE.CREDIT_CARD,
]);
builder.modify('inactiveMode', filter.inactiveMode);
});
// Retrieves the transformed accounts.
const transformed = await this.transformer.transform(
accounts,
new CashflowAccountTransformer(),
);
return transformed;
}
}

View File

@@ -0,0 +1,51 @@
import { Inject, Injectable } from '@nestjs/common';
import { ERRORS } from '../constants';
import { BankTransaction } from '../models/BankTransaction';
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
import { ServiceError } from '@/modules/Items/ServiceError';
import { BankTransactionTransformer } from './BankTransactionTransformer';
@Injectable()
export class GetBankTransactionService {
constructor(
@Inject(BankTransaction.name)
private readonly bankTransactionModel: typeof BankTransaction,
private readonly transformer: TransformerInjectable,
) {}
/**
* Retrieve the given cashflow transaction.
* @param {number} cashflowTransactionId
* @returns
*/
public async getBankTransaction(cashflowTransactionId: number) {
const cashflowTransaction = await this.bankTransactionModel
.query()
.findById(cashflowTransactionId)
.withGraphFetched('entries.cashflowAccount')
.withGraphFetched('entries.creditAccount')
.withGraphFetched('transactions.account')
.orderBy('date', 'DESC')
.throwIfNotFound();
this.throwErrorCashflowTransactionNotFound(cashflowTransaction);
// Transforms the cashflow transaction model to POJO.
return this.transformer.transform(
cashflowTransaction,
new BankTransactionTransformer(),
);
}
/**
* Throw not found error if the given cashflow is undefined.
* @param {BankTransaction} bankTransaction - Bank transaction.
*/
private throwErrorCashflowTransactionNotFound(
bankTransaction: BankTransaction,
) {
if (!bankTransaction) {
throw new ServiceError(ERRORS.CASHFLOW_TRANSACTION_NOT_FOUND);
}
}
}

View File

@@ -0,0 +1,48 @@
import { Inject, Injectable } from '@nestjs/common';
import { GetPendingBankAccountTransactionTransformer } from './GetPendingBankAccountTransactionTransformer';
import { UncategorizedBankTransaction } from '../models/UncategorizedBankTransaction';
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
@Injectable()
export class GetPendingBankAccountTransactions {
constructor(
private readonly transformerService: TransformerInjectable,
@Inject(UncategorizedBankTransaction.name)
private readonly uncategorizedBankTransactionModel: typeof UncategorizedBankTransaction
) {}
/**
* Retrieves the given bank accounts pending transaction.
* @param {GetPendingTransactionsQuery} filter - Pending transactions query.
*/
async getPendingTransactions(filter?: GetPendingTransactionsQuery) {
const _filter = {
page: 1,
pageSize: 20,
...filter,
};
const { results, pagination } =
await this.uncategorizedBankTransactionModel.query()
.onBuild((q) => {
q.modify('pending');
if (_filter?.accountId) {
q.where('accountId', _filter.accountId);
}
})
.pagination(_filter.page - 1, _filter.pageSize);
const data = await this.transformerService.transform(
results,
new GetPendingBankAccountTransactionTransformer()
);
return { data, pagination };
}
}
interface GetPendingTransactionsQuery {
page?: number;
pageSize?: number;
accountId?: number;
}

View File

@@ -0,0 +1,72 @@
import { Transformer } from '@/modules/Transformer/Transformer';
export class GetPendingBankAccountTransactionTransformer extends Transformer {
/**
* Include these attributes to sale invoice object.
* @returns {string[]}
*/
public includeAttributes = (): string[] => {
return [
'formattedAmount',
'formattedDate',
'formattedDepositAmount',
'formattedWithdrawalAmount',
];
};
/**
* Exclude all attributes.
* @returns {Array<string>}
*/
public excludeAttributes = (): string[] => {
return [];
};
/**
* Formattes the transaction date.
* @param transaction
* @returns {string}
*/
public formattedDate(transaction) {
return this.formatDate(transaction.date);
}
/**
* Formatted amount.
* @param transaction
* @returns {string}
*/
public formattedAmount(transaction) {
return this.formatNumber(transaction.amount, {
currencyCode: transaction.currencyCode,
});
}
/**
* Formatted deposit amount.
* @param transaction
* @returns {string}
*/
protected formattedDepositAmount(transaction) {
if (transaction.isDepositTransaction) {
return this.formatNumber(transaction.deposit, {
currencyCode: transaction.currencyCode,
});
}
return '';
}
/**
* Formatted withdrawal amount.
* @param transaction
* @returns {string}
*/
protected formattedWithdrawalAmount(transaction) {
if (transaction.isWithdrawalTransaction) {
return this.formatNumber(transaction.withdrawal, {
currencyCode: transaction.currencyCode,
});
}
return '';
}
}

View File

@@ -0,0 +1,39 @@
import { Inject, Injectable } from '@nestjs/common';
import { GetRecognizedTransactionTransformer } from './GetRecognizedTransactionTransformer';
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
import { UncategorizedBankTransaction } from '../models/UncategorizedBankTransaction';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class GetRecognizedTransactionService {
constructor(
private readonly transformer: TransformerInjectable,
@Inject(UncategorizedBankTransaction.name)
private readonly uncategorizedBankTransactionModel: TenantModelProxy<
typeof UncategorizedBankTransaction
>,
) {}
/**
* Retrieves the recognized transaction of the given uncategorized transaction.
* @param {number} tenantId
* @param {number} uncategorizedTransactionId
*/
public async getRecognizedTransaction(uncategorizedTransactionId: number) {
const uncategorizedTransaction =
await this.uncategorizedBankTransactionModel()
.query()
.findById(uncategorizedTransactionId)
.withGraphFetched('matchedBankTransactions')
.withGraphFetched('recognizedTransaction.assignAccount')
.withGraphFetched('recognizedTransaction.bankRule')
.withGraphFetched('account')
.throwIfNotFound();
return this.transformer.transform(
uncategorizedTransaction,
new GetRecognizedTransactionTransformer(),
);
}
}

View File

@@ -0,0 +1,261 @@
import { Transformer } from "@/modules/Transformer/Transformer";
export class GetRecognizedTransactionTransformer extends Transformer {
/**
* Include these attributes to sale credit note object.
* @returns {Array}
*/
public includeAttributes = (): string[] => {
return [
'uncategorizedTransactionId',
'referenceNo',
'description',
'payee',
'amount',
'formattedAmount',
'date',
'formattedDate',
'assignedAccountId',
'assignedAccountName',
'assignedAccountCode',
'assignedPayee',
'assignedMemo',
'assignedCategory',
'assignedCategoryFormatted',
'withdrawal',
'deposit',
'isDepositTransaction',
'isWithdrawalTransaction',
'formattedDepositAmount',
'formattedWithdrawalAmount',
'bankRuleId',
'bankRuleName',
];
};
/**
* Exclude all attributes.
* @returns {Array<string>}
*/
public excludeAttributes = (): string[] => {
return ['*'];
};
/**
* Get the uncategorized transaction id.
* @param transaction
* @returns {number}
*/
public uncategorizedTransactionId = (transaction): number => {
return transaction.id;
}
/**
* Get the reference number of the transaction.
* @param {object} transaction
* @returns {string}
*/
public referenceNo(transaction: any): string {
return transaction.referenceNo;
}
/**
* Get the description of the transaction.
* @param {object} transaction
* @returns {string}
*/
public description(transaction: any): string {
return transaction.description;
}
/**
* Get the payee of the transaction.
* @param {object} transaction
* @returns {string}
*/
public payee(transaction: any): string {
return transaction.payee;
}
/**
* Get the amount of the transaction.
* @param {object} transaction
* @returns {number}
*/
public amount(transaction: any): number {
return transaction.amount;
}
/**
* Get the formatted amount of the transaction.
* @param {object} transaction
* @returns {string}
*/
public formattedAmount(transaction: any): string {
return this.formatNumber(transaction.formattedAmount, {
money: true,
});
}
/**
* Get the date of the transaction.
* @param {object} transaction
* @returns {string}
*/
public date(transaction: any): string {
return transaction.date;
}
/**
* Get the formatted date of the transaction.
* @param {object} transaction
* @returns {string}
*/
public formattedDate(transaction: any): string {
return this.formatDate(transaction.date);
}
/**
* Get the assigned account ID of the transaction.
* @param {object} transaction
* @returns {number}
*/
public assignedAccountId(transaction: any): number {
return transaction.recognizedTransaction.assignedAccountId;
}
/**
* Get the assigned account name of the transaction.
* @param {object} transaction
* @returns {string}
*/
public assignedAccountName(transaction: any): string {
return transaction.recognizedTransaction.assignAccount.name;
}
/**
* Get the assigned account code of the transaction.
* @param {object} transaction
* @returns {string}
*/
public assignedAccountCode(transaction: any): string {
return transaction.recognizedTransaction.assignAccount.code;
}
/**
* Get the assigned payee of the transaction.
* @param {object} transaction
* @returns {string}
*/
public getAssignedPayee(transaction: any): string {
return transaction.recognizedTransaction.assignedPayee;
}
/**
* Get the assigned memo of the transaction.
* @param {object} transaction
* @returns {string}
*/
public assignedMemo(transaction: any): string {
return transaction.recognizedTransaction.assignedMemo;
}
/**
* Get the assigned category of the transaction.
* @param {object} transaction
* @returns {string}
*/
public assignedCategory(transaction: any): string {
return transaction.recognizedTransaction.assignedCategory;
}
/**
*
* @returns {string}
*/
public assignedCategoryFormatted() {
return 'Other Income'
}
/**
* Check if the transaction is a withdrawal.
* @param {object} transaction
* @returns {boolean}
*/
public isWithdrawal(transaction: any): boolean {
return transaction.withdrawal;
}
/**
* Check if the transaction is a deposit.
* @param {object} transaction
* @returns {boolean}
*/
public isDeposit(transaction: any): boolean {
return transaction.deposit;
}
/**
* Check if the transaction is a deposit transaction.
* @param {object} transaction
* @returns {boolean}
*/
public isDepositTransaction(transaction: any): boolean {
return transaction.isDepositTransaction;
}
/**
* Check if the transaction is a withdrawal transaction.
* @param {object} transaction
* @returns {boolean}
*/
public isWithdrawalTransaction(transaction: any): boolean {
return transaction.isWithdrawalTransaction;
}
/**
* Get formatted deposit amount.
* @param {any} transaction
* @returns {string}
*/
protected formattedDepositAmount(transaction) {
if (transaction.isDepositTransaction) {
return this.formatNumber(transaction.deposit, {
currencyCode: transaction.currencyCode,
});
}
return '';
}
/**
* Get formatted withdrawal amount.
* @param transaction
* @returns {string}
*/
protected formattedWithdrawalAmount(transaction) {
if (transaction.isWithdrawalTransaction) {
return this.formatNumber(transaction.withdrawal, {
currencyCode: transaction.currencyCode,
});
}
return '';
}
/**
* Get the transaction bank rule id.
* @param transaction
* @returns {string}
*/
protected bankRuleId(transaction) {
return transaction.recognizedTransaction.bankRuleId;
}
/**
* Get the transaction bank rule name.
* @param transaction
* @returns {string}
*/
protected bankRuleName(transaction) {
return transaction.recognizedTransaction.bankRule.name;
}
}

View File

@@ -0,0 +1,67 @@
import { Inject, Injectable } from '@nestjs/common';
import { GetRecognizedTransactionTransformer } from './GetRecognizedTransactionTransformer';
import { UncategorizedBankTransaction } from '../models/UncategorizedBankTransaction';
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
import { IGetRecognizedTransactionsQuery } from '../types/BankingTransactions.types';
@Injectable()
export class GetRecognizedTransactionsService {
constructor(
private readonly transformer: TransformerInjectable,
@Inject(UncategorizedBankTransaction.name)
private readonly uncategorizedBankTransactionModel: typeof UncategorizedBankTransaction,
) {}
/**
* Retrieves the recognized transactions of the given account.
* @param {number} tenantId
* @param {IGetRecognizedTransactionsQuery} filter -
*/
async getRecognizedTranactions(filter?: IGetRecognizedTransactionsQuery) {
const _query = {
page: 1,
pageSize: 20,
...filter,
};
const { results, pagination } =
await this.uncategorizedBankTransactionModel.query()
.onBuild((q) => {
q.withGraphFetched('recognizedTransaction.assignAccount');
q.withGraphFetched('recognizedTransaction.bankRule');
q.whereNotNull('recognizedTransactionId');
// Exclude the excluded transactions.
q.modify('notExcluded');
// Exclude the pending transactions.
q.modify('notPending');
if (_query.accountId) {
q.where('accountId', _query.accountId);
}
if (_query.minDate) {
q.modify('fromDate', _query.minDate);
}
if (_query.maxDate) {
q.modify('toDate', _query.maxDate);
}
if (_query.minAmount) {
q.modify('minAmount', _query.minAmount);
}
if (_query.maxAmount) {
q.modify('maxAmount', _query.maxAmount);
}
if (_query.accountId) {
q.where('accountId', _query.accountId);
}
})
.pagination(_query.page - 1, _query.pageSize);
const data = await this.transformer.transform(
results,
new GetRecognizedTransactionTransformer(),
);
return { data, pagination };
}
}

View File

@@ -0,0 +1,32 @@
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
import { Inject, Injectable } from '@nestjs/common';
import { UncategorizedBankTransaction } from '../models/UncategorizedBankTransaction';
import { UncategorizedTransactionTransformer } from '../../BankingCategorize/commands/UncategorizedTransaction.transformer';
@Injectable()
export class GetUncategorizedBankTransactionService {
constructor(
private readonly transformer: TransformerInjectable,
@Inject(UncategorizedBankTransaction.name)
private readonly uncategorizedBankTransaction: typeof UncategorizedBankTransaction,
) {}
/**
* Retrieves specific uncategorized cashflow transaction.
* @param {number} tenantId - Tenant id.
* @param {number} uncategorizedTransactionId - Uncategorized transaction id.
*/
public async getTransaction(uncategorizedTransactionId: number) {
const transaction = await this.uncategorizedBankTransaction
.query()
.findById(uncategorizedTransactionId)
.withGraphFetched('account')
.throwIfNotFound();
return this.transformer.transform(
transaction,
new UncategorizedTransactionTransformer(),
);
}
}

View File

@@ -0,0 +1,73 @@
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
import { UncategorizedBankTransaction } from '../models/UncategorizedBankTransaction';
import { Inject, Injectable } from '@nestjs/common';
import { UncategorizedTransactionTransformer } from '../../BankingCategorize/commands/UncategorizedTransaction.transformer';
import { IGetUncategorizedTransactionsQuery } from '../types/BankingTransactions.types';
@Injectable()
export class GetUncategorizedTransactions {
constructor(
private readonly transformer: TransformerInjectable,
@Inject(UncategorizedBankTransaction.name)
private readonly uncategorizedBankTransactionModel: typeof UncategorizedBankTransaction,
) {}
/**
* Retrieves the uncategorized cashflow transactions.
* @param {number} tenantId - Tenant id.
* @param {number} accountId - Account Id.
*/
public async getTransactions(
accountId: number,
query: IGetUncategorizedTransactionsQuery
) {
// Parsed query with default values.
const _query = {
page: 1,
pageSize: 20,
...query,
};
const { results, pagination } =
await this.uncategorizedBankTransactionModel.query()
.onBuild((q) => {
q.where('accountId', accountId);
q.where('categorized', false);
q.modify('notExcluded');
q.modify('notPending');
q.withGraphFetched('account');
q.withGraphFetched('recognizedTransaction.assignAccount');
q.withGraphJoined('matchedBankTransactions');
q.whereNull('matchedBankTransactions.id');
q.orderBy('date', 'DESC');
if (_query.minDate) {
q.modify('fromDate', _query.minDate);
}
if (_query.maxDate) {
q.modify('toDate', _query.maxDate);
}
if (_query.minAmount) {
q.modify('minAmount', _query.minAmount);
}
if (_query.maxAmount) {
q.modify('maxAmount', _query.maxAmount);
}
})
.pagination(_query.page - 1, _query.pageSize);
const data = await this.transformer.transform(
results,
new UncategorizedTransactionTransformer()
);
return {
data,
pagination,
};
}
}