feat: getting matched transactiosn from multi uncategorized transactions

This commit is contained in:
Ahmed Bouhuolia
2024-08-01 12:11:40 +02:00
parent f5e18fc1fe
commit 8623b69991
4 changed files with 32 additions and 21 deletions

View File

@@ -1,6 +1,6 @@
import { Service, Inject } from 'typedi'; import { Service, Inject } from 'typedi';
import { Router, Request, Response, NextFunction } from 'express'; import { Router, Request, Response, NextFunction } from 'express';
import { param } from 'express-validator'; import { param, query } from 'express-validator';
import BaseController from '../BaseController'; import BaseController from '../BaseController';
import { ServiceError } from '@/exceptions'; import { ServiceError } from '@/exceptions';
import CheckPolicies from '@/api/middleware/CheckPolicies'; import CheckPolicies from '@/api/middleware/CheckPolicies';
@@ -24,7 +24,12 @@ export default class GetCashflowAccounts extends BaseController {
const router = Router(); const router = Router();
router.get( router.get(
'/transactions/:transactionId/matches', '/transactions/matches',
[
query('uncategorizeTransactionsIds').exists().isArray({ min: 1 }),
query('uncategorizeTransactionsIds.*').exists().isNumeric().toInt(),
],
this.validationResult,
this.getMatchedTransactions.bind(this) this.getMatchedTransactions.bind(this)
); );
router.get( router.get(
@@ -76,14 +81,15 @@ export default class GetCashflowAccounts extends BaseController {
next: NextFunction next: NextFunction
) { ) {
const { tenantId } = req; const { tenantId } = req;
const { transactionId } = req.params; const uncategorizeTransactionsIds: Array<number> =
req.query.uncategorizeTransactionsIds;
const filter = this.matchedQueryData(req) as GetMatchedTransactionsFilter; const filter = this.matchedQueryData(req) as GetMatchedTransactionsFilter;
try { try {
const data = const data =
await this.bankTransactionsMatchingApp.getMatchedTransactions( await this.bankTransactionsMatchingApp.getMatchedTransactions(
tenantId, tenantId,
transactionId, uncategorizeTransactionsIds,
filter filter
); );
return res.status(200).send(data); return res.status(200).send(data);

View File

@@ -1,6 +1,7 @@
import { Inject, Service } from 'typedi'; import { Inject, Service } from 'typedi';
import * as R from 'ramda'; import * as R from 'ramda';
import moment from 'moment'; import moment from 'moment';
import { first, sumBy } from 'lodash';
import { PromisePool } from '@supercharge/promise-pool'; import { PromisePool } from '@supercharge/promise-pool';
import { GetMatchedTransactionsFilter, MatchedTransactionsPOJO } from './types'; import { GetMatchedTransactionsFilter, MatchedTransactionsPOJO } from './types';
import { GetMatchedTransactionsByExpenses } from './GetMatchedTransactionsByExpenses'; import { GetMatchedTransactionsByExpenses } from './GetMatchedTransactionsByExpenses';
@@ -47,19 +48,20 @@ export class GetMatchedTransactions {
/** /**
* Retrieves the matched transactions. * Retrieves the matched transactions.
* @param {number} tenantId - * @param {number} tenantId -
* @param {Array<number>} uncategorizedTransactionIds - Uncategorized transactions ids.
* @param {GetMatchedTransactionsFilter} filter - * @param {GetMatchedTransactionsFilter} filter -
* @returns {Promise<MatchedTransactionsPOJO>} * @returns {Promise<MatchedTransactionsPOJO>}
*/ */
public async getMatchedTransactions( public async getMatchedTransactions(
tenantId: number, tenantId: number,
uncategorizedTransactionId: number, uncategorizedTransactionIds: Array<number>,
filter: GetMatchedTransactionsFilter filter: GetMatchedTransactionsFilter
): Promise<MatchedTransactionsPOJO> { ): Promise<MatchedTransactionsPOJO> {
const { UncategorizedCashflowTransaction } = this.tenancy.models(tenantId); const { UncategorizedCashflowTransaction } = this.tenancy.models(tenantId);
const uncategorizedTransaction = const uncategorizedTransactions =
await UncategorizedCashflowTransaction.query() await UncategorizedCashflowTransaction.query()
.findById(uncategorizedTransactionId) .whereIn('id', uncategorizedTransactionIds)
.throwIfNotFound(); .throwIfNotFound();
const filtered = filter.transactionType const filtered = filter.transactionType
@@ -71,9 +73,8 @@ export class GetMatchedTransactions {
.process(async ({ type, service }) => { .process(async ({ type, service }) => {
return service.getMatchedTransactions(tenantId, filter); return service.getMatchedTransactions(tenantId, filter);
}); });
const { perfectMatches, possibleMatches } = this.groupMatchedResults( const { perfectMatches, possibleMatches } = this.groupMatchedResults(
uncategorizedTransaction, uncategorizedTransactions,
matchedTransactions matchedTransactions
); );
return { return {
@@ -90,20 +91,20 @@ export class GetMatchedTransactions {
* @returns {MatchedTransactionsPOJO} * @returns {MatchedTransactionsPOJO}
*/ */
private groupMatchedResults( private groupMatchedResults(
uncategorizedTransaction, uncategorizedTransactions: Array<any>,
matchedTransactions matchedTransactions
): MatchedTransactionsPOJO { ): MatchedTransactionsPOJO {
const results = R.compose(R.flatten)(matchedTransactions?.results); const results = R.compose(R.flatten)(matchedTransactions?.results);
const firstUncategorized = first(uncategorizedTransactions);
const amount = sumBy(uncategorizedTransactions, 'amount');
const date = firstUncategorized.date;
// Sort the results based on amount, date, and transaction type // Sort the results based on amount, date, and transaction type
const closestResullts = sortClosestMatchTransactions( const closestResullts = sortClosestMatchTransactions(amount, date, results);
uncategorizedTransaction,
results
);
const perfectMatches = R.filter( const perfectMatches = R.filter(
(match) => (match) =>
match.amount === uncategorizedTransaction.amount && match.amount === amount && moment(match.date).isSame(date, 'day'),
moment(match.date).isSame(uncategorizedTransaction.date, 'day'),
closestResullts closestResullts
); );
const possibleMatches = R.difference(closestResullts, perfectMatches); const possibleMatches = R.difference(closestResullts, perfectMatches);

View File

@@ -23,12 +23,12 @@ export class MatchBankTransactionsApplication {
*/ */
public getMatchedTransactions( public getMatchedTransactions(
tenantId: number, tenantId: number,
uncategorizedTransactionId: number, uncategorizedTransactionsIds: Array<number>,
filter: GetMatchedTransactionsFilter filter: GetMatchedTransactionsFilter
) { ) {
return this.getMatchedTransactionsService.getMatchedTransactions( return this.getMatchedTransactionsService.getMatchedTransactions(
tenantId, tenantId,
uncategorizedTransactionId, uncategorizedTransactionsIds,
filter filter
); );
} }

View File

@@ -16,6 +16,7 @@ import { MatchTransactionsTypes } from './MatchTransactionsTypes';
import { ServiceError } from '@/exceptions'; import { ServiceError } from '@/exceptions';
import { import {
sumMatchTranasctions, sumMatchTranasctions,
sumUncategorizedTransactions,
validateUncategorizedTransactionsExcluded, validateUncategorizedTransactionsExcluded,
validateUncategorizedTransactionsNotMatched, validateUncategorizedTransactionsNotMatched,
} from './_utils'; } from './_utils';
@@ -95,11 +96,14 @@ export class MatchBankTransactions {
const totalMatchedTranasctions = sumMatchTranasctions( const totalMatchedTranasctions = sumMatchTranasctions(
validatationResult.results validatationResult.results
); );
const totalUncategorizedTransactions = sumUncategorizedTransactions(
uncategorizedTransactions
);
// Validates the total given matching transcations whether is not equal // Validates the total given matching transcations whether is not equal
// uncategorized transaction amount. // uncategorized transaction amount.
// if (totalMatchedTranasctions !== uncategorizedTransaction.amount) { if (totalUncategorizedTransactions === totalMatchedTranasctions) {
// throw new ServiceError(ERRORS.TOTAL_MATCHING_TRANSACTIONS_INVALID); throw new ServiceError(ERRORS.TOTAL_MATCHING_TRANSACTIONS_INVALID);
// } }
} }
/** /**