mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-15 20:30:33 +00:00
feat(nestjs): migrate to NestJS
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
Param,
|
||||
Post,
|
||||
Query,
|
||||
} from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { BankingTransactionsApplication } from './BankingTransactionsApplication.service';
|
||||
import { IBankAccountsFilter } from './types/BankingTransactions.types';
|
||||
import { CreateBankTransactionDto } from './dtos/CreateBankTransaction.dto';
|
||||
|
||||
@Controller('banking/transactions')
|
||||
@ApiTags('banking-transactions')
|
||||
export class BankingTransactionsController {
|
||||
constructor(
|
||||
private readonly bankingTransactionsApplication: BankingTransactionsApplication,
|
||||
) {}
|
||||
|
||||
@Get('')
|
||||
async getBankAccounts(@Query() filterDTO: IBankAccountsFilter) {
|
||||
return this.bankingTransactionsApplication.getBankAccounts(filterDTO);
|
||||
}
|
||||
|
||||
@Post()
|
||||
async createTransaction(@Body() transactionDTO: CreateBankTransactionDto) {
|
||||
return this.bankingTransactionsApplication.createTransaction(
|
||||
transactionDTO,
|
||||
);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
async deleteTransaction(@Param('id') transactionId: string) {
|
||||
return this.bankingTransactionsApplication.deleteTransaction(
|
||||
Number(transactionId),
|
||||
);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
async getTransaction(@Param('id') transactionId: string) {
|
||||
return this.bankingTransactionsApplication.getTransaction(
|
||||
Number(transactionId),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { RegisterTenancyModel } from '../Tenancy/TenancyModels/Tenancy.module';
|
||||
import { UncategorizedBankTransaction } from './models/UncategorizedBankTransaction';
|
||||
import { BankTransactionLine } from './models/BankTransactionLine';
|
||||
import { BankTransaction } from './models/BankTransaction';
|
||||
import { BankTransactionAutoIncrement } from './commands/BankTransactionAutoIncrement.service';
|
||||
import { BankingTransactionGLEntriesSubscriber } from './subscribers/CashflowTransactionSubscriber';
|
||||
import { DecrementUncategorizedTransactionOnCategorizeSubscriber } from './subscribers/DecrementUncategorizedTransactionOnCategorize';
|
||||
import { DeleteCashflowTransactionOnUncategorizeSubscriber } from './subscribers/DeleteCashflowTransactionOnUncategorize';
|
||||
import { PreventDeleteTransactionOnDeleteSubscriber } from './subscribers/PreventDeleteTransactionsOnDelete';
|
||||
import { ValidateDeleteBankAccountTransactions } from './commands/ValidateDeleteBankAccountTransactions.service';
|
||||
import { BankTransactionGLEntriesService } from './commands/BankTransactionGLEntries';
|
||||
import { BankingTransactionsApplication } from './BankingTransactionsApplication.service';
|
||||
import { AutoIncrementOrdersModule } from '../AutoIncrementOrders/AutoIncrementOrders.module';
|
||||
import { DeleteCashflowTransaction } from './commands/DeleteCashflowTransaction.service';
|
||||
import { CreateBankTransactionService } from './commands/CreateBankTransaction.service';
|
||||
import { GetBankTransactionService } from './queries/GetBankTransaction.service';
|
||||
import { CommandBankTransactionValidator } from './commands/CommandCasflowValidator.service';
|
||||
import { BranchTransactionDTOTransformer } from '../Branches/integrations/BranchTransactionDTOTransform';
|
||||
import { BranchesModule } from '../Branches/Branches.module';
|
||||
import { RemovePendingUncategorizedTransaction } from './commands/RemovePendingUncategorizedTransaction.service';
|
||||
import { BankingTransactionsController } from './BankingTransactions.controller';
|
||||
import { GetBankAccountsService } from './queries/GetBankAccounts.service';
|
||||
import { DynamicListModule } from '../DynamicListing/DynamicList.module';
|
||||
import { BankAccount } from './models/BankAccount';
|
||||
import { LedgerModule } from '../Ledger/Ledger.module';
|
||||
|
||||
const models = [
|
||||
RegisterTenancyModel(UncategorizedBankTransaction),
|
||||
RegisterTenancyModel(BankTransaction),
|
||||
RegisterTenancyModel(BankTransactionLine),
|
||||
RegisterTenancyModel(BankAccount),
|
||||
];
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
AutoIncrementOrdersModule,
|
||||
LedgerModule,
|
||||
BranchesModule,
|
||||
DynamicListModule,
|
||||
...models,
|
||||
],
|
||||
controllers: [BankingTransactionsController],
|
||||
providers: [
|
||||
BankTransactionAutoIncrement,
|
||||
BankTransactionGLEntriesService,
|
||||
ValidateDeleteBankAccountTransactions,
|
||||
BankingTransactionGLEntriesSubscriber,
|
||||
DecrementUncategorizedTransactionOnCategorizeSubscriber,
|
||||
DeleteCashflowTransactionOnUncategorizeSubscriber,
|
||||
PreventDeleteTransactionOnDeleteSubscriber,
|
||||
BankingTransactionsApplication,
|
||||
DeleteCashflowTransaction,
|
||||
CreateBankTransactionService,
|
||||
GetBankTransactionService,
|
||||
GetBankAccountsService,
|
||||
CommandBankTransactionValidator,
|
||||
BranchTransactionDTOTransformer,
|
||||
RemovePendingUncategorizedTransaction,
|
||||
],
|
||||
exports: [...models, RemovePendingUncategorizedTransaction],
|
||||
})
|
||||
export class BankingTransactionsModule {}
|
||||
@@ -0,0 +1,58 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { DeleteCashflowTransaction } from './commands/DeleteCashflowTransaction.service';
|
||||
import { CreateBankTransactionService } from './commands/CreateBankTransaction.service';
|
||||
import { GetBankTransactionService } from './queries/GetBankTransaction.service';
|
||||
import {
|
||||
IBankAccountsFilter,
|
||||
} from './types/BankingTransactions.types';
|
||||
import { GetBankAccountsService } from './queries/GetBankAccounts.service';
|
||||
import { CreateBankTransactionDto } from './dtos/CreateBankTransaction.dto';
|
||||
|
||||
@Injectable()
|
||||
export class BankingTransactionsApplication {
|
||||
constructor(
|
||||
private readonly createTransactionService: CreateBankTransactionService,
|
||||
private readonly deleteTransactionService: DeleteCashflowTransaction,
|
||||
private readonly getCashflowTransactionService: GetBankTransactionService,
|
||||
private readonly getBankAccountsService: GetBankAccountsService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Creates a new cashflow transaction.
|
||||
* @param {ICashflowNewCommandDTO} transactionDTO
|
||||
* @returns
|
||||
*/
|
||||
public createTransaction(transactionDTO: CreateBankTransactionDto) {
|
||||
return this.createTransactionService.newCashflowTransaction(transactionDTO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the given cashflow transaction.
|
||||
* @param {number} cashflowTransactionId - Cashflow transaction id.
|
||||
* @returns {Promise<{ oldCashflowTransaction: ICashflowTransaction }>}
|
||||
*/
|
||||
public deleteTransaction(cashflowTransactionId: number) {
|
||||
return this.deleteTransactionService.deleteCashflowTransaction(
|
||||
cashflowTransactionId,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves specific cashflow transaction.
|
||||
* @param {number} cashflowTransactionId
|
||||
* @returns
|
||||
*/
|
||||
public getTransaction(cashflowTransactionId: number) {
|
||||
return this.getCashflowTransactionService.getBankTransaction(
|
||||
cashflowTransactionId,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the cashflow accounts.
|
||||
* @param {IBankAccountsFilter} filterDTO
|
||||
*/
|
||||
public getBankAccounts(filterDTO: IBankAccountsFilter) {
|
||||
return this.getBankAccountsService.getBankAccounts(filterDTO);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { AutoIncrementOrdersService } from '../../AutoIncrementOrders/AutoIncrementOrders.service';
|
||||
|
||||
@Injectable()
|
||||
export class BankTransactionAutoIncrement {
|
||||
constructor(
|
||||
private readonly autoIncrementOrdersService: AutoIncrementOrdersService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Retrieve the next unique invoice number.
|
||||
* @return {string}
|
||||
*/
|
||||
public getNextTransactionNumber = (): Promise<string> => {
|
||||
return this.autoIncrementOrdersService.getNextTransactionNumber('cashflow');
|
||||
};
|
||||
|
||||
/**
|
||||
* Increment the invoice next number.
|
||||
*/
|
||||
public incrementNextTransactionNumber = () => {
|
||||
return this.autoIncrementOrdersService.incrementSettingsNextNumber(
|
||||
'cashflow',
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
import { ILedgerEntry } from '@/modules/Ledger/types/Ledger.types';
|
||||
import { BankTransaction } from '../models/BankTransaction';
|
||||
import { transformCashflowTransactionType } from '../utils';
|
||||
import { Ledger } from '@/modules/Ledger/Ledger';
|
||||
|
||||
export class BankTransactionGL {
|
||||
private bankTransactionModel: BankTransaction;
|
||||
/**
|
||||
* @param {BankTransaction} bankTransactionModel - The bank transaction model.
|
||||
*/
|
||||
constructor(bankTransactionModel: BankTransaction) {
|
||||
this.bankTransactionModel = bankTransactionModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the common entry of cashflow transaction.
|
||||
* @returns {Partial<ILedgerEntry>}
|
||||
*/
|
||||
private get commonEntry() {
|
||||
const { entries, ...transaction } = this.bankTransactionModel;
|
||||
|
||||
return {
|
||||
date: this.bankTransactionModel.date,
|
||||
currencyCode: this.bankTransactionModel.currencyCode,
|
||||
exchangeRate: this.bankTransactionModel.exchangeRate,
|
||||
|
||||
transactionType: 'CashflowTransaction',
|
||||
transactionId: this.bankTransactionModel.id,
|
||||
transactionNumber: this.bankTransactionModel.transactionNumber,
|
||||
transactionSubType: transformCashflowTransactionType(
|
||||
this.bankTransactionModel.transactionType,
|
||||
),
|
||||
referenceNumber: this.bankTransactionModel.referenceNo,
|
||||
|
||||
note: this.bankTransactionModel.description,
|
||||
|
||||
branchId: this.bankTransactionModel.branchId,
|
||||
userId: this.bankTransactionModel.userId,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the cashflow debit GL entry.
|
||||
* @returns {ILedgerEntry}
|
||||
*/
|
||||
private get cashflowDebitGLEntry(): ILedgerEntry {
|
||||
const commonEntry = this.commonEntry;
|
||||
|
||||
return {
|
||||
...commonEntry,
|
||||
accountId: this.bankTransactionModel.cashflowAccountId,
|
||||
credit: this.bankTransactionModel.isCashCredit
|
||||
? this.bankTransactionModel.localAmount
|
||||
: 0,
|
||||
debit: this.bankTransactionModel.isCashDebit
|
||||
? this.bankTransactionModel.localAmount
|
||||
: 0,
|
||||
accountNormal: this.bankTransactionModel?.cashflowAccount?.accountNormal,
|
||||
index: 1,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the cashflow credit GL entry.
|
||||
* @returns {ILedgerEntry}
|
||||
*/
|
||||
private get cashflowCreditGLEntry(): ILedgerEntry {
|
||||
return {
|
||||
...this.commonEntry,
|
||||
credit: this.bankTransactionModel.isCashDebit
|
||||
? this.bankTransactionModel.localAmount
|
||||
: 0,
|
||||
debit: this.bankTransactionModel.isCashCredit
|
||||
? this.bankTransactionModel.localAmount
|
||||
: 0,
|
||||
accountId: this.bankTransactionModel.creditAccountId,
|
||||
accountNormal: this.bankTransactionModel.creditAccount.accountNormal,
|
||||
index: 2,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the cashflow transaction GL entry.
|
||||
* @returns {ILedgerEntry[]}
|
||||
*/
|
||||
private getJournalEntries(): ILedgerEntry[] {
|
||||
const debitEntry = this.cashflowDebitGLEntry;
|
||||
const creditEntry = this.cashflowCreditGLEntry;
|
||||
|
||||
return [debitEntry, creditEntry];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the cashflow GL ledger.
|
||||
* @returns {Ledger}
|
||||
*/
|
||||
public getCashflowLedger() {
|
||||
const entries = this.getJournalEntries();
|
||||
|
||||
return new Ledger(entries);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import { Knex } from 'knex';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { LedgerStorageService } from '@/modules/Ledger/LedgerStorage.service';
|
||||
import { BankTransaction } from '../models/BankTransaction';
|
||||
import { BankTransactionGL } from './BankTransactionGL';
|
||||
|
||||
@Injectable()
|
||||
export class BankTransactionGLEntriesService {
|
||||
constructor(
|
||||
private readonly ledgerStorage: LedgerStorageService,
|
||||
|
||||
@Inject(BankTransaction.name)
|
||||
private readonly bankTransactionModel: typeof BankTransaction,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Write the journal entries of the given cashflow transaction.
|
||||
* @param {number} tenantId
|
||||
* @param {ICashflowTransaction} cashflowTransaction
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
public writeJournalEntries = async (
|
||||
cashflowTransactionId: number,
|
||||
trx?: Knex.Transaction,
|
||||
): Promise<void> => {
|
||||
// Retrieves the cashflow transactions with associated entries.
|
||||
const transaction = await this.bankTransactionModel
|
||||
.query(trx)
|
||||
.findById(cashflowTransactionId)
|
||||
.withGraphFetched('cashflowAccount')
|
||||
.withGraphFetched('creditAccount');
|
||||
|
||||
// Retrieves the cashflow transaction ledger.
|
||||
const ledger = new BankTransactionGL(transaction).getCashflowLedger();
|
||||
|
||||
await this.ledgerStorage.commit(ledger, trx);
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete the journal entries.
|
||||
* @param {number} cashflowTransactionId - Cashflow transaction id.
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
public revertJournalEntries = async (
|
||||
cashflowTransactionId: number,
|
||||
trx?: Knex.Transaction,
|
||||
): Promise<void> => {
|
||||
await this.ledgerStorage.deleteByReference(
|
||||
cashflowTransactionId,
|
||||
'CashflowTransaction',
|
||||
trx,
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
import { includes, camelCase, upperFirst, sumBy } from 'lodash';
|
||||
import { getCashflowTransactionType } from '../utils';
|
||||
import {
|
||||
CASHFLOW_DIRECTION,
|
||||
CASHFLOW_TRANSACTION_TYPE,
|
||||
ERRORS,
|
||||
} from '../constants';
|
||||
import { Account } from '@/modules/Accounts/models/Account.model';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { ServiceError } from '@/modules/Items/ServiceError';
|
||||
import { BankTransaction } from '../models/BankTransaction';
|
||||
import { UncategorizedBankTransaction } from '../models/UncategorizedBankTransaction';
|
||||
|
||||
@Injectable()
|
||||
export class CommandBankTransactionValidator {
|
||||
/**
|
||||
* Validates the lines accounts type should be cash or bank account.
|
||||
* @param {Account} accounts -
|
||||
*/
|
||||
public validateCreditAccountWithCashflowType = (
|
||||
creditAccount: Account,
|
||||
cashflowTransactionType: CASHFLOW_TRANSACTION_TYPE
|
||||
): void => {
|
||||
const transactionTypeMeta = getCashflowTransactionType(
|
||||
cashflowTransactionType
|
||||
);
|
||||
const noneCashflowAccount = !includes(
|
||||
transactionTypeMeta.creditType,
|
||||
creditAccount.accountType
|
||||
);
|
||||
if (noneCashflowAccount) {
|
||||
throw new ServiceError(ERRORS.CREDIT_ACCOUNTS_HAS_INVALID_TYPE);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates the cashflow transaction type.
|
||||
* @param {string} transactionType
|
||||
* @returns {string}
|
||||
*/
|
||||
public validateCashflowTransactionType = (transactionType: string) => {
|
||||
const transformedType = upperFirst(
|
||||
camelCase(transactionType)
|
||||
) as CASHFLOW_TRANSACTION_TYPE;
|
||||
|
||||
// Retrieve the given transaction type meta.
|
||||
const transactionTypeMeta = getCashflowTransactionType(transformedType);
|
||||
|
||||
// Throw service error in case not the found the given transaction type.
|
||||
if (!transactionTypeMeta) {
|
||||
throw new ServiceError(ERRORS.CASHFLOW_TRANSACTION_TYPE_INVALID);
|
||||
}
|
||||
return transformedType;
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate the given transaction should be categorized.
|
||||
* @param {CashflowTransaction} cashflowTransaction
|
||||
*/
|
||||
public validateTransactionShouldCategorized(
|
||||
cashflowTransaction: BankTransaction
|
||||
) {
|
||||
if (!cashflowTransaction.uncategorize) {
|
||||
throw new ServiceError(ERRORS.TRANSACTION_ALREADY_CATEGORIZED);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the given transcation shouldn't be categorized.
|
||||
* @param {CashflowTransaction} cashflowTransaction
|
||||
*/
|
||||
public validateTransactionsShouldNotCategorized(
|
||||
cashflowTransactions: Array<UncategorizedBankTransaction>
|
||||
) {
|
||||
const categorized = cashflowTransactions.filter((t) => t.categorized);
|
||||
|
||||
if (categorized?.length > 0) {
|
||||
throw new ServiceError(ERRORS.TRANSACTION_ALREADY_CATEGORIZED, '', {
|
||||
ids: categorized.map((t) => t.id),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the uncategorize transaction type.
|
||||
* @param {uncategorizeTransaction}
|
||||
* @param {string} transactionType
|
||||
* @throws {ServiceError(ERRORS.UNCATEGORIZED_TRANSACTION_TYPE_INVALID)}
|
||||
*/
|
||||
public validateUncategorizeTransactionType(
|
||||
uncategorizeTransactions: Array<UncategorizedBankTransaction>,
|
||||
transactionType: string
|
||||
) {
|
||||
const amount = sumBy(uncategorizeTransactions, 'amount');
|
||||
const isDepositTransaction = amount > 0;
|
||||
const isWithdrawalTransaction = amount <= 0;
|
||||
|
||||
const type = getCashflowTransactionType(
|
||||
transactionType as CASHFLOW_TRANSACTION_TYPE
|
||||
);
|
||||
if (
|
||||
(type.direction === CASHFLOW_DIRECTION.IN && isDepositTransaction) ||
|
||||
(type.direction === CASHFLOW_DIRECTION.OUT && isWithdrawalTransaction)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
throw new ServiceError(ERRORS.UNCATEGORIZED_TRANSACTION_TYPE_INVALID);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { pick } from 'lodash';
|
||||
import { Knex } from 'knex';
|
||||
import * as R from 'ramda';
|
||||
import * as composeAsync from 'async/compose';
|
||||
import { CASHFLOW_TRANSACTION_TYPE } from '../constants';
|
||||
import { transformCashflowTransactionType } from '../utils';
|
||||
import { CommandBankTransactionValidator } from './CommandCasflowValidator.service';
|
||||
import { BankTransactionAutoIncrement } from './BankTransactionAutoIncrement.service';
|
||||
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { BranchTransactionDTOTransformer } from '@/modules/Branches/integrations/BranchTransactionDTOTransform';
|
||||
import { events } from '@/common/events/events';
|
||||
import { Account } from '@/modules/Accounts/models/Account.model';
|
||||
import { BankTransaction } from '../models/BankTransaction';
|
||||
import {
|
||||
ICashflowNewCommandDTO,
|
||||
ICommandCashflowCreatedPayload,
|
||||
ICommandCashflowCreatingPayload,
|
||||
} from '../types/BankingTransactions.types';
|
||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||
import { CreateBankTransactionDto } from '../dtos/CreateBankTransaction.dto';
|
||||
|
||||
@Injectable()
|
||||
export class CreateBankTransactionService {
|
||||
constructor(
|
||||
private validator: CommandBankTransactionValidator,
|
||||
private uow: UnitOfWork,
|
||||
private eventPublisher: EventEmitter2,
|
||||
private autoIncrement: BankTransactionAutoIncrement,
|
||||
private branchDTOTransform: BranchTransactionDTOTransformer,
|
||||
|
||||
@Inject(BankTransaction.name)
|
||||
private bankTransactionModel: TenantModelProxy<typeof BankTransaction>,
|
||||
|
||||
@Inject(Account.name)
|
||||
private accountModel: TenantModelProxy<typeof Account>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Authorize the cashflow creating transaction.
|
||||
* @param {ICashflowNewCommandDTO} newCashflowTransactionDTO
|
||||
*/
|
||||
public authorize = async (
|
||||
newCashflowTransactionDTO: ICashflowNewCommandDTO,
|
||||
creditAccount: Account,
|
||||
) => {
|
||||
const transactionType = transformCashflowTransactionType(
|
||||
newCashflowTransactionDTO.transactionType,
|
||||
);
|
||||
// Validates the cashflow transaction type.
|
||||
this.validator.validateCashflowTransactionType(transactionType);
|
||||
|
||||
// Retrieve accounts of the cashflow lines object.
|
||||
this.validator.validateCreditAccountWithCashflowType(
|
||||
creditAccount,
|
||||
transactionType as CASHFLOW_TRANSACTION_TYPE,
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Transformes owner contribution DTO to cashflow transaction.
|
||||
* @param {ICashflowNewCommandDTO} newCashflowTransactionDTO - New transaction DTO.
|
||||
* @returns {ICashflowTransactionInput} - Cashflow transaction object.
|
||||
*/
|
||||
private transformCashflowTransactionDTO = async (
|
||||
newCashflowTransactionDTO: CreateBankTransactionDto,
|
||||
cashflowAccount: Account,
|
||||
userId: number,
|
||||
): Promise<BankTransaction> => {
|
||||
const amount = newCashflowTransactionDTO.amount;
|
||||
|
||||
const fromDTO = pick(newCashflowTransactionDTO, [
|
||||
'date',
|
||||
'referenceNo',
|
||||
'description',
|
||||
'transactionType',
|
||||
'exchangeRate',
|
||||
'cashflowAccountId',
|
||||
'creditAccountId',
|
||||
'branchId',
|
||||
'plaidTransactionId',
|
||||
'uncategorizedTransactionId',
|
||||
]);
|
||||
// Retreive the next invoice number.
|
||||
const autoNextNumber = await this.autoIncrement.getNextTransactionNumber();
|
||||
|
||||
// Retrieve the transaction number.
|
||||
const transactionNumber =
|
||||
newCashflowTransactionDTO.transactionNumber || autoNextNumber;
|
||||
|
||||
const initialDTO = {
|
||||
amount,
|
||||
...fromDTO,
|
||||
transactionNumber,
|
||||
currencyCode: cashflowAccount.currencyCode,
|
||||
exchangeRate: fromDTO?.exchangeRate || 1,
|
||||
transactionType: transformCashflowTransactionType(
|
||||
fromDTO.transactionType,
|
||||
),
|
||||
userId,
|
||||
...(newCashflowTransactionDTO.publish
|
||||
? {
|
||||
publishedAt: new Date(),
|
||||
}
|
||||
: {}),
|
||||
};
|
||||
return composeAsync(this.branchDTOTransform.transformDTO<BankTransaction>)(
|
||||
initialDTO,
|
||||
) as BankTransaction;
|
||||
};
|
||||
|
||||
/**
|
||||
* Owner contribution money in.
|
||||
* @param {ICashflowOwnerContributionDTO} ownerContributionDTO
|
||||
* @param {number} userId - User id.
|
||||
* @returns {Promise<ICashflowTransaction>}
|
||||
*/
|
||||
public newCashflowTransaction = async (
|
||||
newTransactionDTO: ICashflowNewCommandDTO,
|
||||
userId?: number,
|
||||
): Promise<BankTransaction> => {
|
||||
// Retrieves the cashflow account or throw not found error.
|
||||
const cashflowAccount = await this.accountModel()
|
||||
.query()
|
||||
.findById(newTransactionDTO.cashflowAccountId)
|
||||
.throwIfNotFound();
|
||||
|
||||
// Retrieves the credit account or throw not found error.
|
||||
const creditAccount = await this.accountModel()
|
||||
.query()
|
||||
.findById(newTransactionDTO.creditAccountId)
|
||||
.throwIfNotFound();
|
||||
|
||||
// Authorize before creating cashflow transaction.
|
||||
await this.authorize(newTransactionDTO, creditAccount);
|
||||
|
||||
// Transformes owner contribution DTO to cashflow transaction.
|
||||
const cashflowTransactionObj = await this.transformCashflowTransactionDTO(
|
||||
newTransactionDTO,
|
||||
cashflowAccount,
|
||||
userId,
|
||||
);
|
||||
// Creates a new cashflow transaction under UOW envirement.
|
||||
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
|
||||
// Triggers `onCashflowTransactionCreate` event.
|
||||
await this.eventPublisher.emitAsync(
|
||||
events.cashflow.onTransactionCreating,
|
||||
{
|
||||
trx,
|
||||
newTransactionDTO,
|
||||
} as ICommandCashflowCreatingPayload,
|
||||
);
|
||||
// Inserts cashflow owner contribution transaction.
|
||||
const cashflowTransaction = await this.bankTransactionModel()
|
||||
.query(trx)
|
||||
.upsertGraph(cashflowTransactionObj);
|
||||
|
||||
// Triggers `onCashflowTransactionCreated` event.
|
||||
await this.eventPublisher.emitAsync(
|
||||
events.cashflow.onTransactionCreated,
|
||||
{
|
||||
newTransactionDTO,
|
||||
cashflowTransaction,
|
||||
trx,
|
||||
} as ICommandCashflowCreatedPayload,
|
||||
);
|
||||
return cashflowTransaction;
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
import { Knex } from 'knex';
|
||||
import { ERRORS } from '../constants';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { BankTransaction } from '../models/BankTransaction';
|
||||
import { BankTransactionLine } from '../models/BankTransactionLine';
|
||||
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
|
||||
import { ServiceError } from '@/modules/Items/ServiceError';
|
||||
import { events } from '@/common/events/events';
|
||||
import {
|
||||
ICommandCashflowDeletedPayload,
|
||||
ICommandCashflowDeletingPayload,
|
||||
} from '../types/BankingTransactions.types';
|
||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||
|
||||
@Injectable()
|
||||
export class DeleteCashflowTransaction {
|
||||
constructor(
|
||||
private readonly uow: UnitOfWork,
|
||||
private readonly eventEmitter: EventEmitter2,
|
||||
|
||||
@Inject(BankTransaction.name)
|
||||
private readonly bankTransaction: TenantModelProxy<typeof BankTransaction>,
|
||||
|
||||
@Inject(BankTransactionLine.name)
|
||||
private readonly bankTransactionLine: TenantModelProxy<
|
||||
typeof BankTransactionLine
|
||||
>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Deletes the cashflow transaction with associated journal entries.
|
||||
* @param {number} tenantId -
|
||||
* @param {number} userId - User id.
|
||||
*/
|
||||
public deleteCashflowTransaction = async (
|
||||
cashflowTransactionId: number,
|
||||
trx?: Knex.Transaction,
|
||||
): Promise<BankTransaction> => {
|
||||
// Retrieve the cashflow transaction.
|
||||
const oldCashflowTransaction = await this.bankTransaction()
|
||||
.query()
|
||||
.findById(cashflowTransactionId);
|
||||
// Throw not found error if the given transaction id not found.
|
||||
this.throwErrorIfTransactionNotFound(oldCashflowTransaction);
|
||||
|
||||
// Starting database transaction.
|
||||
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
|
||||
// Triggers `onCashflowTransactionDelete` event.
|
||||
await this.eventEmitter.emitAsync(events.cashflow.onTransactionDeleting, {
|
||||
trx,
|
||||
oldCashflowTransaction,
|
||||
} as ICommandCashflowDeletingPayload);
|
||||
|
||||
// Delete cashflow transaction associated lines first.
|
||||
await this.bankTransactionLine()
|
||||
.query(trx)
|
||||
.where('cashflow_transaction_id', cashflowTransactionId)
|
||||
.delete();
|
||||
|
||||
// Delete cashflow transaction.
|
||||
await this.bankTransaction()
|
||||
.query(trx)
|
||||
.findById(cashflowTransactionId)
|
||||
.delete();
|
||||
|
||||
// Triggers `onCashflowTransactionDeleted` event.
|
||||
await this.eventEmitter.emitAsync(events.cashflow.onTransactionDeleted, {
|
||||
trx,
|
||||
cashflowTransactionId,
|
||||
oldCashflowTransaction,
|
||||
} as ICommandCashflowDeletedPayload);
|
||||
|
||||
return oldCashflowTransaction;
|
||||
}, trx);
|
||||
};
|
||||
|
||||
/**
|
||||
* Throw not found error if the given transaction id not found.
|
||||
* @param transaction
|
||||
*/
|
||||
private throwErrorIfTransactionNotFound(transaction) {
|
||||
if (!transaction) {
|
||||
throw new ServiceError(ERRORS.CASHFLOW_TRANSACTION_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import { Knex } from 'knex';
|
||||
import { ERRORS } from '../constants';
|
||||
import {
|
||||
IPendingTransactionRemovedEventPayload,
|
||||
IPendingTransactionRemovingEventPayload,
|
||||
} from '../types/BankingTransactions.types';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { UncategorizedBankTransaction } from '../models/UncategorizedBankTransaction';
|
||||
import { ServiceError } from '@/modules/Items/ServiceError';
|
||||
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
|
||||
import { events } from '@/common/events/events';
|
||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||
|
||||
@Injectable()
|
||||
export class RemovePendingUncategorizedTransaction {
|
||||
constructor(
|
||||
private readonly eventPublisher: EventEmitter2,
|
||||
private readonly uow: UnitOfWork,
|
||||
|
||||
@Inject(UncategorizedBankTransaction.name)
|
||||
private readonly uncategorizedBankTransaction: TenantModelProxy<
|
||||
typeof UncategorizedBankTransaction
|
||||
>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* REmoves the pending uncategorized transaction.
|
||||
* @param {number} uncategorizedTransactionId -
|
||||
* @param {Knex.Transaction} trx -
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public async removePendingTransaction(
|
||||
uncategorizedTransactionId: number,
|
||||
trx?: Knex.Transaction,
|
||||
): Promise<void> {
|
||||
const pendingTransaction = await this.uncategorizedBankTransaction()
|
||||
.query(trx)
|
||||
.findById(uncategorizedTransactionId)
|
||||
.throwIfNotFound();
|
||||
|
||||
if (!pendingTransaction.isPending) {
|
||||
throw new ServiceError(ERRORS.TRANSACTION_NOT_PENDING);
|
||||
}
|
||||
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
|
||||
await this.eventPublisher.emitAsync(
|
||||
events.bankTransactions.onPendingRemoving,
|
||||
{
|
||||
uncategorizedTransactionId,
|
||||
pendingTransaction,
|
||||
trx,
|
||||
} as IPendingTransactionRemovingEventPayload,
|
||||
);
|
||||
// Removes the pending uncategorized transaction.
|
||||
await this.uncategorizedBankTransaction()
|
||||
.query(trx)
|
||||
.findById(uncategorizedTransactionId)
|
||||
.delete();
|
||||
|
||||
await this.eventPublisher.emitAsync(
|
||||
events.bankTransactions.onPendingRemoved,
|
||||
{
|
||||
uncategorizedTransactionId,
|
||||
pendingTransaction,
|
||||
trx,
|
||||
} as IPendingTransactionRemovedEventPayload,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { ERRORS } from '../constants';
|
||||
import { ServiceError } from '../../Items/ServiceError';
|
||||
import { BankTransactionLine } from '../models/BankTransactionLine';
|
||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||
|
||||
@Injectable()
|
||||
export class ValidateDeleteBankAccountTransactions {
|
||||
constructor(
|
||||
@Inject(BankTransactionLine.name)
|
||||
private readonly bankTransactionLineModel: TenantModelProxy<
|
||||
typeof BankTransactionLine
|
||||
>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Validate the account has no associated cashflow transactions.
|
||||
* @param {number} accountId
|
||||
*/
|
||||
public validateAccountHasNoCashflowEntries = async (accountId: number) => {
|
||||
const associatedLines = await this.bankTransactionLineModel()
|
||||
.query()
|
||||
.where('creditAccountId', accountId)
|
||||
.orWhere('cashflowAccountId', accountId);
|
||||
|
||||
if (associatedLines.length > 0) {
|
||||
throw new ServiceError(ERRORS.ACCOUNT_HAS_ASSOCIATED_TRANSACTIONS);
|
||||
}
|
||||
};
|
||||
}
|
||||
149
packages/server/src/modules/BankingTransactions/constants.ts
Normal file
149
packages/server/src/modules/BankingTransactions/constants.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
import { ACCOUNT_TYPE } from "@/constants/accounts";
|
||||
|
||||
|
||||
export const ERRORS = {
|
||||
CASHFLOW_TRANSACTION_TYPE_INVALID: 'CASHFLOW_TRANSACTION_TYPE_INVALID',
|
||||
CASHFLOW_ACCOUNTS_HAS_INVALID_TYPE: 'CASHFLOW_ACCOUNTS_HAS_INVALID_TYPE',
|
||||
CASHFLOW_TRANSACTION_NOT_FOUND: 'CASHFLOW_TRANSACTION_NOT_FOUND',
|
||||
CASHFLOW_ACCOUNTS_IDS_NOT_FOUND: 'CASHFLOW_ACCOUNTS_IDS_NOT_FOUND',
|
||||
CREDIT_ACCOUNTS_IDS_NOT_FOUND: 'CREDIT_ACCOUNTS_IDS_NOT_FOUND',
|
||||
CREDIT_ACCOUNTS_HAS_INVALID_TYPE: 'CREDIT_ACCOUNTS_HAS_INVALID_TYPE',
|
||||
ACCOUNT_ID_HAS_INVALID_TYPE: 'ACCOUNT_ID_HAS_INVALID_TYPE',
|
||||
ACCOUNT_HAS_ASSOCIATED_TRANSACTIONS: 'account_has_associated_transactions',
|
||||
TRANSACTION_ALREADY_CATEGORIZED: 'TRANSACTION_ALREADY_CATEGORIZED',
|
||||
TRANSACTION_ALREADY_UNCATEGORIZED: 'TRANSACTION_ALREADY_UNCATEGORIZED',
|
||||
UNCATEGORIZED_TRANSACTION_TYPE_INVALID:
|
||||
'UNCATEGORIZED_TRANSACTION_TYPE_INVALID',
|
||||
CANNOT_DELETE_TRANSACTION_CONVERTED_FROM_UNCATEGORIZED:
|
||||
'CANNOT_DELETE_TRANSACTION_CONVERTED_FROM_UNCATEGORIZED',
|
||||
CANNOT_CATEGORIZE_EXCLUDED_TRANSACTION:
|
||||
'CANNOT_CATEGORIZE_EXCLUDED_TRANSACTION',
|
||||
TRANSACTION_NOT_CATEGORIZED: 'TRANSACTION_NOT_CATEGORIZED',
|
||||
TRANSACTION_NOT_PENDING: 'TRANSACTION_NOT_PENDING',
|
||||
};
|
||||
|
||||
export enum CASHFLOW_DIRECTION {
|
||||
IN = 'In',
|
||||
OUT = 'Out',
|
||||
}
|
||||
|
||||
export enum CASHFLOW_TRANSACTION_TYPE {
|
||||
ONWERS_DRAWING = 'OwnerDrawing',
|
||||
OWNER_CONTRIBUTION = 'OwnerContribution',
|
||||
OTHER_INCOME = 'OtherIncome',
|
||||
TRANSFER_FROM_ACCOUNT = 'TransferFromAccount',
|
||||
TRANSFER_TO_ACCOUNT = 'TransferToAccount',
|
||||
OTHER_EXPENSE = 'OtherExpense',
|
||||
}
|
||||
|
||||
export const CASHFLOW_TRANSACTION_TYPE_META = {
|
||||
[`${CASHFLOW_TRANSACTION_TYPE.ONWERS_DRAWING}`]: {
|
||||
type: 'OwnerDrawing',
|
||||
direction: CASHFLOW_DIRECTION.OUT,
|
||||
creditType: [ACCOUNT_TYPE.EQUITY],
|
||||
},
|
||||
[`${CASHFLOW_TRANSACTION_TYPE.OWNER_CONTRIBUTION}`]: {
|
||||
type: 'OwnerContribution',
|
||||
direction: CASHFLOW_DIRECTION.IN,
|
||||
creditType: [ACCOUNT_TYPE.EQUITY],
|
||||
},
|
||||
[`${CASHFLOW_TRANSACTION_TYPE.OTHER_INCOME}`]: {
|
||||
type: 'OtherIncome',
|
||||
direction: CASHFLOW_DIRECTION.IN,
|
||||
creditType: [ACCOUNT_TYPE.INCOME, ACCOUNT_TYPE.OTHER_INCOME],
|
||||
},
|
||||
[`${CASHFLOW_TRANSACTION_TYPE.TRANSFER_FROM_ACCOUNT}`]: {
|
||||
type: 'TransferFromAccount',
|
||||
direction: CASHFLOW_DIRECTION.IN,
|
||||
creditType: [
|
||||
ACCOUNT_TYPE.CASH,
|
||||
ACCOUNT_TYPE.BANK,
|
||||
ACCOUNT_TYPE.CREDIT_CARD,
|
||||
],
|
||||
},
|
||||
[`${CASHFLOW_TRANSACTION_TYPE.TRANSFER_TO_ACCOUNT}`]: {
|
||||
type: 'TransferToAccount',
|
||||
direction: CASHFLOW_DIRECTION.OUT,
|
||||
creditType: [
|
||||
ACCOUNT_TYPE.CASH,
|
||||
ACCOUNT_TYPE.BANK,
|
||||
ACCOUNT_TYPE.CREDIT_CARD,
|
||||
],
|
||||
},
|
||||
[`${CASHFLOW_TRANSACTION_TYPE.OTHER_EXPENSE}`]: {
|
||||
type: 'OtherExpense',
|
||||
direction: CASHFLOW_DIRECTION.OUT,
|
||||
creditType: [
|
||||
ACCOUNT_TYPE.EXPENSE,
|
||||
ACCOUNT_TYPE.OTHER_EXPENSE,
|
||||
ACCOUNT_TYPE.COST_OF_GOODS_SOLD,
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export interface ICashflowTransactionTypeMeta {
|
||||
type: string;
|
||||
direction: CASHFLOW_DIRECTION;
|
||||
creditType: string[];
|
||||
}
|
||||
|
||||
export const BankTransactionsSampleData = [
|
||||
{
|
||||
Amount: '6,410.19',
|
||||
Date: '2024-03-26',
|
||||
Payee: 'MacGyver and Sons',
|
||||
'Reference No.': 'REF-1',
|
||||
Description: 'Commodi quo labore.',
|
||||
},
|
||||
{
|
||||
Amount: '8,914.17',
|
||||
Date: '2024-01-05',
|
||||
Payee: 'Eichmann - Bergnaum',
|
||||
'Reference No.': 'REF-1',
|
||||
Description: 'Quia enim et.',
|
||||
},
|
||||
{
|
||||
Amount: '6,200.88',
|
||||
Date: '2024-02-17',
|
||||
Payee: 'Luettgen, Mraz and Legros',
|
||||
'Reference No.': 'REF-1',
|
||||
Description: 'Occaecati consequuntur cum impedit illo.',
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
export const CashflowTransactionTypes = {
|
||||
OtherIncome: 'Other income',
|
||||
OtherExpense: 'Other expense',
|
||||
OwnerDrawing: 'Owner drawing',
|
||||
OwnerContribution: 'Owner contribution',
|
||||
TransferToAccount: 'Transfer to account',
|
||||
TransferFromAccount: 'Transfer from account',
|
||||
};
|
||||
|
||||
export const TransactionTypes = {
|
||||
SaleInvoice: 'Sale invoice',
|
||||
SaleReceipt: 'Sale receipt',
|
||||
PaymentReceive: 'Payment received',
|
||||
Bill: 'Bill',
|
||||
BillPayment: 'Payment made',
|
||||
VendorOpeningBalance: 'Vendor opening balance',
|
||||
CustomerOpeningBalance: 'Customer opening balance',
|
||||
InventoryAdjustment: 'Inventory adjustment',
|
||||
ManualJournal: 'Manual journal',
|
||||
Journal: 'Manual journal',
|
||||
Expense: 'Expense',
|
||||
OwnerContribution: 'Owner contribution',
|
||||
TransferToAccount: 'Transfer to account',
|
||||
TransferFromAccount: 'Transfer from account',
|
||||
OtherIncome: 'Other income',
|
||||
OtherExpense: 'Other expense',
|
||||
OwnerDrawing: 'Owner drawing',
|
||||
InvoiceWriteOff: 'Invoice write-off',
|
||||
CreditNote: 'transaction_type.credit_note',
|
||||
VendorCredit: 'transaction_type.vendor_credit',
|
||||
RefundCreditNote: 'transaction_type.refund_credit_note',
|
||||
RefundVendorCredit: 'transaction_type.refund_vendor_credit',
|
||||
LandedCost: 'transaction_type.landed_cost',
|
||||
CashflowTransaction: CashflowTransactionTypes,
|
||||
};
|
||||
@@ -0,0 +1,58 @@
|
||||
import {
|
||||
IsBoolean,
|
||||
IsDate,
|
||||
IsNumber,
|
||||
IsOptional,
|
||||
IsString,
|
||||
} from 'class-validator';
|
||||
|
||||
export class CreateBankTransactionDto {
|
||||
@IsDate()
|
||||
date: Date;
|
||||
|
||||
@IsString()
|
||||
transactionNumber: string;
|
||||
|
||||
@IsString()
|
||||
referenceNo: string;
|
||||
|
||||
@IsString()
|
||||
transactionType: string;
|
||||
|
||||
@IsString()
|
||||
description: string;
|
||||
|
||||
@IsNumber()
|
||||
amount: number;
|
||||
|
||||
@IsNumber()
|
||||
exchangeRate: number;
|
||||
|
||||
@IsString()
|
||||
currencyCode: string;
|
||||
|
||||
@IsNumber()
|
||||
creditAccountId: number;
|
||||
|
||||
@IsNumber()
|
||||
cashflowAccountId: number;
|
||||
|
||||
@IsBoolean()
|
||||
publish: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
branchId?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
plaidTransactionId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
plaidAccountId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
uncategorizedTransactionId?: number;
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
/* eslint-disable global-require */
|
||||
import { Model } from 'objection';
|
||||
import { castArray } from 'lodash';
|
||||
import { AccountTypesUtils } from '@/libs/accounts-utils/AccountTypesUtils';
|
||||
import { PlaidItem } from '@/modules/BankingPlaid/models/PlaidItem';
|
||||
import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
|
||||
|
||||
export class BankAccount extends TenantBaseModel {
|
||||
public name!: string;
|
||||
public slug!: string;
|
||||
public code!: string;
|
||||
public index!: number;
|
||||
public accountType!: string;
|
||||
public predefined!: boolean;
|
||||
public currencyCode!: string;
|
||||
public active!: boolean;
|
||||
public bankBalance!: number;
|
||||
public lastFeedsUpdatedAt!: string | null;
|
||||
public amount!: number;
|
||||
public plaidItemId!: number;
|
||||
|
||||
public plaidItem!: PlaidItem;
|
||||
|
||||
/**
|
||||
* Table name.
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'accounts';
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
static get timestamps() {
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Virtual attributes.
|
||||
*/
|
||||
static get virtualAttributes() {
|
||||
return ['accountTypeLabel'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve account type label.
|
||||
*/
|
||||
get accountTypeLabel(): string {
|
||||
return AccountTypesUtils.getType(this.accountType, 'label');
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows to mark model as resourceable to viewable and filterable.
|
||||
*/
|
||||
static get resourceable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Model modifiers.
|
||||
*/
|
||||
static get modifiers() {
|
||||
return {
|
||||
/**
|
||||
* Inactive/Active mode.
|
||||
*/
|
||||
inactiveMode(query, active = false) {
|
||||
query.where('accounts.active', !active);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const AccountTransaction = require('models/AccountTransaction');
|
||||
|
||||
return {
|
||||
/**
|
||||
* Account model may has many transactions.
|
||||
*/
|
||||
transactions: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: AccountTransaction.default,
|
||||
join: {
|
||||
from: 'accounts.id',
|
||||
to: 'accounts_transactions.accountId',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the given type equals the account type.
|
||||
* @param {string} accountType
|
||||
* @return {boolean}
|
||||
*/
|
||||
isAccountType(accountType) {
|
||||
const types = castArray(accountType);
|
||||
return types.indexOf(this.accountType) !== -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmine whether the given parent type equals the account type.
|
||||
* @param {string} parentType
|
||||
* @return {boolean}
|
||||
*/
|
||||
isParentType(parentType) {
|
||||
return AccountTypesUtils.isParentTypeEqualsKey(
|
||||
this.accountType,
|
||||
parentType
|
||||
);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Model settings.
|
||||
// */
|
||||
// static get meta() {
|
||||
// return CashflowAccountSettings;
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Retrieve the default custom views, roles and columns.
|
||||
// */
|
||||
// static get defaultViews() {
|
||||
// return DEFAULT_VIEWS;
|
||||
// }
|
||||
|
||||
/**
|
||||
* Model search roles.
|
||||
*/
|
||||
static get searchRoles() {
|
||||
return [
|
||||
{ condition: 'or', fieldKey: 'name', comparator: 'contains' },
|
||||
{ condition: 'or', fieldKey: 'code', comparator: 'like' },
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
import { Model } from 'objection';
|
||||
import { BaseModel } from '@/models/Model';
|
||||
import {
|
||||
getCashflowAccountTransactionsTypes,
|
||||
getCashflowTransactionType,
|
||||
} from '../utils';
|
||||
import { CASHFLOW_DIRECTION, CASHFLOW_TRANSACTION_TYPE } from '../constants';
|
||||
import { BankTransactionLine } from './BankTransactionLine';
|
||||
import { Account } from '@/modules/Accounts/models/Account.model';
|
||||
|
||||
export class BankTransaction extends BaseModel {
|
||||
transactionType: string;
|
||||
amount: number;
|
||||
exchangeRate: number;
|
||||
uncategorize: boolean;
|
||||
uncategorizedTransaction!: boolean;
|
||||
currencyCode: string;
|
||||
date: Date;
|
||||
transactionNumber: string;
|
||||
referenceNo: string;
|
||||
description: string;
|
||||
|
||||
cashflowAccountId: number;
|
||||
creditAccountId: number;
|
||||
|
||||
categorizeRefType: string;
|
||||
categorizeRefId: number;
|
||||
uncategorized: boolean;
|
||||
|
||||
branchId: number;
|
||||
userId: number;
|
||||
|
||||
publishedAt: Date;
|
||||
|
||||
entries: BankTransactionLine[];
|
||||
cashflowAccount: Account;
|
||||
creditAccount: Account;
|
||||
|
||||
uncategorizedTransactionId: number;
|
||||
|
||||
/**
|
||||
* Table name.
|
||||
* @returns {string}
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'cashflow_transactions';
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamps columns.
|
||||
* @returns {Array<string>}
|
||||
*/
|
||||
static get timestamps() {
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Virtual attributes.
|
||||
* @returns {Array<string>}
|
||||
*/
|
||||
static get virtualAttributes() {
|
||||
return [
|
||||
'localAmount',
|
||||
'transactionTypeFormatted',
|
||||
'isPublished',
|
||||
'typeMeta',
|
||||
'isCashCredit',
|
||||
'isCashDebit',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the local amount of cashflow transaction.
|
||||
* @returns {number}
|
||||
*/
|
||||
get localAmount() {
|
||||
return this.amount * this.exchangeRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the cashflow transaction is published.
|
||||
* @return {boolean}
|
||||
*/
|
||||
get isPublished() {
|
||||
return !!this.publishedAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transaction type formatted.
|
||||
* @returns {string}
|
||||
*/
|
||||
// get transactionTypeFormatted() {
|
||||
// return getCashflowTransactionFormattedType(this.transactionType);
|
||||
// }
|
||||
|
||||
get typeMeta() {
|
||||
return getCashflowTransactionType(
|
||||
this.transactionType as CASHFLOW_TRANSACTION_TYPE,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the cashflow transaction cash credit type.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get isCashCredit() {
|
||||
return this.typeMeta?.direction === CASHFLOW_DIRECTION.OUT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the cashflow transaction cash debit type.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get isCashDebit() {
|
||||
return this.typeMeta?.direction === CASHFLOW_DIRECTION.IN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the transaction imported from uncategorized transaction.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get isCategroizedTranasction() {
|
||||
return !!this.uncategorizedTransaction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Model modifiers.
|
||||
*/
|
||||
static get modifiers() {
|
||||
return {
|
||||
/**
|
||||
* Filter the published transactions.
|
||||
*/
|
||||
published(query) {
|
||||
query.whereNot('published_at', null);
|
||||
},
|
||||
|
||||
/**
|
||||
* Filter the not categorized transactions.
|
||||
*/
|
||||
notCategorized(query) {
|
||||
query.whereNull('cashflowTransactions.uncategorizedTransactionId');
|
||||
},
|
||||
|
||||
/**
|
||||
* Filter the categorized transactions.
|
||||
*/
|
||||
categorized(query) {
|
||||
query.whereNotNull('cashflowTransactions.uncategorizedTransactionId');
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const { BankTransactionLine } = require('./BankTransactionLine');
|
||||
const {
|
||||
AccountTransaction,
|
||||
} = require('../../Accounts/models/AccountTransaction.model');
|
||||
const { Account } = require('../../Accounts/models/Account.model');
|
||||
const {
|
||||
MatchedBankTransaction,
|
||||
} = require('../../BankingMatching/models/MatchedBankTransaction');
|
||||
|
||||
return {
|
||||
/**
|
||||
* Cashflow transaction entries.
|
||||
*/
|
||||
entries: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: BankTransactionLine,
|
||||
join: {
|
||||
from: 'cashflow_transactions.id',
|
||||
to: 'cashflow_transaction_lines.cashflowTransactionId',
|
||||
},
|
||||
filter: (query) => {
|
||||
query.orderBy('index', 'ASC');
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Cashflow transaction has associated account transactions.
|
||||
*/
|
||||
transactions: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: AccountTransaction,
|
||||
join: {
|
||||
from: 'cashflow_transactions.id',
|
||||
to: 'accounts_transactions.referenceId',
|
||||
},
|
||||
filter(builder) {
|
||||
builder.where('reference_type', 'CashflowTransaction');
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Cashflow transaction may has associated cashflow account.
|
||||
*/
|
||||
cashflowAccount: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Account,
|
||||
join: {
|
||||
from: 'cashflow_transactions.cashflowAccountId',
|
||||
to: 'accounts.id',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Cashflow transcation may has associated to credit account.
|
||||
*/
|
||||
creditAccount: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Account,
|
||||
join: {
|
||||
from: 'cashflow_transactions.creditAccountId',
|
||||
to: 'accounts.id',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Cashflow transaction may belongs to matched bank transaction.
|
||||
*/
|
||||
matchedBankTransaction: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: MatchedBankTransaction,
|
||||
join: {
|
||||
from: 'cashflow_transactions.id',
|
||||
to: 'matched_bank_transactions.referenceId',
|
||||
},
|
||||
filter: (query) => {
|
||||
const referenceTypes = getCashflowAccountTransactionsTypes();
|
||||
query.whereIn('reference_type', referenceTypes);
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/* eslint-disable global-require */
|
||||
import { Model } from 'objection';
|
||||
import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
|
||||
|
||||
export class BankTransactionLine extends TenantBaseModel{
|
||||
/**
|
||||
* Table name.
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'cashflow_transaction_lines';
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
static get timestamps() {
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the model is resourceable.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
static get resourceable(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const { Account } = require('../../Accounts/models/Account.model');
|
||||
|
||||
return {
|
||||
cashflowAccount: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Account,
|
||||
join: {
|
||||
from: 'cashflow_transaction_lines.cashflowAccountId',
|
||||
to: 'accounts.id',
|
||||
},
|
||||
},
|
||||
creditAccount: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Account,
|
||||
join: {
|
||||
from: 'cashflow_transaction_lines.creditAccountId',
|
||||
to: 'accounts.id',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
/* eslint-disable global-require */
|
||||
import * as moment from 'moment';
|
||||
import { Model } from 'objection';
|
||||
import { BaseModel } from '@/models/Model';
|
||||
|
||||
export class UncategorizedBankTransaction extends BaseModel {
|
||||
readonly amount!: number;
|
||||
readonly date!: Date | string;
|
||||
readonly categorized!: boolean;
|
||||
readonly accountId!: number;
|
||||
readonly referenceNo!: string;
|
||||
readonly payee!: string;
|
||||
readonly description!: string;
|
||||
readonly plaidTransactionId!: string;
|
||||
readonly recognizedTransactionId!: number;
|
||||
readonly excludedAt: Date;
|
||||
readonly pending: boolean;
|
||||
readonly categorizeRefId!: number;
|
||||
readonly categorizeRefType!: string;
|
||||
|
||||
/**
|
||||
* Table name.
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'uncategorized_cashflow_transactions';
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
get timestamps() {
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Virtual attributes.
|
||||
*/
|
||||
static get virtualAttributes() {
|
||||
return [
|
||||
'withdrawal',
|
||||
'deposit',
|
||||
'isDepositTransaction',
|
||||
'isWithdrawalTransaction',
|
||||
'isRecognized',
|
||||
'isExcluded',
|
||||
'isPending',
|
||||
];
|
||||
}
|
||||
|
||||
// static get meta() {
|
||||
// return UncategorizedCashflowTransactionMeta;
|
||||
// }
|
||||
|
||||
/**
|
||||
* Retrieves the withdrawal amount.
|
||||
* @returns {number}
|
||||
*/
|
||||
public get withdrawal() {
|
||||
return this.amount < 0 ? Math.abs(this.amount) : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the deposit amount.
|
||||
* @returns {number}
|
||||
*/
|
||||
public get deposit(): number {
|
||||
return this.amount > 0 ? Math.abs(this.amount) : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the transaction is deposit transaction.
|
||||
*/
|
||||
public get isDepositTransaction(): boolean {
|
||||
return 0 < this.deposit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the transaction is withdrawal transaction.
|
||||
*/
|
||||
public get isWithdrawalTransaction(): boolean {
|
||||
return 0 < this.withdrawal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the transaction is recognized.
|
||||
*/
|
||||
public get isRecognized(): boolean {
|
||||
return !!this.recognizedTransactionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the transaction is excluded.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
public get isExcluded(): boolean {
|
||||
return !!this.excludedAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the transaction is pending.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
public get isPending(): boolean {
|
||||
return !!this.pending;
|
||||
}
|
||||
|
||||
/**
|
||||
* Model modifiers.
|
||||
*/
|
||||
static get modifiers() {
|
||||
return {
|
||||
/**
|
||||
* Filters the not excluded transactions.
|
||||
*/
|
||||
notExcluded(query) {
|
||||
query.whereNull('excluded_at');
|
||||
},
|
||||
|
||||
/**
|
||||
* Filters the excluded transactions.
|
||||
*/
|
||||
excluded(query) {
|
||||
query.whereNotNull('excluded_at');
|
||||
},
|
||||
|
||||
/**
|
||||
* Filter out the recognized transactions.
|
||||
* @param query
|
||||
*/
|
||||
recognized(query) {
|
||||
query.whereNotNull('recognizedTransactionId');
|
||||
},
|
||||
|
||||
/**
|
||||
* Filter out the not recognized transactions.
|
||||
* @param query
|
||||
*/
|
||||
notRecognized(query) {
|
||||
query.whereNull('recognizedTransactionId');
|
||||
},
|
||||
|
||||
categorized(query) {
|
||||
query.whereNotNull('categorizeRefType');
|
||||
query.whereNotNull('categorizeRefId');
|
||||
},
|
||||
|
||||
notCategorized(query) {
|
||||
query.whereNull('categorizeRefType');
|
||||
query.whereNull('categorizeRefId');
|
||||
},
|
||||
|
||||
/**
|
||||
* Filters the not pending transactions.
|
||||
*/
|
||||
notPending(query) {
|
||||
query.where('pending', false);
|
||||
},
|
||||
|
||||
/**
|
||||
* Filters the pending transactions.
|
||||
*/
|
||||
pending(query) {
|
||||
query.where('pending', true);
|
||||
},
|
||||
|
||||
minAmount(query, minAmount) {
|
||||
query.where('amount', '>=', minAmount);
|
||||
},
|
||||
|
||||
maxAmount(query, maxAmount) {
|
||||
query.where('amount', '<=', maxAmount);
|
||||
},
|
||||
|
||||
toDate(query, toDate) {
|
||||
const dateFormat = 'YYYY-MM-DD';
|
||||
const _toDate = moment(toDate).endOf('day').format(dateFormat);
|
||||
|
||||
query.where('date', '<=', _toDate);
|
||||
},
|
||||
|
||||
fromDate(query, fromDate) {
|
||||
const dateFormat = 'YYYY-MM-DD';
|
||||
const _fromDate = moment(fromDate).startOf('day').format(dateFormat);
|
||||
|
||||
query.where('date', '>=', _fromDate);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const { Account } = require('../../Accounts/models/Account.model');
|
||||
const {
|
||||
RecognizedBankTransaction,
|
||||
} = require('../../BankingTranasctionsRegonize/models/RecognizedBankTransaction');
|
||||
const {
|
||||
MatchedBankTransaction,
|
||||
} = require('../../BankingMatching/models/MatchedBankTransaction');
|
||||
|
||||
return {
|
||||
/**
|
||||
* Transaction may has associated to account.
|
||||
*/
|
||||
account: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Account,
|
||||
join: {
|
||||
from: 'uncategorized_cashflow_transactions.accountId',
|
||||
to: 'accounts.id',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Transaction may has association to recognized transaction.
|
||||
*/
|
||||
recognizedTransaction: {
|
||||
relation: Model.HasOneRelation,
|
||||
modelClass: RecognizedBankTransaction,
|
||||
join: {
|
||||
from: 'uncategorized_cashflow_transactions.recognizedTransactionId',
|
||||
to: 'recognized_bank_transactions.id',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Uncategorized transaction may has association to matched transaction.
|
||||
*/
|
||||
matchedBankTransactions: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: MatchedBankTransaction,
|
||||
join: {
|
||||
from: 'uncategorized_cashflow_transactions.id',
|
||||
to: 'matched_bank_transactions.uncategorizedTransactionId',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
: '';
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
@@ -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,
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 '';
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { OnEvent } from '@nestjs/event-emitter';
|
||||
import { BankTransactionAutoIncrement } from '../commands/BankTransactionAutoIncrement.service';
|
||||
import { BankTransactionGLEntriesService } from '../commands/BankTransactionGLEntries';
|
||||
import { events } from '@/common/events/events';
|
||||
import { ICommandCashflowCreatedPayload, ICommandCashflowDeletedPayload } from '../types/BankingTransactions.types';
|
||||
|
||||
@Injectable()
|
||||
export class BankingTransactionGLEntriesSubscriber {
|
||||
/**
|
||||
* @param {BankTransactionGLEntriesService} bankTransactionGLEntries - Bank transaction GL entries service.
|
||||
* @param {BankTransactionAutoIncrement} cashflowTransactionAutoIncrement - Cashflow transaction auto increment service.
|
||||
*/
|
||||
constructor(
|
||||
private readonly bankTransactionGLEntries: BankTransactionGLEntriesService,
|
||||
private readonly cashflowTransactionAutoIncrement: BankTransactionAutoIncrement,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Writes the journal entries once the cashflow transaction create.
|
||||
* @param {ICommandCashflowCreatedPayload} payload -
|
||||
*/
|
||||
@OnEvent(events.cashflow.onTransactionCreated)
|
||||
public async writeJournalEntriesOnceTransactionCreated({
|
||||
cashflowTransaction,
|
||||
trx,
|
||||
}: ICommandCashflowCreatedPayload) {
|
||||
// Can't write GL entries if the transaction not published yet.
|
||||
if (!cashflowTransaction.isPublished) return;
|
||||
|
||||
await this.bankTransactionGLEntries.writeJournalEntries(
|
||||
cashflowTransaction.id,
|
||||
trx,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment the cashflow transaction number once the transaction created.
|
||||
* @param {ICommandCashflowCreatedPayload} payload -
|
||||
*/
|
||||
@OnEvent(events.cashflow.onTransactionCreated)
|
||||
public async incrementTransactionNumberOnceTransactionCreated({}: ICommandCashflowCreatedPayload) {
|
||||
this.cashflowTransactionAutoIncrement.incrementNextTransactionNumber();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the GL entries once the cashflow transaction deleted.
|
||||
* @param {ICommandCashflowDeletedPayload} payload -
|
||||
*/
|
||||
@OnEvent(events.cashflow.onTransactionDeleted)
|
||||
public async revertGLEntriesOnceTransactionDeleted({
|
||||
cashflowTransactionId,
|
||||
trx,
|
||||
}: ICommandCashflowDeletedPayload) {
|
||||
await this.bankTransactionGLEntries.revertJournalEntries(
|
||||
cashflowTransactionId,
|
||||
trx,
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { events } from '@/common/events/events';
|
||||
|
||||
import { ValidateDeleteBankAccountTransactions } from '../commands/ValidateDeleteBankAccountTransactions.service';
|
||||
import { OnEvent } from '@nestjs/event-emitter';
|
||||
import { IAccountEventDeletePayload } from '@/interfaces/Account';
|
||||
|
||||
@Injectable()
|
||||
export class CashflowWithAccountSubscriber {
|
||||
constructor(
|
||||
private readonly validateDeleteBankAccount: ValidateDeleteBankAccountTransactions,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Validate chart account has no associated cashflow transactions on delete.
|
||||
* @param {IAccountEventDeletePayload} payload -
|
||||
*/
|
||||
@OnEvent(events.accounts.onDelete)
|
||||
public async validateAccountHasNoCashflowTransactionsOnDelete({
|
||||
oldAccount,
|
||||
}: IAccountEventDeletePayload) {
|
||||
await this.validateDeleteBankAccount.validateAccountHasNoCashflowEntries(
|
||||
oldAccount.id
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import PromisePool from '@supercharge/promise-pool';
|
||||
import {
|
||||
ICashflowTransactionCategorizedPayload,
|
||||
ICashflowTransactionUncategorizedPayload,
|
||||
} from '../types/BankingTransactions.types';
|
||||
import { OnEvent } from '@nestjs/event-emitter';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { events } from '@/common/events/events';
|
||||
import { Account } from '@/modules/Accounts/models/Account.model';
|
||||
import { UncategorizedBankTransaction } from '../models/UncategorizedBankTransaction';
|
||||
|
||||
@Injectable()
|
||||
export class DecrementUncategorizedTransactionOnCategorizeSubscriber {
|
||||
constructor(
|
||||
@Inject(Account.name)
|
||||
private readonly accountModel: typeof Account,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Decrement the uncategoirzed transactions on the account once categorizing.
|
||||
* @param {ICashflowTransactionCategorizedPayload}
|
||||
*/
|
||||
@OnEvent(events.cashflow.onTransactionCategorized)
|
||||
public async decrementUnCategorizedTransactionsOnCategorized({
|
||||
uncategorizedTransactions,
|
||||
trx,
|
||||
}: ICashflowTransactionCategorizedPayload) {
|
||||
await PromisePool.withConcurrency(1)
|
||||
.for(uncategorizedTransactions)
|
||||
.process(
|
||||
async (uncategorizedTransaction: UncategorizedBankTransaction) => {
|
||||
// Cannot continue if the transaction is still pending.
|
||||
if (uncategorizedTransaction.isPending) {
|
||||
return;
|
||||
}
|
||||
await this.accountModel
|
||||
.query(trx)
|
||||
.findById(uncategorizedTransaction.accountId)
|
||||
.decrement('uncategorizedTransactions', 1);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment the uncategorized transaction on the given account on uncategorizing.
|
||||
* @param {IManualJournalDeletingPayload}
|
||||
*/
|
||||
@OnEvent(events.cashflow.onTransactionUncategorized)
|
||||
public async incrementUnCategorizedTransactionsOnUncategorized({
|
||||
uncategorizedTransactions,
|
||||
trx,
|
||||
}: ICashflowTransactionUncategorizedPayload) {
|
||||
await PromisePool.withConcurrency(1)
|
||||
.for(uncategorizedTransactions)
|
||||
.process(
|
||||
async (uncategorizedTransaction: UncategorizedBankTransaction) => {
|
||||
// Cannot continue if the transaction is still pending.
|
||||
if (uncategorizedTransaction.isPending) {
|
||||
return;
|
||||
}
|
||||
await this.accountModel
|
||||
.query(trx)
|
||||
.findById(uncategorizedTransaction.accountId)
|
||||
.increment('uncategorizedTransactions', 1);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments uncategorized transactions count once creating a new transaction.
|
||||
* @param {ICommandCashflowCreatedPayload} payload -
|
||||
*/
|
||||
@OnEvent(events.cashflow.onTransactionUncategorizedCreated)
|
||||
public async incrementUncategoirzedTransactionsOnCreated({
|
||||
uncategorizedTransaction,
|
||||
trx,
|
||||
}: any) {
|
||||
if (!uncategorizedTransaction.accountId) return;
|
||||
|
||||
// Cannot continue if the transaction is still pending.
|
||||
if (uncategorizedTransaction.isPending) return;
|
||||
|
||||
await this.accountModel
|
||||
.query(trx)
|
||||
.findById(uncategorizedTransaction.accountId)
|
||||
.increment('uncategorizedTransactions', 1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { OnEvent } from '@nestjs/event-emitter';
|
||||
import { PromisePool } from '@supercharge/promise-pool';
|
||||
import { DeleteCashflowTransaction } from '../commands/DeleteCashflowTransaction.service';
|
||||
import { events } from '@/common/events/events';
|
||||
import { ICashflowTransactionUncategorizedPayload } from '@/modules/BankingCategorize/types/BankingCategorize.types';
|
||||
|
||||
@Injectable()
|
||||
export class DeleteCashflowTransactionOnUncategorizeSubscriber {
|
||||
constructor(
|
||||
private readonly deleteCashflowTransactionService: DeleteCashflowTransaction,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Deletes the cashflow transaction once uncategorize the bank transaction.
|
||||
* @param {ICashflowTransactionUncategorizedPayload} payload
|
||||
*/
|
||||
@OnEvent(events.cashflow.onTransactionUncategorized)
|
||||
public async deleteCashflowTransactionOnUncategorize({
|
||||
oldMainUncategorizedTransaction,
|
||||
trx,
|
||||
}: ICashflowTransactionUncategorizedPayload) {
|
||||
// Cannot continue if the main transaction does not reference to cashflow type.
|
||||
if (
|
||||
oldMainUncategorizedTransaction.categorizeRefType !==
|
||||
'CashflowTransaction'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
await this.deleteCashflowTransactionService.deleteCashflowTransaction(
|
||||
oldMainUncategorizedTransaction.categorizeRefId,
|
||||
trx
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { events } from '@/common/events/events';
|
||||
import { ERRORS } from '../constants';
|
||||
import { OnEvent } from '@nestjs/event-emitter';
|
||||
import { ServiceError } from '@/modules/Items/ServiceError';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { UncategorizedBankTransaction } from '../models/UncategorizedBankTransaction';
|
||||
import { ICommandCashflowDeletingPayload } from '../types/BankingTransactions.types';
|
||||
|
||||
@Injectable()
|
||||
export class PreventDeleteTransactionOnDeleteSubscriber {
|
||||
constructor(
|
||||
@Inject(UncategorizedBankTransaction.name)
|
||||
private readonly uncategorizedBankTransactionModel: typeof UncategorizedBankTransaction,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Prevent delete cashflow transaction has converted from uncategorized transaction.
|
||||
* @param {ICommandCashflowDeletingPayload} payload
|
||||
*/
|
||||
@OnEvent(events.cashflow.onTransactionDeleting)
|
||||
public async preventDeleteCashflowTransactionHasUncategorizedTransaction({
|
||||
oldCashflowTransaction,
|
||||
trx,
|
||||
}: ICommandCashflowDeletingPayload) {
|
||||
if (oldCashflowTransaction.uncategorizedTransactionId) {
|
||||
const foundTransactions = await this.uncategorizedBankTransactionModel
|
||||
.query(trx)
|
||||
.where({
|
||||
categorized: true,
|
||||
categorizeRefId: oldCashflowTransaction.id,
|
||||
categorizeRefType: 'CashflowTransaction',
|
||||
});
|
||||
// Throw the error if the cashflow transaction still linked to uncategorized transaction.
|
||||
if (foundTransactions.length > 0) {
|
||||
throw new ServiceError(
|
||||
ERRORS.CANNOT_DELETE_TRANSACTION_CONVERTED_FROM_UNCATEGORIZED,
|
||||
'Cannot delete cashflow transaction converted from uncategorized transaction.',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
import { Knex } from 'knex';
|
||||
import { UncategorizedBankTransaction } from '../models/UncategorizedBankTransaction';
|
||||
import { BankTransaction } from '../models/BankTransaction';
|
||||
import { CreateBankTransactionDto } from '../dtos/CreateBankTransaction.dto';
|
||||
|
||||
export interface IPendingTransactionRemovingEventPayload {
|
||||
uncategorizedTransactionId: number;
|
||||
pendingTransaction: UncategorizedBankTransaction;
|
||||
trx?: Knex.Transaction;
|
||||
}
|
||||
|
||||
export interface IPendingTransactionRemovedEventPayload {
|
||||
uncategorizedTransactionId: number;
|
||||
pendingTransaction: UncategorizedBankTransaction;
|
||||
trx?: Knex.Transaction;
|
||||
}
|
||||
|
||||
export interface IGetRecognizedTransactionsQuery {
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
accountId?: number;
|
||||
minDate?: Date;
|
||||
maxDate?: Date;
|
||||
minAmount?: number;
|
||||
maxAmount?: number;
|
||||
}
|
||||
|
||||
export interface ICashflowCommandDTO {
|
||||
date: Date;
|
||||
|
||||
transactionNumber: string;
|
||||
referenceNo: string;
|
||||
transactionType: string;
|
||||
description: string;
|
||||
|
||||
amount: number;
|
||||
exchangeRate: number;
|
||||
currencyCode: string;
|
||||
|
||||
creditAccountId: number;
|
||||
cashflowAccountId: number;
|
||||
|
||||
publish: boolean;
|
||||
branchId?: number;
|
||||
plaidTransactionId?: string;
|
||||
}
|
||||
|
||||
export interface ICashflowNewCommandDTO extends ICashflowCommandDTO {
|
||||
plaidAccountId?: string;
|
||||
uncategorizedTransactionId?: number;
|
||||
}
|
||||
|
||||
export interface IBankAccountsFilter {
|
||||
inactiveMode: boolean;
|
||||
stringifiedFilterRoles?: string;
|
||||
sortOrder: string;
|
||||
columnSortBy: string;
|
||||
}
|
||||
|
||||
export enum CashflowDirection {
|
||||
IN = 'in',
|
||||
OUT = 'out',
|
||||
}
|
||||
|
||||
export interface ICommandCashflowCreatingPayload {
|
||||
trx: Knex.Transaction;
|
||||
newTransactionDTO: ICashflowNewCommandDTO;
|
||||
}
|
||||
|
||||
export interface ICommandCashflowCreatedPayload {
|
||||
newTransactionDTO: CreateBankTransactionDto;
|
||||
cashflowTransaction: BankTransaction;
|
||||
trx: Knex.Transaction;
|
||||
}
|
||||
|
||||
export interface ICommandCashflowDeletingPayload {
|
||||
oldCashflowTransaction: BankTransaction;
|
||||
trx: Knex.Transaction;
|
||||
}
|
||||
|
||||
export interface ICommandCashflowDeletedPayload {
|
||||
cashflowTransactionId: number;
|
||||
oldCashflowTransaction: BankTransaction;
|
||||
trx: Knex.Transaction;
|
||||
}
|
||||
|
||||
export interface ICashflowTransactionCategorizedPayload {
|
||||
uncategorizedTransactions: Array<UncategorizedBankTransaction>;
|
||||
cashflowTransaction: BankTransaction;
|
||||
oldUncategorizedTransactions: Array<UncategorizedBankTransaction>;
|
||||
categorizeDTO: any;
|
||||
trx: Knex.Transaction;
|
||||
}
|
||||
|
||||
export interface ICashflowTransactionUncategorizingPayload {
|
||||
uncategorizedTransactionId: number;
|
||||
oldUncategorizedTransactions: Array<UncategorizedBankTransaction>;
|
||||
trx: Knex.Transaction;
|
||||
}
|
||||
|
||||
export interface ICashflowTransactionUncategorizedPayload {
|
||||
uncategorizedTransactionId: number;
|
||||
uncategorizedTransactions: Array<UncategorizedBankTransaction>;
|
||||
oldMainUncategorizedTransaction: UncategorizedBankTransaction;
|
||||
oldUncategorizedTransactions: Array<UncategorizedBankTransaction>;
|
||||
trx: Knex.Transaction;
|
||||
}
|
||||
|
||||
export enum CashflowAction {
|
||||
Create = 'Create',
|
||||
Delete = 'Delete',
|
||||
View = 'View',
|
||||
}
|
||||
|
||||
export interface CategorizeTransactionAsExpenseDTO {
|
||||
expenseAccountId: number;
|
||||
exchangeRate: number;
|
||||
referenceNo: string;
|
||||
description: string;
|
||||
branchId?: number;
|
||||
}
|
||||
|
||||
export interface IGetUncategorizedTransactionsQuery {
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
minDate?: Date;
|
||||
maxDate?: Date;
|
||||
minAmount?: number;
|
||||
maxAmount?: number;
|
||||
}
|
||||
125
packages/server/src/modules/BankingTransactions/utils.ts
Normal file
125
packages/server/src/modules/BankingTransactions/utils.ts
Normal file
@@ -0,0 +1,125 @@
|
||||
import { upperFirst, camelCase, first, sumBy, isObject } from 'lodash';
|
||||
import {
|
||||
CASHFLOW_TRANSACTION_TYPE,
|
||||
CASHFLOW_TRANSACTION_TYPE_META,
|
||||
CashflowTransactionTypes,
|
||||
ERRORS,
|
||||
ICashflowTransactionTypeMeta,
|
||||
TransactionTypes,
|
||||
} from './constants';
|
||||
import { ICashflowNewCommandDTO } from './types/BankingTransactions.types';
|
||||
import { UncategorizedBankTransaction } from './models/UncategorizedBankTransaction';
|
||||
import { ICategorizeCashflowTransactioDTO } from '../BankingCategorize/types/BankingCategorize.types';
|
||||
import { ServiceError } from '../Items/ServiceError';
|
||||
|
||||
/**
|
||||
* Ensures the given transaction type to transformed to appropriate format.
|
||||
* @param {string} type
|
||||
* @returns {string}
|
||||
*/
|
||||
export const transformCashflowTransactionType = (type) => {
|
||||
return upperFirst(camelCase(type));
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the cashflow transaction type meta.
|
||||
* @param {CASHFLOW_TRANSACTION_TYPE} transactionType
|
||||
* @returns {ICashflowTransactionTypeMeta}
|
||||
*/
|
||||
export function getCashflowTransactionType(
|
||||
transactionType: CASHFLOW_TRANSACTION_TYPE,
|
||||
): ICashflowTransactionTypeMeta {
|
||||
const _transactionType = transformCashflowTransactionType(transactionType);
|
||||
|
||||
return CASHFLOW_TRANSACTION_TYPE_META[_transactionType];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve cashflow accounts transactions types
|
||||
* @returns {string}
|
||||
*/
|
||||
export const getCashflowAccountTransactionsTypes = () => {
|
||||
return Object.values(CASHFLOW_TRANSACTION_TYPE_META).map((meta) => meta.type);
|
||||
};
|
||||
|
||||
/**
|
||||
* Tranasformes the given uncategorized transaction and categorized DTO
|
||||
* to cashflow create DTO.
|
||||
* @param {IUncategorizedCashflowTransaction} uncategorizeModel
|
||||
* @param {ICategorizeCashflowTransactioDTO} categorizeDTO
|
||||
* @returns {ICashflowNewCommandDTO}
|
||||
*/
|
||||
export const transformCategorizeTransToCashflow = (
|
||||
uncategorizeTransactions: Array<UncategorizedBankTransaction>,
|
||||
categorizeDTO: ICategorizeCashflowTransactioDTO,
|
||||
): ICashflowNewCommandDTO => {
|
||||
const uncategorizeTransaction = first(uncategorizeTransactions);
|
||||
const amount = sumBy(uncategorizeTransactions, 'amount');
|
||||
const amountAbs = Math.abs(amount);
|
||||
|
||||
return {
|
||||
date: categorizeDTO.date,
|
||||
referenceNo: categorizeDTO.referenceNo,
|
||||
description: categorizeDTO.description,
|
||||
cashflowAccountId: uncategorizeTransaction.accountId,
|
||||
creditAccountId: categorizeDTO.creditAccountId,
|
||||
exchangeRate: categorizeDTO.exchangeRate || 1,
|
||||
currencyCode: categorizeDTO.currencyCode,
|
||||
amount: amountAbs,
|
||||
transactionNumber: categorizeDTO.transactionNumber,
|
||||
transactionType: categorizeDTO.transactionType,
|
||||
branchId: categorizeDTO?.branchId,
|
||||
publish: true,
|
||||
};
|
||||
};
|
||||
|
||||
export const validateUncategorizedTransactionsNotExcluded = (
|
||||
transactions: Array<UncategorizedBankTransaction>,
|
||||
) => {
|
||||
const excluded = transactions.filter((tran) => tran.isExcluded);
|
||||
|
||||
if (excluded?.length > 0) {
|
||||
throw new ServiceError(ERRORS.CANNOT_CATEGORIZE_EXCLUDED_TRANSACTION, '', {
|
||||
ids: excluded.map((t) => t.id),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const validateTransactionShouldBeCategorized = (
|
||||
uncategorizedTransaction: any,
|
||||
) => {
|
||||
if (!uncategorizedTransaction.categorized) {
|
||||
throw new ServiceError(ERRORS.TRANSACTION_NOT_CATEGORIZED);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the formatted type of account transaction.
|
||||
* @param {string} referenceType
|
||||
* @param {string} transactionType
|
||||
* @returns {string}
|
||||
*/
|
||||
export const getTransactionTypeLabel = (
|
||||
referenceType: string,
|
||||
transactionType?: string,
|
||||
) => {
|
||||
const _referenceType = upperFirst(camelCase(referenceType));
|
||||
const _transactionType = upperFirst(camelCase(transactionType));
|
||||
|
||||
return isObject(TransactionTypes[_referenceType])
|
||||
? TransactionTypes[_referenceType][_transactionType]
|
||||
: TransactionTypes[_referenceType] || null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the formatted type of cashflow transaction.
|
||||
* @param {string} transactionType
|
||||
* @returns {string¿}
|
||||
*/
|
||||
export const getCashflowTransactionFormattedType = (
|
||||
transactionType: string,
|
||||
) => {
|
||||
const _transactionType = upperFirst(camelCase(transactionType));
|
||||
|
||||
return CashflowTransactionTypes[_transactionType] || null;
|
||||
};
|
||||
Reference in New Issue
Block a user