// @ts-nocheck import * as R from 'ramda'; import * as 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'; import { I18nService } from 'nestjs-i18n'; export class GetBankAccountTransactions extends FinancialSheet { private runningBalance: any; private query: ICashflowAccountTransactionsQuery; private repo: GetBankAccountTransactionsRepository; private i18n: I18nService; /** * Constructor method. * @param {IAccountTransaction[]} transactions - * @param {number} openingBalance - * @param {ICashflowAccountTransactionsQuery} query - */ constructor( repo: GetBankAccountTransactionsRepository, query: ICashflowAccountTransactionsQuery, i18n: I18nService, ) { super(); this.repo = repo; this.query = query; this.i18n = i18n; 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: this.i18n.t(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); } }