diff --git a/packages/server/src/api/controllers/Banking/BankTransactionsMatchingController.ts b/packages/server/src/api/controllers/Banking/BankTransactionsMatchingController.ts index 3b2199903..23c392e46 100644 --- a/packages/server/src/api/controllers/Banking/BankTransactionsMatchingController.ts +++ b/packages/server/src/api/controllers/Banking/BankTransactionsMatchingController.ts @@ -5,7 +5,7 @@ import { MatchBankTransactionsApplication } from '@/services/Banking/Matching/Ma import { body, param } from 'express-validator'; import { GetMatchedTransactionsFilter, - IMatchTransactionDTO, + IMatchTransactionsDTO, } from '@/services/Banking/Matching/types'; @Service() @@ -44,10 +44,10 @@ export class BankTransactionsMatchingController extends BaseController { * @param {Request} req * @param {Response} res * @param {NextFunction} next - * @returns + * @returns {Promise} */ private async matchBankTransaction( - req: Request, + req: Request<{ transactionId: number }>, res: Response, next: NextFunction ) { @@ -55,7 +55,7 @@ export class BankTransactionsMatchingController extends BaseController { const { transactionId } = req.params; const matchTransactionDTO = this.matchedBodyData( req - ) as IMatchTransactionDTO; + ) as IMatchTransactionsDTO; try { await this.bankTransactionsMatchingApp.matchTransaction( @@ -64,6 +64,7 @@ export class BankTransactionsMatchingController extends BaseController { matchTransactionDTO ); return res.status(200).send({ + id: transactionId, message: 'The bank transaction has been matched.', }); } catch (error) { @@ -72,60 +73,31 @@ export class BankTransactionsMatchingController extends BaseController { } /** - * + * Unmatches the matched bank transaction. * @param {Request} req * @param {Response} res * @param {NextFunction} next - * @returns + * @returns {Promise} */ private async unmatchMatchedBankTransaction( - req: Request, + req: Request<{ transactionId: number }>, res: Response, next: NextFunction ) { const { tenantId } = req; - const transactionId = req.params?.transactionId; + const { transactionId } = req.params; try { await this.bankTransactionsMatchingApp.unmatchMatchedTransaction( tenantId, transactionId ); - return res.status(200).send({ + id: transactionId, message: 'The bank matched transaction has been unmatched.', }); } catch (error) { next(error); } } - - /** - * Retrieves the matched transactions. - * @param {Request} req - * @param {Response} res - * @param {NextFunction} next - */ - private async getMatchedTransactions( - req: Request, - res: Response, - next: NextFunction - ) { - const { tenantId } = req; - const filter = this.matchedQueryData(req) as GetMatchedTransactionsFilter; - - console.log('test'); - - try { - const matchedTransactions = - await this.bankTransactionsMatchingApp.getMatchedTransactions( - tenantId, - filter - ); - - return res.status(200).send({ data: matchedTransactions }); - } catch (error) { - next(error); - } - } } diff --git a/packages/server/src/database/migrations/20240619133733_create_matched_bank_transactions_table.js b/packages/server/src/database/migrations/20240619133733_create_matched_bank_transactions_table.js index 1ed36e10c..2ca0a0966 100644 --- a/packages/server/src/database/migrations/20240619133733_create_matched_bank_transactions_table.js +++ b/packages/server/src/database/migrations/20240619133733_create_matched_bank_transactions_table.js @@ -3,7 +3,7 @@ exports.up = function (knex) { table.increments('id'); table.integer('uncategorized_transaction_id').unsigned(); table.string('reference_type'); - table.integer('reference_id'); + table.integer('reference_id').unsigned(); table.decimal('amount'); table.timestamps(); }); diff --git a/packages/server/src/models/BankRule.ts b/packages/server/src/models/BankRule.ts index ce47853f7..051a0f226 100644 --- a/packages/server/src/models/BankRule.ts +++ b/packages/server/src/models/BankRule.ts @@ -54,14 +54,17 @@ export class BankRule extends TenantModel { }, }, + /** + * Bank rule may associated to the assign account. + */ assignAccount: { relation: Model.BelongsToOneRelation, modelClass: Account.default, join: { from: 'bank_rules.assignAccountId', - to: 'accounts.id' - } - } + to: 'accounts.id', + }, + }, }; } } diff --git a/packages/server/src/services/Banking/Matching/MatchBankTransactionsApplication.ts b/packages/server/src/services/Banking/Matching/MatchBankTransactionsApplication.ts index 44895067a..61884190f 100644 --- a/packages/server/src/services/Banking/Matching/MatchBankTransactionsApplication.ts +++ b/packages/server/src/services/Banking/Matching/MatchBankTransactionsApplication.ts @@ -2,7 +2,7 @@ import { Inject, Service } from 'typedi'; import { GetMatchedTransactions } from './GetMatchedTransactions'; import { MatchBankTransactions } from './MatchTransactions'; import { UnmatchMatchedBankTransaction } from './UnmatchMatchedTransaction'; -import { GetMatchedTransactionsFilter, IMatchTransactionDTO } from './types'; +import { GetMatchedTransactionsFilter, IMatchTransactionsDTO } from './types'; @Service() export class MatchBankTransactionsApplication { @@ -43,7 +43,7 @@ export class MatchBankTransactionsApplication { public matchTransaction( tenantId: number, uncategorizedTransactionId: number, - matchTransactionsDTO: IMatchTransactionDTO + matchTransactionsDTO: IMatchTransactionsDTO ): Promise { return this.matchTransactionService.matchTransaction( tenantId, diff --git a/packages/server/src/services/Banking/Matching/MatchTransactions.ts b/packages/server/src/services/Banking/Matching/MatchTransactions.ts index a86a17953..c91cb152d 100644 --- a/packages/server/src/services/Banking/Matching/MatchTransactions.ts +++ b/packages/server/src/services/Banking/Matching/MatchTransactions.ts @@ -105,12 +105,13 @@ export class MatchBankTransactions { * Matches the given uncategorized transaction to the given references. * @param {number} tenantId * @param {number} uncategorizedTransactionId + * @returns {Promise} */ public async matchTransaction( tenantId: number, uncategorizedTransactionId: number, matchTransactionsDTO: IMatchTransactionsDTO - ) { + ): Promise { const { matchedTransactions } = matchTransactionsDTO; // Validates the given matching transactions DTO. diff --git a/packages/server/src/services/Banking/Matching/ValidateTransactionsMatched.ts b/packages/server/src/services/Banking/Matching/ValidateTransactionsMatched.ts index 372ae424c..e6e5a2cd7 100644 --- a/packages/server/src/services/Banking/Matching/ValidateTransactionsMatched.ts +++ b/packages/server/src/services/Banking/Matching/ValidateTransactionsMatched.ts @@ -9,10 +9,11 @@ export class ValidateTransactionMatched { private tenancy: HasTenancyService; /** - * + * Validate the given transaction whether is matched with bank transactions. * @param {number} tenantId - * @param {string} referenceType - * @param {number} referenceId + * @param {string} referenceType - Transaction reference type. + * @param {number} referenceId - Transaction reference id. + * @returns {Promise} */ public async validateTransactionNoMatchLinking( tenantId: number, diff --git a/packages/server/src/services/Banking/Matching/events/ValidateMatchingOnCashflowDelete.ts b/packages/server/src/services/Banking/Matching/events/ValidateMatchingOnCashflowDelete.ts index 9d9a8e965..c6087b9e8 100644 --- a/packages/server/src/services/Banking/Matching/events/ValidateMatchingOnCashflowDelete.ts +++ b/packages/server/src/services/Banking/Matching/events/ValidateMatchingOnCashflowDelete.ts @@ -14,15 +14,15 @@ export class ValidateMatchingOnCashflowDelete { public attach(bus) { bus.subscribe( events.cashflow.onTransactionDeleting, - this.validateMatchingOnCashflowDelete.bind(this) + this.validateMatchingOnCashflowDeleting.bind(this) ); } /** - * + * Validates the cashflow transaction whether matched with bank transaction on deleting. * @param {IManualJournalDeletingPayload} */ - public async validateMatchingOnCashflowDelete({ + public async validateMatchingOnCashflowDeleting({ tenantId, oldManualJournal, trx, diff --git a/packages/server/src/services/Banking/Matching/events/ValidateMatchingOnExpenseDelete.ts b/packages/server/src/services/Banking/Matching/events/ValidateMatchingOnExpenseDelete.ts index 2e45c0159..38c2dcba8 100644 --- a/packages/server/src/services/Banking/Matching/events/ValidateMatchingOnExpenseDelete.ts +++ b/packages/server/src/services/Banking/Matching/events/ValidateMatchingOnExpenseDelete.ts @@ -14,15 +14,15 @@ export class ValidateMatchingOnExpenseDelete { public attach(bus) { bus.subscribe( events.expenses.onDeleting, - this.validateMatchingOnExpenseDelete.bind(this) + this.validateMatchingOnExpenseDeleting.bind(this) ); } /** - * + * Validates the expense transaction whether matched with bank transaction on deleting. * @param {IExpenseEventDeletePayload} */ - public async validateMatchingOnExpenseDelete({ + public async validateMatchingOnExpenseDeleting({ tenantId, oldExpense, trx, diff --git a/packages/server/src/services/Banking/Matching/events/ValidateMatchingOnManualJournalDelete.ts b/packages/server/src/services/Banking/Matching/events/ValidateMatchingOnManualJournalDelete.ts index f4ebfbeca..90078bfdc 100644 --- a/packages/server/src/services/Banking/Matching/events/ValidateMatchingOnManualJournalDelete.ts +++ b/packages/server/src/services/Banking/Matching/events/ValidateMatchingOnManualJournalDelete.ts @@ -14,15 +14,15 @@ export class ValidateMatchingOnManualJournalDelete { public attach(bus) { bus.subscribe( events.manualJournals.onDeleting, - this.validateMatchingOnManualJournalDelete.bind(this) + this.validateMatchingOnManualJournalDeleting.bind(this) ); } /** - * + * Validates the manual journal transaction whether matched with bank transaction on deleting. * @param {IManualJournalDeletingPayload} */ - public async validateMatchingOnManualJournalDelete({ + public async validateMatchingOnManualJournalDeleting({ tenantId, oldManualJournal, trx, diff --git a/packages/server/src/services/Banking/Matching/events/ValidateMatchingOnPaymentMadeDelete.ts b/packages/server/src/services/Banking/Matching/events/ValidateMatchingOnPaymentMadeDelete.ts index 0188ed9dd..0ce97cdb9 100644 --- a/packages/server/src/services/Banking/Matching/events/ValidateMatchingOnPaymentMadeDelete.ts +++ b/packages/server/src/services/Banking/Matching/events/ValidateMatchingOnPaymentMadeDelete.ts @@ -17,15 +17,15 @@ export class ValidateMatchingOnPaymentMadeDelete { public attach(bus) { bus.subscribe( events.billPayment.onDeleting, - this.validateMatchingOnPaymentMadeDelete.bind(this) + this.validateMatchingOnPaymentMadeDeleting.bind(this) ); } /** - * + * Validates the payment made transaction whether matched with bank transaction on deleting. * @param {IPaymentReceiveDeletedPayload} */ - public async validateMatchingOnPaymentMadeDelete({ + public async validateMatchingOnPaymentMadeDeleting({ tenantId, oldBillPayment, trx, diff --git a/packages/server/src/services/Banking/Matching/events/ValidateMatchingOnPaymentReceivedDelete.ts b/packages/server/src/services/Banking/Matching/events/ValidateMatchingOnPaymentReceivedDelete.ts index e94020bb3..20c3018ac 100644 --- a/packages/server/src/services/Banking/Matching/events/ValidateMatchingOnPaymentReceivedDelete.ts +++ b/packages/server/src/services/Banking/Matching/events/ValidateMatchingOnPaymentReceivedDelete.ts @@ -14,15 +14,15 @@ export class ValidateMatchingOnPaymentReceivedDelete { public attach(bus) { bus.subscribe( events.paymentReceive.onDeleting, - this.validateMatchingOnPaymentReceivedDelete.bind(this) + this.validateMatchingOnPaymentReceivedDeleting.bind(this) ); } /** - * + * Validates the payment received transaction whether matched with bank transaction on deleting. * @param {IPaymentReceiveDeletedPayload} */ - public async validateMatchingOnPaymentReceivedDelete({ + public async validateMatchingOnPaymentReceivedDeleting({ tenantId, oldPaymentReceive, trx, diff --git a/packages/server/src/services/Banking/Rules/types.ts b/packages/server/src/services/Banking/Rules/types.ts index a2435204a..007756f41 100644 --- a/packages/server/src/services/Banking/Rules/types.ts +++ b/packages/server/src/services/Banking/Rules/types.ts @@ -3,6 +3,7 @@ import { Knex } from 'knex'; export enum BankRuleConditionField { Amount = 'Amount', Description = 'Description', + Payee = 'Payee' } export enum BankRuleConditionComparator { diff --git a/packages/server/src/services/Cashflow/GetUncategorizedTransactions.ts b/packages/server/src/services/Cashflow/GetUncategorizedTransactions.ts index 69199cc99..7b3c76774 100644 --- a/packages/server/src/services/Cashflow/GetUncategorizedTransactions.ts +++ b/packages/server/src/services/Cashflow/GetUncategorizedTransactions.ts @@ -1,4 +1,5 @@ import { Inject, Service } from 'typedi'; +import { initialize } from 'objection'; import HasTenancyService from '../Tenancy/TenancyService'; import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable'; import { UncategorizedTransactionTransformer } from './UncategorizedTransactionTransformer'; @@ -22,7 +23,13 @@ export class GetUncategorizedTransactions { accountId: number, query: IGetUncategorizedTransactionsQuery ) { - const { UncategorizedCashflowTransaction } = this.tenancy.models(tenantId); + const { + UncategorizedCashflowTransaction, + RecognizedBankTransaction, + MatchedBankTransaction, + Account, + } = this.tenancy.models(tenantId); + const knex = this.tenancy.knex(tenantId); // Parsed query with default values. const _query = { @@ -30,6 +37,15 @@ export class GetUncategorizedTransactions { pageSize: 20, ...query, }; + + // Initialize the ORM models metadata. + await initialize(knex, [ + UncategorizedCashflowTransaction, + MatchedBankTransaction, + RecognizedBankTransaction, + Account, + ]); + const { results, pagination } = await UncategorizedCashflowTransaction.query() .onBuild((q) => { @@ -40,6 +56,9 @@ export class GetUncategorizedTransactions { q.withGraphFetched('account'); q.withGraphFetched('recognizedTransaction.assignAccount'); + q.withGraphJoined('matchedBankTransactions'); + + q.whereNull('matchedBankTransactions.id'); q.orderBy('date', 'DESC'); }) .pagination(_query.page - 1, _query.pageSize); diff --git a/packages/webapp/src/containers/Banking/Rules/RuleFormDialog/RuleFormContentForm.tsx b/packages/webapp/src/containers/Banking/Rules/RuleFormDialog/RuleFormContentForm.tsx index 1fc94f6d1..0cf97da39 100644 --- a/packages/webapp/src/containers/Banking/Rules/RuleFormDialog/RuleFormContentForm.tsx +++ b/packages/webapp/src/containers/Banking/Rules/RuleFormDialog/RuleFormContentForm.tsx @@ -34,7 +34,6 @@ import { DialogsName } from '@/constants/dialogs'; function RuleFormContentFormRoot({ // #withDialogActions - openDialog, closeDialog, }) { const { accounts, bankRule, isEditMode, bankRuleId } = @@ -180,6 +179,10 @@ export const RuleFormContentForm = R.compose(withDialogActions)( RuleFormContentFormRoot, ); +/** + * Rule form conditions stack. + * @returns {React.ReactNode} + */ function RuleFormConditions() { const { values, setFieldValue } = useFormikContext(); @@ -245,6 +248,10 @@ function RuleFormConditions() { ); } +/** + * Rule form actions buttons. + * @returns {React.ReactNode} + */ function RuleFormActionsRoot({ // #withDialogActions closeDialog, diff --git a/packages/webapp/src/containers/CashFlow/AccountTransactions/AccountTransactionsActionsBar.tsx b/packages/webapp/src/containers/CashFlow/AccountTransactions/AccountTransactionsActionsBar.tsx index 51e131e24..b5db2eb78 100644 --- a/packages/webapp/src/containers/CashFlow/AccountTransactions/AccountTransactionsActionsBar.tsx +++ b/packages/webapp/src/containers/CashFlow/AccountTransactions/AccountTransactionsActionsBar.tsx @@ -135,7 +135,7 @@ function AccountTransactionsActionsBar({ { + const handleSubmit = ( + values: MatchingTransactionFormValues, + { setSubmitting }: FormikHelpers, + ) => { const _values = transformToReq(values); if (_values.matchedTransactions?.length === 0) { @@ -42,18 +48,22 @@ export function MatchingBankTransaction() { }); return; } + setSubmitting(true); matchTransaction({ id: uncategorizedTransactionId, value: _values }) .then(() => { AppToaster.show({ intent: Intent.SUCCESS, message: 'The bank transaction has been matched successfully.', }); + setSubmitting(false); + closeMatchingTransactionAside(); }) .catch((err) => { AppToaster.show({ intent: Intent.DANGER, message: 'Something went wrong.', }); + setSubmitting(false); }); }; @@ -71,6 +81,10 @@ export function MatchingBankTransaction() { ); } +export const MatchingBankTransaction = R.compose(withBankingActions)( + MatchingBankTransactionRoot, +); + function MatchingBankTransactionContent() { return ( @@ -193,12 +207,16 @@ interface MatchTransctionFooterProps extends WithBankingActionsProps {} */ const MatchTransactionFooter = R.compose(withBankingActions)( ({ closeMatchingTransactionAside }: MatchTransctionFooterProps) => { + const { submitForm, isSubmitting } = useFormikContext(); const totalPending = useGetPendingAmountMatched(); const showReconcileLink = useIsShowReconcileTransactionLink(); const handleCancelBtnClick = () => { closeMatchingTransactionAside(); }; + const handleSubmitBtnClick = () => { + submitForm(); + }; return ( @@ -223,7 +241,8 @@ const MatchTransactionFooter = R.compose(withBankingActions)( diff --git a/packages/webapp/src/hooks/query/bank-rules.ts b/packages/webapp/src/hooks/query/bank-rules.ts index 62a457bf5..9608f7496 100644 --- a/packages/webapp/src/hooks/query/bank-rules.ts +++ b/packages/webapp/src/hooks/query/bank-rules.ts @@ -116,6 +116,9 @@ export function useDeleteBankRule( queryClient.invalidateQueries( QUERY_KEY.RECOGNIZED_BANK_TRANSACTIONS_INFINITY, ); + queryClient.invalidateQueries([ + t.CASHFLOW_ACCOUNT_UNCATEGORIZED_TRANSACTIONS_INFINITY, + ]); }, ...options, }, diff --git a/packages/webapp/src/routes/dashboard.tsx b/packages/webapp/src/routes/dashboard.tsx index 47375ff88..b1b4cb1d4 100644 --- a/packages/webapp/src/routes/dashboard.tsx +++ b/packages/webapp/src/routes/dashboard.tsx @@ -1228,6 +1228,8 @@ export const getDashboardRoutes = () => [ () => import('@/containers/Banking/Rules/RulesList/RulesLandingPage'), ), pageTitle: 'Bank Rules', + breadcrumb: 'Bank Rules', + subscriptionActive: [SUBSCRIPTION_TYPE.MAIN], }, // Homepage { diff --git a/packages/webapp/src/store/banking/banking.actions.ts b/packages/webapp/src/store/banking/banking.actions.ts deleted file mode 100644 index 9374c8c4d..000000000 --- a/packages/webapp/src/store/banking/banking.actions.ts +++ /dev/null @@ -1,21 +0,0 @@ -// @ts-nocheck -import t from '@/store/types'; - -/** - * Sets global table state of the table. - * @param {object} queries - */ -export const setUncategorizedTransactionIdForMatching = ( - uncategorizedTransactionId: number, -) => { - return { - type: 'setUncategorizedTransactionIdForMatching', - payload: uncategorizedTransactionId, - }; -}; - -export const closeMatchingTransactionAside = () => { - return { - type: 'closeMatchingTransactionAside', - }; -};