diff --git a/packages/server/src/api/controllers/Cashflow/NewCashflowTransaction.ts b/packages/server/src/api/controllers/Cashflow/NewCashflowTransaction.ts index fea9b84c9..0ddf5861a 100644 --- a/packages/server/src/api/controllers/Cashflow/NewCashflowTransaction.ts +++ b/packages/server/src/api/controllers/Cashflow/NewCashflowTransaction.ts @@ -1,5 +1,5 @@ import { Service, Inject } from 'typedi'; -import { ValidationChain, check, param, query } from 'express-validator'; +import { ValidationChain, body, check, param, query } from 'express-validator'; import { Router, Request, Response, NextFunction } from 'express'; import { omit } from 'lodash'; import BaseController from '../BaseController'; @@ -43,6 +43,16 @@ export default class NewCashflowTransactionController extends BaseController { this.asyncMiddleware(this.newCashflowTransaction), this.catchServiceErrors ); + router.post( + '/transactions/uncategorize/bulk', + [ + body('ids').isArray({ min: 1 }), + body('ids.*').exists().isNumeric().toInt(), + ], + this.validationResult, + this.uncategorizeBulkTransactions.bind(this), + this.catchServiceErrors + ); router.post( '/transactions/:id/uncategorize', this.revertCategorizedCashflowTransaction, @@ -184,6 +194,34 @@ export default class NewCashflowTransactionController extends BaseController { } }; + /** + * Uncategorize the given transactions in bulk. + * @param {Request<{}>} req + * @param {Response} res + * @param {NextFunction} next + * @returns {Promise} + */ + private uncategorizeBulkTransactions = async ( + req: Request<{}>, + res: Response, + next: NextFunction + ) => { + const { tenantId } = req; + const { ids: uncategorizedTransactionIds } = this.matchedBodyData(req); + + try { + await this.cashflowApplication.uncategorizeTransactions( + tenantId, + uncategorizedTransactionIds + ); + return res.status(200).send({ + message: 'The given transactions have been uncategorized successfully.', + }); + } catch (error) { + next(error); + } + }; + /** * Categorize the cashflow transaction. * @param {Request} req diff --git a/packages/server/src/services/Cashflow/CashflowApplication.ts b/packages/server/src/services/Cashflow/CashflowApplication.ts index f54935db7..5773c1ee1 100644 --- a/packages/server/src/services/Cashflow/CashflowApplication.ts +++ b/packages/server/src/services/Cashflow/CashflowApplication.ts @@ -21,6 +21,7 @@ import GetCashflowAccountsService from './GetCashflowAccountsService'; import { GetCashflowTransactionService } from './GetCashflowTransactionsService'; import { GetRecognizedTransactionsService } from './GetRecongizedTransactions'; import { GetRecognizedTransactionService } from './GetRecognizedTransaction'; +import { UncategorizeCashflowTransactionsBulk } from './UncategorizeCashflowTransactionsBulk'; @Service() export class CashflowApplication { @@ -39,6 +40,9 @@ export class CashflowApplication { @Inject() private uncategorizeTransactionService: UncategorizeCashflowTransaction; + @Inject() + private uncategorizeTransasctionsService: UncategorizeCashflowTransactionsBulk; + @Inject() private categorizeTransactionService: CategorizeCashflowTransaction; @@ -155,6 +159,22 @@ export class CashflowApplication { ); } + /** + * Uncategorize the given transactions in bulk. + * @param {number} tenantId + * @param {number | Array} transactionId + * @returns + */ + public uncategorizeTransactions( + tenantId: number, + transactionId: number | Array + ) { + return this.uncategorizeTransasctionsService.uncategorizeBulk( + tenantId, + transactionId + ); + } + /** * Categorize the given cashflow transaction. * @param {number} tenantId @@ -241,9 +261,9 @@ export class CashflowApplication { /** * Retrieves the recognized transaction of the given uncategorized transaction. - * @param {number} tenantId - * @param {number} uncategorizedTransactionId - * @returns + * @param {number} tenantId + * @param {number} uncategorizedTransactionId + * @returns */ public getRecognizedTransaction( tenantId: number, diff --git a/packages/server/src/services/Cashflow/UncategorizeCashflowTransactionsBulk.ts b/packages/server/src/services/Cashflow/UncategorizeCashflowTransactionsBulk.ts new file mode 100644 index 000000000..e5f9ceee2 --- /dev/null +++ b/packages/server/src/services/Cashflow/UncategorizeCashflowTransactionsBulk.ts @@ -0,0 +1,37 @@ +import PromisePool from '@supercharge/promise-pool'; +import { castArray } from 'lodash'; +import { Service, Inject } from 'typedi'; +import HasTenancyService from '../Tenancy/TenancyService'; +import { UncategorizeCashflowTransaction } from './UncategorizeCashflowTransaction'; + +@Service() +export class UncategorizeCashflowTransactionsBulk { + @Inject() + private tenancy: HasTenancyService; + + @Inject() + private uncategorizeTransaction: UncategorizeCashflowTransaction; + + /** + * Uncategorize the given bank transactions in bulk. + * @param {number} tenantId + * @param {number} uncategorizedTransactionId + */ + public async uncategorizeBulk( + tenantId: number, + uncategorizedTransactionId: number | Array + ) { + const uncategorizedTransactionIds = castArray(uncategorizedTransactionId); + + const result = await PromisePool.withConcurrency(MIGRATION_CONCURRENCY) + .for(uncategorizedTransactionIds) + .process(async (_uncategorizedTransactionId: number, index, pool) => { + await this.uncategorizeTransaction.uncategorize( + tenantId, + _uncategorizedTransactionId + ); + }); + } +} + +const MIGRATION_CONCURRENCY = 1; diff --git a/packages/webapp/src/containers/CashFlow/AccountTransactions/AccountTransactionsActionsBar.tsx b/packages/webapp/src/containers/CashFlow/AccountTransactions/AccountTransactionsActionsBar.tsx index cf267da57..9b605a672 100644 --- a/packages/webapp/src/containers/CashFlow/AccountTransactions/AccountTransactionsActionsBar.tsx +++ b/packages/webapp/src/containers/CashFlow/AccountTransactions/AccountTransactionsActionsBar.tsx @@ -64,6 +64,7 @@ function AccountTransactionsActionsBar({ uncategorizedTransationsIdsSelected, excludedTransactionsIdsSelected, openMatchingTransactionAside, + categorizedTransactionsSelected, // #withBankingActions enableMultipleCategorization, @@ -194,7 +195,7 @@ function AccountTransactionsActionsBar({ // Handle multi select transactions for categorization or matching. const handleMultipleCategorizingSwitch = (event) => { enableMultipleCategorization(event.currentTarget.checked); - } + }; // Handle resume bank feeds syncing. const handleResumeFeedsSyncing = () => { openAlert('resume-feeds-syncing-bank-accounnt', { @@ -208,6 +209,13 @@ function AccountTransactionsActionsBar({ }); }; + // Handles uncategorize the categorized transactions in bulk. + const handleUncategorizeCategorizedBulkBtnClick = () => { + openAlert('uncategorize-transactions-bulk', { + uncategorizeTransactionsIds: categorizedTransactionsSelected, + }); + }; + return ( @@ -297,6 +305,14 @@ function AccountTransactionsActionsBar({ disabled={isUnexcludingLoading} /> )} + {!isEmpty(categorizedTransactionsSelected) && ( +