mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-15 20:30:33 +00:00
refactor(nestjs): hook up new endpoints
This commit is contained in:
@@ -1,12 +1,25 @@
|
||||
import { Controller, Param, Post } from '@nestjs/common';
|
||||
import { Controller, Get, Param, Post, Query } from '@nestjs/common';
|
||||
import { BankAccountsApplication } from './BankAccountsApplication.service';
|
||||
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
|
||||
import { ICashflowAccountsFilter } from './types/BankAccounts.types';
|
||||
|
||||
@Controller('banking/accounts')
|
||||
@ApiTags('banking-accounts')
|
||||
export class BankAccountsController {
|
||||
constructor(private bankAccountsApplication: BankAccountsApplication) {}
|
||||
|
||||
@Get()
|
||||
@ApiOperation({ summary: 'Retrieve the bank accounts.' })
|
||||
getBankAccounts(@Query() filterDto: ICashflowAccountsFilter) {
|
||||
return this.bankAccountsApplication.getBankAccounts(filterDto);
|
||||
}
|
||||
|
||||
@Get(':bankAccountId/summary')
|
||||
@ApiOperation({ summary: 'Retrieve the bank account summary.' })
|
||||
getBankAccountSummary(@Param('bankAccountId') bankAccountId: number) {
|
||||
return this.bankAccountsApplication.getBankAccountSumnmary(bankAccountId);
|
||||
}
|
||||
|
||||
@Post(':id/disconnect')
|
||||
@ApiOperation({
|
||||
summary: 'Disconnect the bank connection of the given bank account.',
|
||||
|
||||
@@ -12,6 +12,9 @@ import { PlaidModule } from '../Plaid/Plaid.module';
|
||||
import { BankRulesModule } from '../BankRules/BankRules.module';
|
||||
import { BankingTransactionsRegonizeModule } from '../BankingTranasctionsRegonize/BankingTransactionsRegonize.module';
|
||||
import { BankingTransactionsModule } from '../BankingTransactions/BankingTransactions.module';
|
||||
import { GetBankAccountsService } from './queries/GetBankAccounts';
|
||||
import { DynamicListModule } from '../DynamicListing/DynamicList.module';
|
||||
import { GetBankAccountSummary } from './queries/GetBankAccountSummary';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -20,6 +23,7 @@ import { BankingTransactionsModule } from '../BankingTransactions/BankingTransac
|
||||
BankRulesModule,
|
||||
BankingTransactionsRegonizeModule,
|
||||
BankingTransactionsModule,
|
||||
DynamicListModule
|
||||
],
|
||||
providers: [
|
||||
DisconnectBankAccountService,
|
||||
@@ -29,6 +33,8 @@ import { BankingTransactionsModule } from '../BankingTransactions/BankingTransac
|
||||
DeleteUncategorizedTransactionsOnAccountDeleting,
|
||||
DisconnectPlaidItemOnAccountDeleted,
|
||||
BankAccountsApplication,
|
||||
GetBankAccountsService,
|
||||
GetBankAccountSummary
|
||||
],
|
||||
exports: [BankAccountsApplication],
|
||||
controllers: [BankAccountsController],
|
||||
|
||||
@@ -3,16 +3,39 @@ import { DisconnectBankAccountService } from './commands/DisconnectBankAccount.s
|
||||
import { RefreshBankAccountService } from './commands/RefreshBankAccount.service';
|
||||
import { ResumeBankAccountFeedsService } from './commands/ResumeBankAccountFeeds.service';
|
||||
import { PauseBankAccountFeeds } from './commands/PauseBankAccountFeeds.service';
|
||||
import { GetBankAccountsService } from './queries/GetBankAccounts';
|
||||
import { ICashflowAccountsFilter } from './types/BankAccounts.types';
|
||||
import { GetBankAccountSummary } from './queries/GetBankAccountSummary';
|
||||
|
||||
@Injectable()
|
||||
export class BankAccountsApplication {
|
||||
constructor(
|
||||
private disconnectBankAccountService: DisconnectBankAccountService,
|
||||
private readonly getBankAccountsService: GetBankAccountsService,
|
||||
private readonly getBankAccountSummaryService: GetBankAccountSummary,
|
||||
private readonly disconnectBankAccountService: DisconnectBankAccountService,
|
||||
private readonly refreshBankAccountService: RefreshBankAccountService,
|
||||
private readonly resumeBankAccountFeedsService: ResumeBankAccountFeedsService,
|
||||
private readonly pauseBankAccountFeedsService: PauseBankAccountFeeds,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Retrieves the bank accounts.
|
||||
* @param {ICashflowAccountsFilter} filterDto -
|
||||
*/
|
||||
getBankAccounts(filterDto: ICashflowAccountsFilter) {
|
||||
return this.getBankAccountsService.getCashflowAccounts(filterDto);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the given bank account summary.
|
||||
* @param {number} bankAccountId
|
||||
*/
|
||||
getBankAccountSumnmary(bankAccountId: number) {
|
||||
return this.getBankAccountSummaryService.getBankAccountSummary(
|
||||
bankAccountId,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects the given bank account.
|
||||
* @param {number} bankAccountId - Bank account identifier.
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { ACCOUNT_TYPE } from '@/constants/accounts';
|
||||
import { Account } from '@/modules/Accounts/models/Account.model';
|
||||
import { CashflowAccountTransformer } from '@/modules/BankingTransactions/queries/BankAccountTransformer';
|
||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||
import { ICashflowAccountsFilter } from '../types/BankAccounts.types';
|
||||
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
|
||||
import { DynamicListService } from '@/modules/DynamicListing/DynamicList.service';
|
||||
|
||||
@Injectable()
|
||||
export class GetBankAccountsService {
|
||||
constructor(
|
||||
private readonly dynamicListService: DynamicListService,
|
||||
private readonly transformer: TransformerInjectable,
|
||||
|
||||
@Inject(Account.name)
|
||||
private readonly accountModel: TenantModelProxy<typeof Account>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Retrieve the cash flow accounts.
|
||||
* @param {ICashflowAccountsFilter} filterDTO - Filter DTO.
|
||||
* @returns {ICashflowAccount[]}
|
||||
*/
|
||||
public async getCashflowAccounts(filterDTO: ICashflowAccountsFilter) {
|
||||
const _filterDto = {
|
||||
sortOrder: 'desc',
|
||||
columnSortBy: 'created_at',
|
||||
inactiveMode: false,
|
||||
...filterDTO,
|
||||
};
|
||||
// Parsees accounts list filter DTO.
|
||||
const filter = this.dynamicListService.parseStringifiedFilter(_filterDto);
|
||||
|
||||
// Dynamic list service.
|
||||
const dynamicList = await this.dynamicListService.dynamicList(
|
||||
this.accountModel(),
|
||||
filter,
|
||||
);
|
||||
// Retrieve accounts model based on the given query.
|
||||
const accounts = await this.accountModel()
|
||||
.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;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { IDynamicListFilter } from '@/modules/DynamicListing/DynamicFilter/DynamicFilter.types';
|
||||
import { Knex } from 'knex';
|
||||
|
||||
export interface IBankAccountDisconnectingEventPayload {
|
||||
@@ -15,3 +16,11 @@ export const ERRORS = {
|
||||
BANK_ACCOUNT_FEEDS_ALREADY_PAUSED: 'BANK_ACCOUNT_FEEDS_ALREADY_PAUSED',
|
||||
BANK_ACCOUNT_FEEDS_ALREADY_RESUMED: 'BANK_ACCOUNT_FEEDS_ALREADY_RESUMED',
|
||||
};
|
||||
|
||||
|
||||
export interface ICashflowAccountsFilter extends IDynamicListFilter{
|
||||
page: number;
|
||||
pageSize: number;
|
||||
inactiveMode: boolean;
|
||||
viewSlug?: string;
|
||||
}
|
||||
@@ -9,7 +9,10 @@ import {
|
||||
} from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { BankingTransactionsApplication } from './BankingTransactionsApplication.service';
|
||||
import { IBankAccountsFilter } from './types/BankingTransactions.types';
|
||||
import {
|
||||
IBankAccountsFilter,
|
||||
ICashflowAccountTransactionsQuery,
|
||||
} from './types/BankingTransactions.types';
|
||||
import { CreateBankTransactionDto } from './dtos/CreateBankTransaction.dto';
|
||||
|
||||
@Controller('banking/transactions')
|
||||
@@ -19,9 +22,13 @@ export class BankingTransactionsController {
|
||||
private readonly bankingTransactionsApplication: BankingTransactionsApplication,
|
||||
) {}
|
||||
|
||||
@Get('')
|
||||
async getBankAccounts(@Query() filterDTO: IBankAccountsFilter) {
|
||||
return this.bankingTransactionsApplication.getBankAccounts(filterDTO);
|
||||
@Get()
|
||||
async getBankAccountTransactions(
|
||||
@Query() query: ICashflowAccountTransactionsQuery,
|
||||
) {
|
||||
return this.bankingTransactionsApplication.getBankAccountTransactions(
|
||||
query,
|
||||
);
|
||||
}
|
||||
|
||||
@Post()
|
||||
|
||||
@@ -24,6 +24,8 @@ import { GetBankAccountsService } from './queries/GetBankAccounts.service';
|
||||
import { DynamicListModule } from '../DynamicListing/DynamicList.module';
|
||||
import { BankAccount } from './models/BankAccount';
|
||||
import { LedgerModule } from '../Ledger/Ledger.module';
|
||||
import { GetBankAccountTransactionsService } from './queries/GetBankAccountTransactions/GetBankAccountTransactions.service';
|
||||
import { GetBankAccountTransactionsRepository } from './queries/GetBankAccountTransactions/GetBankAccountTransactionsRepo.service';
|
||||
|
||||
const models = [
|
||||
RegisterTenancyModel(UncategorizedBankTransaction),
|
||||
@@ -57,6 +59,8 @@ const models = [
|
||||
CommandBankTransactionValidator,
|
||||
BranchTransactionDTOTransformer,
|
||||
RemovePendingUncategorizedTransaction,
|
||||
GetBankAccountTransactionsRepository,
|
||||
GetBankAccountTransactionsService,
|
||||
],
|
||||
exports: [...models, RemovePendingUncategorizedTransaction],
|
||||
})
|
||||
|
||||
@@ -4,9 +4,11 @@ import { CreateBankTransactionService } from './commands/CreateBankTransaction.s
|
||||
import { GetBankTransactionService } from './queries/GetBankTransaction.service';
|
||||
import {
|
||||
IBankAccountsFilter,
|
||||
ICashflowAccountTransactionsQuery,
|
||||
} from './types/BankingTransactions.types';
|
||||
import { GetBankAccountsService } from './queries/GetBankAccounts.service';
|
||||
import { CreateBankTransactionDto } from './dtos/CreateBankTransaction.dto';
|
||||
import { GetBankAccountTransactionsService } from './queries/GetBankAccountTransactions/GetBankAccountTransactions.service';
|
||||
|
||||
@Injectable()
|
||||
export class BankingTransactionsApplication {
|
||||
@@ -15,6 +17,7 @@ export class BankingTransactionsApplication {
|
||||
private readonly deleteTransactionService: DeleteCashflowTransaction,
|
||||
private readonly getCashflowTransactionService: GetBankTransactionService,
|
||||
private readonly getBankAccountsService: GetBankAccountsService,
|
||||
private readonly getBankAccountTransactionsService: GetBankAccountTransactionsService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -37,6 +40,16 @@ export class BankingTransactionsApplication {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the bank transactions of the given bank id.
|
||||
* @param {ICashflowAccountTransactionsQuery} query
|
||||
*/
|
||||
public getBankAccountTransactions(query: ICashflowAccountTransactionsQuery) {
|
||||
return this.getBankAccountTransactionsService.bankAccountTransactions(
|
||||
query,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves specific cashflow transaction.
|
||||
* @param {number} cashflowTransactionId
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { getBankAccountTransactionsDefaultQuery } from './_utils';
|
||||
import { GetBankAccountTransactionsRepository } from './GetBankAccountTransactionsRepo.service';
|
||||
import { GetBankAccountTransactions } from './GetBankAccountTransactions';
|
||||
import { ICashflowAccountTransactionsQuery } from '../../types/BankingTransactions.types';
|
||||
|
||||
@Injectable()
|
||||
export class GetBankAccountTransactionsService {
|
||||
constructor(
|
||||
private readonly getBankAccountTransactionsRepository: GetBankAccountTransactionsRepository,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Retrieve the cashflow account transactions report data.
|
||||
* @param {ICashflowAccountTransactionsQuery} query -
|
||||
* @return {Promise<IInvetoryItemDetailDOO>}
|
||||
*/
|
||||
public async bankAccountTransactions(
|
||||
query: ICashflowAccountTransactionsQuery,
|
||||
) {
|
||||
const parsedQuery = {
|
||||
...getBankAccountTransactionsDefaultQuery(),
|
||||
...query,
|
||||
};
|
||||
this.getBankAccountTransactionsRepository.setQuery(parsedQuery);
|
||||
|
||||
await this.getBankAccountTransactionsRepository.asyncInit();
|
||||
|
||||
// Retrieve the computed report.
|
||||
const report = new GetBankAccountTransactions(
|
||||
this.getBankAccountTransactionsRepository,
|
||||
parsedQuery,
|
||||
);
|
||||
const transactions = report.reportData();
|
||||
const pagination = this.getBankAccountTransactionsRepository.pagination;
|
||||
|
||||
return { transactions, pagination };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
// @ts-nocheck
|
||||
import R from 'ramda';
|
||||
import moment from 'moment';
|
||||
import { first, isEmpty } from 'lodash';
|
||||
import {
|
||||
ICashflowAccountTransaction,
|
||||
ICashflowAccountTransactionsQuery,
|
||||
} from '../../types/BankingTransactions.types';
|
||||
import { BankTransactionStatus } from './_constants';
|
||||
import { FinancialSheet } from '@/modules/FinancialStatements/common/FinancialSheet';
|
||||
import { formatBankTransactionsStatus } from './_utils';
|
||||
import { GetBankAccountTransactionsRepository } from './GetBankAccountTransactionsRepo.service';
|
||||
import { runningBalance } from '@/utils/running-balance';
|
||||
|
||||
export class GetBankAccountTransactions extends FinancialSheet {
|
||||
private runningBalance: any;
|
||||
private query: ICashflowAccountTransactionsQuery;
|
||||
private repo: GetBankAccountTransactionsRepository;
|
||||
|
||||
/**
|
||||
* Constructor method.
|
||||
* @param {IAccountTransaction[]} transactions -
|
||||
* @param {number} openingBalance -
|
||||
* @param {ICashflowAccountTransactionsQuery} query -
|
||||
*/
|
||||
constructor(
|
||||
repo: GetBankAccountTransactionsRepository,
|
||||
query: ICashflowAccountTransactionsQuery,
|
||||
) {
|
||||
super();
|
||||
|
||||
this.repo = repo;
|
||||
this.query = query;
|
||||
this.runningBalance = runningBalance(this.repo.openingBalance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the transaction status.
|
||||
* @param {} transaction
|
||||
* @returns {BankTransactionStatus}
|
||||
*/
|
||||
private getTransactionStatus(transaction: any): BankTransactionStatus {
|
||||
const categorizedTrans = this.repo.uncategorizedTransactionsMapByRef.get(
|
||||
`${transaction.referenceType}-${transaction.referenceId}`,
|
||||
);
|
||||
const matchedTrans = this.repo.matchedBankTransactionsMapByRef.get(
|
||||
`${transaction.referenceType}-${transaction.referenceId}`,
|
||||
);
|
||||
if (!isEmpty(categorizedTrans)) {
|
||||
return BankTransactionStatus.Categorized;
|
||||
} else if (!isEmpty(matchedTrans)) {
|
||||
return BankTransactionStatus.Matched;
|
||||
} else {
|
||||
return BankTransactionStatus.Manual;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the uncategoized transaction id from the given transaction.
|
||||
* @param transaction
|
||||
* @returns {number|null}
|
||||
*/
|
||||
private getUncategorizedTransId(transaction: any): number {
|
||||
// The given transaction would be categorized, matched or not, so we'd take a look at
|
||||
// the categorized transaction first to get the id if not exist, then should look at the matched
|
||||
// transaction if not exist too, so the given transaction has no uncategorized transaction id.
|
||||
const categorizedTrans = this.repo.uncategorizedTransactionsMapByRef.get(
|
||||
`${transaction.referenceType}-${transaction.referenceId}`,
|
||||
);
|
||||
const matchedTrans = this.repo.matchedBankTransactionsMapByRef.get(
|
||||
`${transaction.referenceType}-${transaction.referenceId}`,
|
||||
);
|
||||
// Relation between the transaction and matching always been one-to-one.
|
||||
const firstCategorizedTrans = first(categorizedTrans);
|
||||
const firstMatchedTrans = first(matchedTrans);
|
||||
|
||||
return (
|
||||
firstCategorizedTrans?.id ||
|
||||
firstMatchedTrans?.uncategorizedTransactionId ||
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*Transformes the account transaction to to cashflow transaction node.
|
||||
* @param {IAccountTransaction} transaction
|
||||
* @returns {ICashflowAccountTransaction}
|
||||
*/
|
||||
private transactionNode = (transaction: any): ICashflowAccountTransaction => {
|
||||
const status = this.getTransactionStatus(transaction);
|
||||
const uncategorizedTransactionId =
|
||||
this.getUncategorizedTransId(transaction);
|
||||
|
||||
return {
|
||||
date: transaction.date,
|
||||
formattedDate: moment(transaction.date).format('YYYY-MM-DD'),
|
||||
|
||||
withdrawal: transaction.credit,
|
||||
deposit: transaction.debit,
|
||||
|
||||
formattedDeposit: this.formatNumber(transaction.debit),
|
||||
formattedWithdrawal: this.formatNumber(transaction.credit),
|
||||
|
||||
referenceId: transaction.referenceId,
|
||||
referenceType: transaction.referenceType,
|
||||
|
||||
formattedTransactionType: transaction.referenceTypeFormatted,
|
||||
|
||||
transactionNumber: transaction.transactionNumber,
|
||||
referenceNumber: transaction.referenceNumber,
|
||||
|
||||
runningBalance: this.runningBalance.amount(),
|
||||
formattedRunningBalance: this.formatNumber(this.runningBalance.amount()),
|
||||
|
||||
balance: 0,
|
||||
formattedBalance: '',
|
||||
status,
|
||||
formattedStatus: formatBankTransactionsStatus(status),
|
||||
uncategorizedTransactionId,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Associate cashflow transaction node with running balance attribute.
|
||||
* @param {IAccountTransaction} transaction
|
||||
* @returns {ICashflowAccountTransaction}
|
||||
*/
|
||||
private transactionRunningBalance = (
|
||||
transaction: ICashflowAccountTransaction,
|
||||
): ICashflowAccountTransaction => {
|
||||
const amount = transaction.deposit - transaction.withdrawal;
|
||||
|
||||
const biggerThanZero = R.lt(0, amount);
|
||||
const lowerThanZero = R.gt(0, amount);
|
||||
|
||||
const absAmount = Math.abs(amount);
|
||||
|
||||
R.when(R.always(biggerThanZero), this.runningBalance.decrement)(absAmount);
|
||||
R.when(R.always(lowerThanZero), this.runningBalance.increment)(absAmount);
|
||||
|
||||
const runningBalance = this.runningBalance.amount();
|
||||
|
||||
return {
|
||||
...transaction,
|
||||
runningBalance,
|
||||
formattedRunningBalance: this.formatNumber(runningBalance),
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Associate to balance attribute to cashflow transaction node.
|
||||
* @param {ICashflowAccountTransaction} transaction
|
||||
* @returns {ICashflowAccountTransaction}
|
||||
*/
|
||||
private transactionBalance = (
|
||||
transaction: ICashflowAccountTransaction,
|
||||
): ICashflowAccountTransaction => {
|
||||
const balance =
|
||||
transaction.runningBalance +
|
||||
transaction.withdrawal * -1 +
|
||||
transaction.deposit;
|
||||
|
||||
return {
|
||||
...transaction,
|
||||
balance,
|
||||
formattedBalance: this.formatNumber(balance),
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Transformes the given account transaction to cashflow report transaction.
|
||||
* @param {ICashflowAccountTransaction} transaction
|
||||
* @returns {ICashflowAccountTransaction}
|
||||
*/
|
||||
private transactionTransformer = (
|
||||
transaction,
|
||||
): ICashflowAccountTransaction => {
|
||||
return R.compose(
|
||||
this.transactionBalance,
|
||||
this.transactionRunningBalance,
|
||||
this.transactionNode,
|
||||
)(transaction);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the report transactions node.
|
||||
* @param {} transactions
|
||||
* @returns {ICashflowAccountTransaction[]}
|
||||
*/
|
||||
private transactionsNode = (transactions): ICashflowAccountTransaction[] => {
|
||||
return R.map(this.transactionTransformer)(transactions);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the reprot data node.
|
||||
* @returns {ICashflowAccountTransaction[]}
|
||||
*/
|
||||
public reportData(): ICashflowAccountTransaction[] {
|
||||
return this.transactionsNode(this.repo.transactions);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
import { Injectable, Scope } from '@nestjs/common';
|
||||
import { ICashflowAccountTransactionsQuery } from '../../types/BankingTransactions.types';
|
||||
import {
|
||||
groupMatchedBankTransactions,
|
||||
groupUncategorizedTransactions,
|
||||
} from './_utils';
|
||||
|
||||
@Injectable({ scope: Scope.REQUEST })
|
||||
export class GetBankAccountTransactionsRepository {
|
||||
private models: any;
|
||||
public query: ICashflowAccountTransactionsQuery;
|
||||
public transactions: any;
|
||||
public uncategorizedTransactions: any;
|
||||
public uncategorizedTransactionsMapByRef: Map<string, any>;
|
||||
public matchedBankTransactions: any;
|
||||
public matchedBankTransactionsMapByRef: Map<string, any>;
|
||||
public pagination: any;
|
||||
public openingBalance: any;
|
||||
|
||||
setQuery(query: ICashflowAccountTransactionsQuery) {
|
||||
this.query = query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Async initalize the resources.
|
||||
*/
|
||||
async asyncInit() {
|
||||
await this.initCashflowAccountTransactions();
|
||||
await this.initCashflowAccountOpeningBalance();
|
||||
await this.initCategorizedTransactions();
|
||||
await this.initMatchedTransactions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the cashflow account transactions.
|
||||
* @param {number} tenantId -
|
||||
* @param {ICashflowAccountTransactionsQuery} query -
|
||||
*/
|
||||
async initCashflowAccountTransactions() {
|
||||
const { AccountTransaction } = this.models;
|
||||
|
||||
const { results, pagination } = await AccountTransaction.query()
|
||||
.where('account_id', this.query.accountId)
|
||||
.orderBy([
|
||||
{ column: 'date', order: 'desc' },
|
||||
{ column: 'created_at', order: 'desc' },
|
||||
])
|
||||
.pagination(this.query.page - 1, this.query.pageSize);
|
||||
|
||||
this.transactions = results;
|
||||
this.pagination = pagination;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the cashflow account opening balance.
|
||||
* @param {number} tenantId
|
||||
* @param {number} accountId
|
||||
* @param {IPaginationMeta} pagination
|
||||
* @return {Promise<number>}
|
||||
*/
|
||||
async initCashflowAccountOpeningBalance(): Promise<void> {
|
||||
const { AccountTransaction } = this.models;
|
||||
|
||||
// Retrieve the opening balance of credit and debit balances.
|
||||
const openingBalancesSubquery = AccountTransaction.query()
|
||||
.where('account_id', this.query.accountId)
|
||||
.orderBy([
|
||||
{ column: 'date', order: 'desc' },
|
||||
{ column: 'created_at', order: 'desc' },
|
||||
])
|
||||
.limit(this.pagination.total)
|
||||
.offset(this.pagination.pageSize * (this.pagination.page - 1));
|
||||
|
||||
// Sumation of credit and debit balance.
|
||||
const openingBalances = await AccountTransaction.query()
|
||||
.sum('credit as credit')
|
||||
.sum('debit as debit')
|
||||
.from(openingBalancesSubquery.as('T'))
|
||||
.first();
|
||||
|
||||
const openingBalance = openingBalances.debit - openingBalances.credit;
|
||||
|
||||
this.openingBalance = openingBalance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the uncategorized transactions of the bank account.
|
||||
*/
|
||||
async initCategorizedTransactions() {
|
||||
const { UncategorizedCashflowTransaction } = this.models;
|
||||
const refs = this.transactions.map((t) => [t.referenceType, t.referenceId]);
|
||||
|
||||
const uncategorizedTransactions =
|
||||
await UncategorizedCashflowTransaction.query().whereIn(
|
||||
['categorizeRefType', 'categorizeRefId'],
|
||||
refs,
|
||||
);
|
||||
|
||||
this.uncategorizedTransactions = uncategorizedTransactions;
|
||||
this.uncategorizedTransactionsMapByRef = groupUncategorizedTransactions(
|
||||
uncategorizedTransactions,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the matched bank transactions of the bank account.
|
||||
*/
|
||||
async initMatchedTransactions(): Promise<void> {
|
||||
const { MatchedBankTransaction } = this.models;
|
||||
const refs = this.transactions.map((t) => [t.referenceType, t.referenceId]);
|
||||
|
||||
const matchedBankTransactions =
|
||||
await MatchedBankTransaction.query().whereIn(
|
||||
['referenceType', 'referenceId'],
|
||||
refs,
|
||||
);
|
||||
this.matchedBankTransactions = matchedBankTransactions;
|
||||
this.matchedBankTransactionsMapByRef = groupMatchedBankTransactions(
|
||||
matchedBankTransactions,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
export const ERRORS = {
|
||||
ACCOUNT_ID_HAS_INVALID_TYPE: 'ACCOUNT_ID_HAS_INVALID_TYPE',
|
||||
};
|
||||
|
||||
export enum BankTransactionStatus {
|
||||
Categorized = 'categorized',
|
||||
Matched = 'matched',
|
||||
Manual = 'manual',
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import * as R from 'ramda';
|
||||
|
||||
export const groupUncategorizedTransactions = (
|
||||
uncategorizedTransactions: any,
|
||||
): Map<string, any> => {
|
||||
return new Map(
|
||||
R.toPairs(
|
||||
R.groupBy(
|
||||
(transaction: any) =>
|
||||
`${transaction.categorizeRefType}-${transaction.categorizeRefId}`,
|
||||
uncategorizedTransactions,
|
||||
),
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
export const groupMatchedBankTransactions = (
|
||||
uncategorizedTransactions: any,
|
||||
): Map<string, any> => {
|
||||
return new Map(
|
||||
R.toPairs(
|
||||
R.groupBy(
|
||||
(transaction: any) =>
|
||||
`${transaction.referenceType}-${transaction.referenceId}`,
|
||||
uncategorizedTransactions,
|
||||
),
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
export const formatBankTransactionsStatus = (status) => {
|
||||
switch (status) {
|
||||
case 'categorized':
|
||||
return 'Categorized';
|
||||
case 'matched':
|
||||
return 'Matched';
|
||||
case 'manual':
|
||||
return 'Manual';
|
||||
}
|
||||
};
|
||||
|
||||
export const getBankAccountTransactionsDefaultQuery = () => {
|
||||
return {
|
||||
pageSize: 50,
|
||||
page: 1,
|
||||
numberFormat: {
|
||||
precision: 2,
|
||||
divideOn1000: false,
|
||||
showZero: false,
|
||||
formatMoney: 'total',
|
||||
negativeFormat: 'mines',
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -2,6 +2,7 @@ import { Knex } from 'knex';
|
||||
import { UncategorizedBankTransaction } from '../models/UncategorizedBankTransaction';
|
||||
import { BankTransaction } from '../models/BankTransaction';
|
||||
import { CreateBankTransactionDto } from '../dtos/CreateBankTransaction.dto';
|
||||
import { INumberFormatQuery } from '@/modules/FinancialStatements/types/Report.types';
|
||||
|
||||
export interface IPendingTransactionRemovingEventPayload {
|
||||
uncategorizedTransactionId: number;
|
||||
@@ -128,3 +129,39 @@ export interface IGetUncategorizedTransactionsQuery {
|
||||
minAmount?: number;
|
||||
maxAmount?: number;
|
||||
}
|
||||
|
||||
export interface ICashflowAccountTransactionsQuery {
|
||||
page: number;
|
||||
pageSize: number;
|
||||
accountId: number;
|
||||
numberFormat: INumberFormatQuery;
|
||||
}
|
||||
|
||||
export interface ICashflowAccountTransaction {
|
||||
withdrawal: number;
|
||||
deposit: number;
|
||||
runningBalance: number;
|
||||
|
||||
formattedWithdrawal: string;
|
||||
formattedDeposit: string;
|
||||
formattedRunningBalance: string;
|
||||
|
||||
transactionNumber: string;
|
||||
referenceNumber: string;
|
||||
|
||||
referenceId: number;
|
||||
referenceType: string;
|
||||
|
||||
formattedTransactionType: string;
|
||||
|
||||
balance: number;
|
||||
formattedBalance: string;
|
||||
|
||||
date: Date;
|
||||
formattedDate: string;
|
||||
|
||||
status: string;
|
||||
formattedStatus: string;
|
||||
|
||||
uncategorizedTransactionId: number;
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Knex } from 'knex';
|
||||
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
|
||||
import { CommandItemCategoryValidatorService } from './CommandItemCategoryValidator.service';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
@@ -7,13 +9,12 @@ import {
|
||||
IItemCategoryOTD,
|
||||
} from '../ItemCategory.interfaces';
|
||||
import { SystemUser } from '@/modules/System/models/SystemUser';
|
||||
import { Knex } from 'knex';
|
||||
import { ItemCategory } from '../models/ItemCategory.model';
|
||||
import { Inject } from '@nestjs/common';
|
||||
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
|
||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||
import { EditItemCategoryDto } from '../dtos/ItemCategory.dto';
|
||||
|
||||
@Injectable()
|
||||
export class EditItemCategoryService {
|
||||
/**
|
||||
* @param {UnitOfWork} uow - Unit of work.
|
||||
|
||||
@@ -17,14 +17,6 @@ class CommandItemCategoryDto {
|
||||
})
|
||||
description?: string;
|
||||
|
||||
@IsNumber()
|
||||
@IsNotEmpty()
|
||||
@ApiProperty({
|
||||
example: 1,
|
||||
description: 'The user ID',
|
||||
})
|
||||
userId: number;
|
||||
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
@ApiProperty({ example: 1, description: 'The cost account ID' })
|
||||
|
||||
15
packages/server/src/utils/running-balance.ts
Normal file
15
packages/server/src/utils/running-balance.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
|
||||
|
||||
export const runningBalance = (amount: number) => {
|
||||
let runningBalance = amount;
|
||||
|
||||
return {
|
||||
decrement: (decrement: number) => {
|
||||
runningBalance -= decrement;
|
||||
},
|
||||
increment: (increment: number) => {
|
||||
runningBalance += increment;
|
||||
},
|
||||
amount: () => runningBalance,
|
||||
};
|
||||
};
|
||||
@@ -6,7 +6,7 @@ import t from './types';
|
||||
|
||||
// Transform the account.
|
||||
const transformAccount = (response) => {
|
||||
return response.data.account;
|
||||
return response.data;
|
||||
};
|
||||
|
||||
const commonInvalidateQueries = (query) => {
|
||||
@@ -58,9 +58,9 @@ export function useAccount(id, props) {
|
||||
export function useAccountsTypes(props) {
|
||||
return useRequestQuery(
|
||||
[t.ACCOUNTS_TYPES],
|
||||
{ method: 'get', url: 'account_types' },
|
||||
{ method: 'get', url: 'accounts/types' },
|
||||
{
|
||||
select: (res) => res.data.account_types,
|
||||
select: (res) => res.data,
|
||||
defaultData: [],
|
||||
...props,
|
||||
},
|
||||
|
||||
@@ -39,7 +39,7 @@ export function useCashflowAccounts(query, props) {
|
||||
[t.CASH_FLOW_ACCOUNTS, query],
|
||||
{ method: 'get', url: 'banking/accounts', params: query },
|
||||
{
|
||||
select: (res) => res.data.cashflow_accounts,
|
||||
select: (res) => res.data,
|
||||
defaultData: [],
|
||||
...props,
|
||||
},
|
||||
|
||||
@@ -124,7 +124,7 @@ export function useCustomer(id, props) {
|
||||
[t.CUSTOMER, id],
|
||||
{ method: 'get', url: `customers/${id}` },
|
||||
{
|
||||
select: (res) => res.data.customer,
|
||||
select: (res) => res.data,
|
||||
defaultData: {},
|
||||
...props,
|
||||
},
|
||||
|
||||
@@ -69,7 +69,7 @@ export function useEstimate(id, props) {
|
||||
[t.SALE_ESTIMATE, id],
|
||||
{ method: 'get', url: `sale-estimates/${id}` },
|
||||
{
|
||||
select: (res) => res.data.estimate,
|
||||
select: (res) => res.data,
|
||||
defaultData: {},
|
||||
...props,
|
||||
},
|
||||
|
||||
@@ -167,7 +167,7 @@ export function useItem(id, props) {
|
||||
url: `items/${id}`,
|
||||
},
|
||||
{
|
||||
select: (response) => response.data.item,
|
||||
select: (response) => response.data,
|
||||
defaultData: {},
|
||||
...props,
|
||||
},
|
||||
@@ -179,10 +179,10 @@ export function useItemAssociatedInvoiceTransactions(id, props) {
|
||||
[t.ITEM_ASSOCIATED_WITH_INVOICES, id],
|
||||
{
|
||||
method: 'get',
|
||||
url: `items/${id}/transactions/invoices`,
|
||||
url: `items/${id}/invoices`,
|
||||
},
|
||||
{
|
||||
select: (res) => res.data.data,
|
||||
select: (res) => res.data,
|
||||
defaultData: [],
|
||||
...props,
|
||||
},
|
||||
@@ -194,10 +194,10 @@ export function useItemAssociatedEstimateTransactions(id, props) {
|
||||
[t.ITEM_ASSOCIATED_WITH_ESTIMATES, id],
|
||||
{
|
||||
method: 'get',
|
||||
url: `items/${id}/transactions/estimates`,
|
||||
url: `items/${id}/estimates`,
|
||||
},
|
||||
{
|
||||
select: (res) => res.data.data,
|
||||
select: (res) => res.data,
|
||||
defaultData: [],
|
||||
...props,
|
||||
},
|
||||
@@ -209,10 +209,10 @@ export function useItemAssociatedReceiptTransactions(id, props) {
|
||||
[t.ITEM_ASSOCIATED_WITH_RECEIPTS, id],
|
||||
{
|
||||
method: 'get',
|
||||
url: `items/${id}/transactions/receipts`,
|
||||
url: `items/${id}/receipts`,
|
||||
},
|
||||
{
|
||||
select: (res) => res.data.data,
|
||||
select: (res) => res.data,
|
||||
defaultData: [],
|
||||
...props,
|
||||
},
|
||||
@@ -223,10 +223,10 @@ export function useItemAssociatedBillTransactions(id, props) {
|
||||
[t.ITEMS_ASSOCIATED_WITH_BILLS, id],
|
||||
{
|
||||
method: 'get',
|
||||
url: `items/${id}/transactions/bills`,
|
||||
url: `items/${id}/bills`,
|
||||
},
|
||||
{
|
||||
select: (res) => res.data.data,
|
||||
select: (res) => res.data,
|
||||
defaultData: [],
|
||||
...props,
|
||||
},
|
||||
@@ -249,11 +249,11 @@ export function useItemWarehouseLocation(id, props) {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} id
|
||||
* @param {*} query
|
||||
* @param {*} props
|
||||
* @returns
|
||||
*
|
||||
* @param {*} id
|
||||
* @param {*} query
|
||||
* @param {*} props
|
||||
* @returns
|
||||
*/
|
||||
export function useItemInventoryCost(query, props) {
|
||||
return useRequestQuery(
|
||||
@@ -268,5 +268,5 @@ export function useItemInventoryCost(query, props) {
|
||||
defaultData: [],
|
||||
...props,
|
||||
},
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ export function useVendor(id, props) {
|
||||
[t.VENDOR, id],
|
||||
{ method: 'get', url: `vendors/${id}` },
|
||||
{
|
||||
select: (res) => res.data.vendor,
|
||||
select: (res) => res.data,
|
||||
defaultData: {},
|
||||
...props,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user