From 7f41b4280ea6775fc39ac88ee417563e93f869db Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Mon, 8 Jul 2024 15:18:58 +0200 Subject: [PATCH 1/4] fix: the database migration schema --- ...0618171553_create_recognized_bank_transactions_table.js | 3 ++- ...ed_transaction_id_to_uncategorized_transactins_table.js | 7 ++++++- ...0240619133733_create_matched_bank_transactions_table.js | 6 +++++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/packages/server/src/database/migrations/20240618171553_create_recognized_bank_transactions_table.js b/packages/server/src/database/migrations/20240618171553_create_recognized_bank_transactions_table.js index aed658e65..fbb8e7734 100644 --- a/packages/server/src/database/migrations/20240618171553_create_recognized_bank_transactions_table.js +++ b/packages/server/src/database/migrations/20240618171553_create_recognized_bank_transactions_table.js @@ -5,7 +5,8 @@ exports.up = function (knex) { .integer('uncategorized_transaction_id') .unsigned() .references('id') - .inTable('uncategorized_cashflow_transactions'); + .inTable('uncategorized_cashflow_transactions') + .withKeyName('recognizedBankTransactionsUncategorizedTransIdForeign'); table .integer('bank_rule_id') .unsigned() diff --git a/packages/server/src/database/migrations/20240618175241_add_recognized_transaction_id_to_uncategorized_transactins_table.js b/packages/server/src/database/migrations/20240618175241_add_recognized_transaction_id_to_uncategorized_transactins_table.js index f50a13473..64c1e5450 100644 --- a/packages/server/src/database/migrations/20240618175241_add_recognized_transaction_id_to_uncategorized_transactins_table.js +++ b/packages/server/src/database/migrations/20240618175241_add_recognized_transaction_id_to_uncategorized_transactins_table.js @@ -1,6 +1,11 @@ exports.up = function (knex) { return knex.schema.table('uncategorized_cashflow_transactions', (table) => { - table.integer('recognized_transaction_id').unsigned(); + table + .integer('recognized_transaction_id') + .unsigned() + .references('id') + .inTable('recognized_bank_transactions') + .withKeyName('uncategorizedCashflowTransRecognizedTranIdForeign'); }); }; 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 2ca0a0966..f804ad7c7 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 @@ -1,7 +1,11 @@ exports.up = function (knex) { return knex.schema.createTable('matched_bank_transactions', (table) => { table.increments('id'); - table.integer('uncategorized_transaction_id').unsigned(); + table + .integer('uncategorized_transaction_id') + .unsigned() + .references('id') + .inTable('uncategorized_cashflow_transactions'); table.string('reference_type'); table.integer('reference_id').unsigned(); table.decimal('amount'); From 24a77c81b3ff6f92afe5e5bbdef7cf15eb379df6 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Mon, 8 Jul 2024 15:25:28 +0200 Subject: [PATCH 2/4] fix: unexpected char in cashflow transactions report --- .../CashflowAccountTransactions/CashflowAccountTransactions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/src/services/FinancialStatements/CashflowAccountTransactions/CashflowAccountTransactions.ts b/packages/server/src/services/FinancialStatements/CashflowAccountTransactions/CashflowAccountTransactions.ts index 7ca2694cf..f287c0268 100644 --- a/packages/server/src/services/FinancialStatements/CashflowAccountTransactions/CashflowAccountTransactions.ts +++ b/packages/server/src/services/FinancialStatements/CashflowAccountTransactions/CashflowAccountTransactions.ts @@ -74,7 +74,7 @@ export class CashflowAccountTransactionReport extends FinancialSheet { const firstMatchedTrans = first(matchedTrans); return ( - (firstCategorizedTrans?.id || + firstCategorizedTrans?.id || firstMatchedTrans?.uncategorizedTransactionId || null ); From 38d4122d11a35a3fb3e4625fe60ab99d5298d65e Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Mon, 8 Jul 2024 19:37:11 +0200 Subject: [PATCH 3/4] fix: matching transactions bugs --- .../server/src/interfaces/CashflowService.ts | 3 +- packages/server/src/interfaces/Import.ts | 8 +++ packages/server/src/loaders/eventEmitter.ts | 2 + .../UncategorizedCashflowTransaction.ts | 52 ----------------- ...crementUncategorizedTransactionsOnMatch.ts | 2 - .../RecognizeTransactionsJob.ts | 4 +- .../events/TriggerRecognizedTransactions.ts | 22 ++++++++ .../Cashflow/CategorizeCashflowTransaction.ts | 21 ++++--- .../UncategorizedTransactionsImportable.ts | 19 +++++++ ...entUncategorizedTransactionOnCategorize.ts | 56 +++++++++++++++++++ .../src/services/Import/ImportFileCommon.ts | 4 -- .../Import/ImportFileProcessCommit.ts | 47 ++++++++++++++++ .../Import/ImportResourceApplication.ts | 8 ++- packages/server/src/subscribers/events.ts | 5 ++ .../AccountTransactions/components.tsx | 4 +- .../CategorizeTransactionOtherIncome.tsx | 3 +- ...CategorizeTransactionOwnerContribution.tsx | 3 +- .../CategorizeTransactionTransferFrom.tsx | 3 +- .../CategorizeTransactionOtherExpense.tsx | 3 +- .../CategorizeTransactionOwnerDrawings.tsx | 3 +- .../CategorizeTransactionToAccount.tsx | 3 +- ...MatchingReconcileTransactionForm.schema.ts | 2 +- .../MatchingReconcileTransactionForm.tsx | 14 ++--- packages/webapp/src/hooks/query/bank-rules.ts | 26 +++++++++ .../src/hooks/query/cashflowAccounts.tsx | 6 ++ 25 files changed, 232 insertions(+), 91 deletions(-) create mode 100644 packages/server/src/interfaces/Import.ts create mode 100644 packages/server/src/services/Cashflow/subscribers/DecrementUncategorizedTransactionOnCategorize.ts create mode 100644 packages/server/src/services/Import/ImportFileProcessCommit.ts diff --git a/packages/server/src/interfaces/CashflowService.ts b/packages/server/src/interfaces/CashflowService.ts index cabdea423..99977c31d 100644 --- a/packages/server/src/interfaces/CashflowService.ts +++ b/packages/server/src/interfaces/CashflowService.ts @@ -130,8 +130,9 @@ export interface ICommandCashflowDeletedPayload { export interface ICashflowTransactionCategorizedPayload { tenantId: number; - cashflowTransactionId: number; + uncategorizedTransaction: any; cashflowTransaction: ICashflowTransaction; + categorizeDTO: any; trx: Knex.Transaction; } export interface ICashflowTransactionUncategorizingPayload { diff --git a/packages/server/src/interfaces/Import.ts b/packages/server/src/interfaces/Import.ts new file mode 100644 index 000000000..4f6d2e069 --- /dev/null +++ b/packages/server/src/interfaces/Import.ts @@ -0,0 +1,8 @@ +import { ImportFilePreviewPOJO } from "@/services/Import/interfaces"; + + +export interface IImportFileCommitedEventPayload { + tenantId: number; + importId: number; + meta: ImportFilePreviewPOJO; +} \ No newline at end of file diff --git a/packages/server/src/loaders/eventEmitter.ts b/packages/server/src/loaders/eventEmitter.ts index 21d20c387..8595bed55 100644 --- a/packages/server/src/loaders/eventEmitter.ts +++ b/packages/server/src/loaders/eventEmitter.ts @@ -112,6 +112,7 @@ import { RecognizeSyncedBankTranasctions } from '@/services/Banking/Plaid/subscr import { UnlinkBankRuleOnDeleteBankRule } from '@/services/Banking/Rules/events/UnlinkBankRuleOnDeleteBankRule'; import { DecrementUncategorizedTransactionOnMatching } from '@/services/Banking/Matching/events/DecrementUncategorizedTransactionsOnMatch'; import { DecrementUncategorizedTransactionOnExclude } from '@/services/Banking/Exclude/events/DecrementUncategorizedTransactionOnExclude'; +import { DecrementUncategorizedTransactionOnCategorize } from '@/services/Cashflow/subscribers/DecrementUncategorizedTransactionOnCategorize'; export default () => { return new EventPublisher(); @@ -262,6 +263,7 @@ export const susbcribers = () => { UnlinkBankRuleOnDeleteBankRule, DecrementUncategorizedTransactionOnMatching, DecrementUncategorizedTransactionOnExclude, + DecrementUncategorizedTransactionOnCategorize, // Validate matching ValidateMatchingOnCashflowDelete, diff --git a/packages/server/src/models/UncategorizedCashflowTransaction.ts b/packages/server/src/models/UncategorizedCashflowTransaction.ts index 2418b1711..029825583 100644 --- a/packages/server/src/models/UncategorizedCashflowTransaction.ts +++ b/packages/server/src/models/UncategorizedCashflowTransaction.ts @@ -184,56 +184,4 @@ export default class UncategorizedCashflowTransaction extends mixin( }, }; } - - /** - * Updates the count of uncategorized transactions for the associated account - * based on the specified operation. - * @param {QueryContext} queryContext - The query context for the transaction. - * @param {boolean} increment - Indicates whether to increment or decrement the count. - */ - private async updateUncategorizedTransactionCount( - queryContext: QueryContext, - increment: boolean, - amount: number = 1 - ) { - const operation = increment ? 'increment' : 'decrement'; - - await Account.query(queryContext.transaction) - .findById(this.accountId) - [operation]('uncategorized_transactions', amount); - } - - /** - * Runs after insert. - * @param {QueryContext} queryContext - */ - public async $afterInsert(queryContext) { - await super.$afterInsert(queryContext); - await this.updateUncategorizedTransactionCount(queryContext, true); - } - - /** - * Runs after update. - * @param {ModelOptions} opt - * @param {QueryContext} queryContext - */ - public async $afterUpdate( - opt: ModelOptions, - queryContext: QueryContext - ): Promise { - await super.$afterUpdate(opt, queryContext); - - if (this.id && this.categorized) { - await this.updateUncategorizedTransactionCount(queryContext, false); - } - } - - /** - * Runs after delete. - * @param {QueryContext} queryContext - */ - public async $afterDelete(queryContext: QueryContext) { - await super.$afterDelete(queryContext); - await this.updateUncategorizedTransactionCount(queryContext, false); - } } diff --git a/packages/server/src/services/Banking/Matching/events/DecrementUncategorizedTransactionsOnMatch.ts b/packages/server/src/services/Banking/Matching/events/DecrementUncategorizedTransactionsOnMatch.ts index 67dda577d..8cd291f18 100644 --- a/packages/server/src/services/Banking/Matching/events/DecrementUncategorizedTransactionsOnMatch.ts +++ b/packages/server/src/services/Banking/Matching/events/DecrementUncategorizedTransactionsOnMatch.ts @@ -39,7 +39,6 @@ export class DecrementUncategorizedTransactionOnMatching { const transaction = await UncategorizedCashflowTransaction.query().findById( uncategorizedTransactionId ); - // await Account.query(trx) .findById(transaction.accountId) .decrement('uncategorizedTransactions', 1); @@ -60,7 +59,6 @@ export class DecrementUncategorizedTransactionOnMatching { const transaction = await UncategorizedCashflowTransaction.query().findById( uncategorizedTransactionId ); - // await Account.query(trx) .findById(transaction.accountId) .increment('uncategorizedTransactions', 1); diff --git a/packages/server/src/services/Banking/RegonizeTranasctions/RecognizeTransactionsJob.ts b/packages/server/src/services/Banking/RegonizeTranasctions/RecognizeTransactionsJob.ts index 2eee22f53..9a3d625a3 100644 --- a/packages/server/src/services/Banking/RegonizeTranasctions/RecognizeTransactionsJob.ts +++ b/packages/server/src/services/Banking/RegonizeTranasctions/RecognizeTransactionsJob.ts @@ -18,11 +18,11 @@ export class RegonizeTransactionsJob { * Triggers sending invoice mail. */ private handler = async (job, done: Function) => { - const { tenantId } = job.attrs.data; + const { tenantId, batch } = job.attrs.data; const regonizeTransactions = Container.get(RecognizeTranasctionsService); try { - await regonizeTransactions.recognizeTransactions(tenantId); + await regonizeTransactions.recognizeTransactions(tenantId, batch); done(); } catch (error) { console.log(error); diff --git a/packages/server/src/services/Banking/RegonizeTranasctions/events/TriggerRecognizedTransactions.ts b/packages/server/src/services/Banking/RegonizeTranasctions/events/TriggerRecognizedTransactions.ts index bb8c87b43..bc5285d1b 100644 --- a/packages/server/src/services/Banking/RegonizeTranasctions/events/TriggerRecognizedTransactions.ts +++ b/packages/server/src/services/Banking/RegonizeTranasctions/events/TriggerRecognizedTransactions.ts @@ -5,6 +5,8 @@ import { IBankRuleEventDeletedPayload, IBankRuleEventEditedPayload, } from '../../Rules/types'; +import { IImportFileCommitedEventPayload } from '@/interfaces/Import'; +import { Import } from '@/system/models'; @Service() export class TriggerRecognizedTransactions { @@ -27,6 +29,10 @@ export class TriggerRecognizedTransactions { events.bankRules.onDeleted, this.recognizedTransactionsOnRuleDeleted.bind(this) ); + bus.subscribe( + events.import.onImportCommitted, + this.triggerRecognizeTransactionsOnImportCommitted.bind(this) + ); } /** @@ -73,4 +79,20 @@ export class TriggerRecognizedTransactions { const payload = { tenantId }; await this.agenda.now('recognize-uncategorized-transactions-job', payload); } + + /** + * Triggers the recognize bank transactions once the imported file commit. + * @param {IImportFileCommitedEventPayload} payload - + */ + private async triggerRecognizeTransactionsOnImportCommitted({ + tenantId, + importId, + meta, + }: IImportFileCommitedEventPayload) { + const importFile = await Import.query().findOne({ importId }); + const batch = importFile.paramsParsed.batch; + const payload = { tenantId, batch }; + + await this.agenda.now('recognize-uncategorized-transactions-job', payload); + } } diff --git a/packages/server/src/services/Cashflow/CategorizeCashflowTransaction.ts b/packages/server/src/services/Cashflow/CategorizeCashflowTransaction.ts index ba8f7c078..43ddfe603 100644 --- a/packages/server/src/services/Cashflow/CategorizeCashflowTransaction.ts +++ b/packages/server/src/services/Cashflow/CategorizeCashflowTransaction.ts @@ -84,20 +84,23 @@ export class CategorizeCashflowTransaction { cashflowTransactionDTO ); // Updates the uncategorized transaction as categorized. - await UncategorizedCashflowTransaction.query(trx).patchAndFetchById( - uncategorizedTransactionId, - { - categorized: true, - categorizeRefType: 'CashflowTransaction', - categorizeRefId: cashflowTransaction.id, - } - ); + const uncategorizedTransaction = + await UncategorizedCashflowTransaction.query(trx).patchAndFetchById( + uncategorizedTransactionId, + { + categorized: true, + categorizeRefType: 'CashflowTransaction', + categorizeRefId: cashflowTransaction.id, + } + ); // Triggers `onCashflowTransactionCategorized` event. await this.eventPublisher.emitAsync( events.cashflow.onTransactionCategorized, { tenantId, - // cashflowTransaction, + cashflowTransaction, + uncategorizedTransaction, + categorizeDTO, trx, } as ICashflowTransactionCategorizedPayload ); diff --git a/packages/server/src/services/Cashflow/UncategorizedTransactionsImportable.ts b/packages/server/src/services/Cashflow/UncategorizedTransactionsImportable.ts index a08a27f07..6350dc406 100644 --- a/packages/server/src/services/Cashflow/UncategorizedTransactionsImportable.ts +++ b/packages/server/src/services/Cashflow/UncategorizedTransactionsImportable.ts @@ -1,6 +1,7 @@ import { Inject, Service } from 'typedi'; import { Knex } from 'knex'; import * as yup from 'yup'; +import uniqid from 'uniqid'; import { Importable } from '../Import/Importable'; import { CreateUncategorizedTransaction } from './CreateUncategorizedTransaction'; import { CreateUncategorizedTransactionDTO } from '@/interfaces'; @@ -15,6 +16,7 @@ export class UncategorizedTransactionsImportable extends Importable { @Inject() private tenancy: HasTenancyService; + /** * Passing the sheet DTO to create uncategorized transaction. * @param {number} tenantId @@ -43,6 +45,7 @@ export class UncategorizedTransactionsImportable extends Importable { return { ...createDTO, accountId: context.import.paramsParsed.accountId, + batch: context.import.paramsParsed.batch, }; } @@ -54,6 +57,9 @@ export class UncategorizedTransactionsImportable extends Importable { return BankTransactionsSampleData; } + // ------------------ + // # Params + // ------------------ /** * Params validation schema. * @returns {ValidationSchema[]} @@ -79,4 +85,17 @@ export class UncategorizedTransactionsImportable extends Importable { await Account.query().findById(params.accountId).throwIfNotFound({}); } } + + /** + * Transformes the import params before storing them. + * @param {Record} parmas + */ + public transformParams(parmas: Record) { + const batch = uniqid(); + + return { + ...parmas, + batch, + }; + } } diff --git a/packages/server/src/services/Cashflow/subscribers/DecrementUncategorizedTransactionOnCategorize.ts b/packages/server/src/services/Cashflow/subscribers/DecrementUncategorizedTransactionOnCategorize.ts new file mode 100644 index 000000000..eb56d67dd --- /dev/null +++ b/packages/server/src/services/Cashflow/subscribers/DecrementUncategorizedTransactionOnCategorize.ts @@ -0,0 +1,56 @@ +import { Inject, Service } from 'typedi'; +import events from '@/subscribers/events'; +import HasTenancyService from '@/services/Tenancy/TenancyService'; +import { + ICashflowTransactionCategorizedPayload, + ICashflowTransactionUncategorizedPayload, +} from '@/interfaces'; + +@Service() +export class DecrementUncategorizedTransactionOnCategorize { + @Inject() + private tenancy: HasTenancyService; + /** + * Constructor method. + */ + public attach(bus) { + bus.subscribe( + events.cashflow.onTransactionCategorized, + this.decrementUnCategorizedTransactionsOnCategorized.bind(this) + ); + bus.subscribe( + events.cashflow.onTransactionUncategorized, + this.incrementUnCategorizedTransactionsOnUncategorized.bind(this) + ); + } + + /** + * Decrement the uncategoirzed transactions on the account once categorizing. + * @param {ICashflowTransactionCategorizedPayload} + */ + public async decrementUnCategorizedTransactionsOnCategorized({ + tenantId, + uncategorizedTransaction, + }: ICashflowTransactionCategorizedPayload) { + const { Account } = this.tenancy.models(tenantId); + + await Account.query() + .findById(uncategorizedTransaction.accountId) + .decrement('uncategorizedTransactions', 1); + } + + /** + * Increment the uncategorized transaction on the given account on uncategorizing. + * @param {IManualJournalDeletingPayload} + */ + public async incrementUnCategorizedTransactionsOnUncategorized({ + tenantId, + uncategorizedTransaction, + }: ICashflowTransactionUncategorizedPayload) { + const { Account } = this.tenancy.models(tenantId); + + await Account.query() + .findById(uncategorizedTransaction.accountId) + .increment('uncategorizedTransactions', 1); + } +} diff --git a/packages/server/src/services/Import/ImportFileCommon.ts b/packages/server/src/services/Import/ImportFileCommon.ts index 739927e6c..4e9179bdd 100644 --- a/packages/server/src/services/Import/ImportFileCommon.ts +++ b/packages/server/src/services/Import/ImportFileCommon.ts @@ -15,14 +15,10 @@ import { ServiceError } from '@/exceptions'; import { getUniqueImportableValue, trimObject } from './_utils'; import { ImportableResources } from './ImportableResources'; import ResourceService from '../Resource/ResourceService'; -import HasTenancyService from '../Tenancy/TenancyService'; import { Import } from '@/system/models'; @Service() export class ImportFileCommon { - @Inject() - private tenancy: HasTenancyService; - @Inject() private importFileValidator: ImportFileDataValidator; diff --git a/packages/server/src/services/Import/ImportFileProcessCommit.ts b/packages/server/src/services/Import/ImportFileProcessCommit.ts new file mode 100644 index 000000000..14c229fec --- /dev/null +++ b/packages/server/src/services/Import/ImportFileProcessCommit.ts @@ -0,0 +1,47 @@ +import { Inject, Service } from 'typedi'; +import HasTenancyService from '../Tenancy/TenancyService'; +import { ImportFilePreviewPOJO } from './interfaces'; +import { ImportFileProcess } from './ImportFileProcess'; +import { EventPublisher } from '@/lib/EventPublisher/EventPublisher'; +import events from '@/subscribers/events'; +import { IImportFileCommitedEventPayload } from '@/interfaces/Import'; + +@Service() +export class ImportFileProcessCommit { + @Inject() + private tenancy: HasTenancyService; + + @Inject() + private importFile: ImportFileProcess; + + @Inject() + private eventPublisher: EventPublisher; + + /** + * Commits the imported file. + * @param {number} tenantId + * @param {number} importId + * @returns {Promise} + */ + public async commit( + tenantId: number, + importId: number + ): Promise { + const knex = this.tenancy.knex(tenantId); + const trx = await knex.transaction({ isolationLevel: 'read uncommitted' }); + + const meta = await this.importFile.import(tenantId, importId, trx); + + // Commit the successed transaction. + await trx.commit(); + + // Triggers `onImportFileCommitted` event. + await this.eventPublisher.emitAsync(events.import.onImportCommitted, { + meta, + importId, + tenantId, + } as IImportFileCommitedEventPayload); + + return meta; + } +} diff --git a/packages/server/src/services/Import/ImportResourceApplication.ts b/packages/server/src/services/Import/ImportResourceApplication.ts index e4a7cb2f9..34df746c6 100644 --- a/packages/server/src/services/Import/ImportResourceApplication.ts +++ b/packages/server/src/services/Import/ImportResourceApplication.ts @@ -6,6 +6,7 @@ import { ImportFileProcess } from './ImportFileProcess'; import { ImportFilePreview } from './ImportFilePreview'; import { ImportSampleService } from './ImportSample'; import { ImportFileMeta } from './ImportFileMeta'; +import { ImportFileProcessCommit } from './ImportFileProcessCommit'; @Inject() export class ImportResourceApplication { @@ -27,6 +28,9 @@ export class ImportResourceApplication { @Inject() private importMetaService: ImportFileMeta; + @Inject() + private importProcessCommit: ImportFileProcessCommit; + /** * Reads the imported file and stores the import file meta under unqiue id. * @param {number} tenantId - @@ -74,12 +78,12 @@ export class ImportResourceApplication { * @returns {Promise} */ public async process(tenantId: number, importId: number) { - return this.importProcessService.import(tenantId, importId); + return this.importProcessCommit.commit(tenantId, importId); } /** * Retrieves the import meta of the given import id. - * @param {number} tenantId - + * @param {number} tenantId - * @param {string} importId - Import id. * @returns {} */ diff --git a/packages/server/src/subscribers/events.ts b/packages/server/src/subscribers/events.ts index b254c7113..ba6f28c35 100644 --- a/packages/server/src/subscribers/events.ts +++ b/packages/server/src/subscribers/events.ts @@ -647,4 +647,9 @@ export default { onUnexcluding: 'onBankTransactionUnexcluding', onUnexcluded: 'onBankTransactionUnexcluded', }, + + // Import files. + import: { + onImportCommitted: 'onImportFileCommitted', + }, }; diff --git a/packages/webapp/src/containers/CashFlow/AccountTransactions/components.tsx b/packages/webapp/src/containers/CashFlow/AccountTransactions/components.tsx index d69196163..2edf0c423 100644 --- a/packages/webapp/src/containers/CashFlow/AccountTransactions/components.tsx +++ b/packages/webapp/src/containers/CashFlow/AccountTransactions/components.tsx @@ -9,7 +9,6 @@ import { PopoverInteractionKind, Position, Tooltip, - MenuDivider, } from '@blueprintjs/core'; import { Box, FormatDateCell, Icon, MaterialProgressBar } from '@/components'; import { useAccountTransactionsContext } from './AccountTransactionsProvider'; @@ -213,9 +212,8 @@ export function useAccountUncategorizedTransactionsColumns() { { id: 'reference_number', Header: 'Ref.#', - accessor: 'reference_number', + accessor: 'reference_no', width: 50, - className: 'reference_number', clickable: true, textOverview: true, }, diff --git a/packages/webapp/src/containers/CashFlow/CategorizeTransaction/drawers/CategorizeTransactionDrawer/MoneyIn/CategorizeTransactionOtherIncome.tsx b/packages/webapp/src/containers/CashFlow/CategorizeTransaction/drawers/CategorizeTransactionDrawer/MoneyIn/CategorizeTransactionOtherIncome.tsx index b45f00bb9..aecf24af3 100644 --- a/packages/webapp/src/containers/CashFlow/CategorizeTransaction/drawers/CategorizeTransactionDrawer/MoneyIn/CategorizeTransactionOtherIncome.tsx +++ b/packages/webapp/src/containers/CashFlow/CategorizeTransaction/drawers/CategorizeTransactionDrawer/MoneyIn/CategorizeTransactionOtherIncome.tsx @@ -6,6 +6,7 @@ import { FFormGroup, FInputGroup, FTextArea, + Icon, } from '@/components'; import { useCategorizeTransactionBoot } from '../CategorizeTransactionBoot'; import { CategorizeTransactionBranchField } from '../CategorizeTransactionBranchField'; @@ -21,7 +22,7 @@ export default function CategorizeTransactionOtherIncome() { popoverProps={{ position: Position.BOTTOM, minimal: true }} formatDate={(date) => date.toLocaleDateString()} parseDate={(str) => new Date(str)} - inputProps={{ fill: true }} + inputProps={{ fill: true, leftElement: }} /> diff --git a/packages/webapp/src/containers/CashFlow/CategorizeTransaction/drawers/CategorizeTransactionDrawer/MoneyIn/CategorizeTransactionOwnerContribution.tsx b/packages/webapp/src/containers/CashFlow/CategorizeTransaction/drawers/CategorizeTransactionDrawer/MoneyIn/CategorizeTransactionOwnerContribution.tsx index f850f4dc0..5c257ef2b 100644 --- a/packages/webapp/src/containers/CashFlow/CategorizeTransaction/drawers/CategorizeTransactionDrawer/MoneyIn/CategorizeTransactionOwnerContribution.tsx +++ b/packages/webapp/src/containers/CashFlow/CategorizeTransaction/drawers/CategorizeTransactionDrawer/MoneyIn/CategorizeTransactionOwnerContribution.tsx @@ -6,6 +6,7 @@ import { FFormGroup, FInputGroup, FTextArea, + Icon, } from '@/components'; import { useCategorizeTransactionBoot } from '../CategorizeTransactionBoot'; import { CategorizeTransactionBranchField } from '../CategorizeTransactionBranchField'; @@ -21,7 +22,7 @@ export default function CategorizeTransactionOwnerContribution() { popoverProps={{ position: Position.BOTTOM, minimal: true }} formatDate={(date) => date.toLocaleDateString()} parseDate={(str) => new Date(str)} - inputProps={{ fill: true }} + inputProps={{ fill: true, leftElement: }} /> diff --git a/packages/webapp/src/containers/CashFlow/CategorizeTransaction/drawers/CategorizeTransactionDrawer/MoneyIn/CategorizeTransactionTransferFrom.tsx b/packages/webapp/src/containers/CashFlow/CategorizeTransaction/drawers/CategorizeTransactionDrawer/MoneyIn/CategorizeTransactionTransferFrom.tsx index 01b5b8bba..837b4aaa7 100644 --- a/packages/webapp/src/containers/CashFlow/CategorizeTransaction/drawers/CategorizeTransactionDrawer/MoneyIn/CategorizeTransactionTransferFrom.tsx +++ b/packages/webapp/src/containers/CashFlow/CategorizeTransaction/drawers/CategorizeTransactionDrawer/MoneyIn/CategorizeTransactionTransferFrom.tsx @@ -6,6 +6,7 @@ import { FFormGroup, FInputGroup, FTextArea, + Icon, } from '@/components'; import { useCategorizeTransactionBoot } from '../CategorizeTransactionBoot'; import { CategorizeTransactionBranchField } from '../CategorizeTransactionBranchField'; @@ -21,7 +22,7 @@ export default function CategorizeTransactionTransferFrom() { popoverProps={{ position: Position.BOTTOM, minimal: true }} formatDate={(date) => date.toLocaleDateString()} parseDate={(str) => new Date(str)} - inputProps={{ fill: true }} + inputProps={{ fill: true, leftElement: }} /> diff --git a/packages/webapp/src/containers/CashFlow/CategorizeTransaction/drawers/CategorizeTransactionDrawer/MoneyOut/CategorizeTransactionOtherExpense.tsx b/packages/webapp/src/containers/CashFlow/CategorizeTransaction/drawers/CategorizeTransactionDrawer/MoneyOut/CategorizeTransactionOtherExpense.tsx index 9be2042f2..0071ea99e 100644 --- a/packages/webapp/src/containers/CashFlow/CategorizeTransaction/drawers/CategorizeTransactionDrawer/MoneyOut/CategorizeTransactionOtherExpense.tsx +++ b/packages/webapp/src/containers/CashFlow/CategorizeTransaction/drawers/CategorizeTransactionDrawer/MoneyOut/CategorizeTransactionOtherExpense.tsx @@ -6,6 +6,7 @@ import { FFormGroup, FInputGroup, FTextArea, + Icon, } from '@/components'; import { useCategorizeTransactionBoot } from '../CategorizeTransactionBoot'; import { CategorizeTransactionBranchField } from '../CategorizeTransactionBranchField'; @@ -21,7 +22,7 @@ export default function CategorizeTransactionOtherExpense() { popoverProps={{ position: Position.BOTTOM, minimal: true }} formatDate={(date) => date.toLocaleDateString()} parseDate={(str) => new Date(str)} - inputProps={{ fill: true }} + inputProps={{ fill: true, leftElement: }} /> diff --git a/packages/webapp/src/containers/CashFlow/CategorizeTransaction/drawers/CategorizeTransactionDrawer/MoneyOut/CategorizeTransactionOwnerDrawings.tsx b/packages/webapp/src/containers/CashFlow/CategorizeTransaction/drawers/CategorizeTransactionDrawer/MoneyOut/CategorizeTransactionOwnerDrawings.tsx index 79746421f..4f5ea024b 100644 --- a/packages/webapp/src/containers/CashFlow/CategorizeTransaction/drawers/CategorizeTransactionDrawer/MoneyOut/CategorizeTransactionOwnerDrawings.tsx +++ b/packages/webapp/src/containers/CashFlow/CategorizeTransaction/drawers/CategorizeTransactionDrawer/MoneyOut/CategorizeTransactionOwnerDrawings.tsx @@ -6,6 +6,7 @@ import { FFormGroup, FInputGroup, FTextArea, + Icon, } from '@/components'; import { useCategorizeTransactionBoot } from '../CategorizeTransactionBoot'; import { CategorizeTransactionBranchField } from '../CategorizeTransactionBranchField'; @@ -21,7 +22,7 @@ export default function CategorizeTransactionOwnerDrawings() { popoverProps={{ position: Position.BOTTOM, minimal: true }} formatDate={(date) => date.toLocaleDateString()} parseDate={(str) => new Date(str)} - inputProps={{ fill: true }} + inputProps={{ fill: true, leftElement: }} /> diff --git a/packages/webapp/src/containers/CashFlow/CategorizeTransaction/drawers/CategorizeTransactionDrawer/MoneyOut/CategorizeTransactionToAccount.tsx b/packages/webapp/src/containers/CashFlow/CategorizeTransaction/drawers/CategorizeTransactionDrawer/MoneyOut/CategorizeTransactionToAccount.tsx index 93d6b7174..4a13f30ad 100644 --- a/packages/webapp/src/containers/CashFlow/CategorizeTransaction/drawers/CategorizeTransactionDrawer/MoneyOut/CategorizeTransactionToAccount.tsx +++ b/packages/webapp/src/containers/CashFlow/CategorizeTransaction/drawers/CategorizeTransactionDrawer/MoneyOut/CategorizeTransactionToAccount.tsx @@ -6,6 +6,7 @@ import { FFormGroup, FInputGroup, FTextArea, + Icon, } from '@/components'; import { useCategorizeTransactionBoot } from '../CategorizeTransactionBoot'; import { CategorizeTransactionBranchField } from '../CategorizeTransactionBranchField'; @@ -21,7 +22,7 @@ export default function CategorizeTransactionToAccount() { popoverProps={{ position: Position.BOTTOM, minimal: true }} formatDate={(date) => date.toLocaleDateString()} parseDate={(str) => new Date(str)} - inputProps={{ fill: true }} + inputProps={{ fill: true, leftElement: }} /> diff --git a/packages/webapp/src/containers/CashFlow/CategorizeTransactionAside/MatchingReconcileTransactionAside/MatchingReconcileTransactionForm.schema.ts b/packages/webapp/src/containers/CashFlow/CategorizeTransactionAside/MatchingReconcileTransactionAside/MatchingReconcileTransactionForm.schema.ts index 107444d44..35470e6d0 100644 --- a/packages/webapp/src/containers/CashFlow/CategorizeTransactionAside/MatchingReconcileTransactionAside/MatchingReconcileTransactionForm.schema.ts +++ b/packages/webapp/src/containers/CashFlow/CategorizeTransactionAside/MatchingReconcileTransactionAside/MatchingReconcileTransactionForm.schema.ts @@ -4,7 +4,7 @@ export const MatchingReconcileFormSchema = Yup.object().shape({ type: Yup.string().required().label('Type'), date: Yup.string().required().label('Date'), amount: Yup.string().required().label('Amount'), - memo: Yup.string().required().label('Memo'), + memo: Yup.string().required().min(3).label('Memo'), referenceNo: Yup.string().label('Refernece #'), category: Yup.string().required().label('Categogry'), }); diff --git a/packages/webapp/src/containers/CashFlow/CategorizeTransactionAside/MatchingReconcileTransactionAside/MatchingReconcileTransactionForm.tsx b/packages/webapp/src/containers/CashFlow/CategorizeTransactionAside/MatchingReconcileTransactionAside/MatchingReconcileTransactionForm.tsx index 3ae33fd68..7e93aacd6 100644 --- a/packages/webapp/src/containers/CashFlow/CategorizeTransactionAside/MatchingReconcileTransactionAside/MatchingReconcileTransactionForm.tsx +++ b/packages/webapp/src/containers/CashFlow/CategorizeTransactionAside/MatchingReconcileTransactionAside/MatchingReconcileTransactionForm.tsx @@ -1,14 +1,9 @@ // @ts-nocheck import * as R from 'ramda'; import { Button, Intent, Position, Tag } from '@blueprintjs/core'; -import { - Form, - Formik, - FormikHelpers, - FormikValues, - useFormikContext, -} from 'formik'; +import { Form, Formik, FormikHelpers, useFormikContext } from 'formik'; import moment from 'moment'; +import { round } from 'lodash'; import { AccountsSelect, AppToaster, @@ -19,6 +14,7 @@ import { FInputGroup, FMoneyInputGroup, Group, + Icon, } from '@/components'; import { Aside } from '@/components/Aside/Aside'; import { momentFormatter } from '@/utils'; @@ -100,7 +96,7 @@ function MatchingReconcileTransactionFormRoot({ const _initialValues = { ...initialValues, - amount: Math.abs(reconcileMatchingTransactionPendingAmount) || 0, + amount: round(Math.abs(reconcileMatchingTransactionPendingAmount), 2) || 0, date: moment().format('YYYY-MM-DD'), type: reconcileMatchingTransactionPendingAmount > 0 ? 'deposit' : 'withdrawal', @@ -179,7 +175,7 @@ function CreateReconcileTransactionContent() { }, boundary: 'viewport', }} - inputProps={{ fill: true }} + inputProps={{ fill: true, leftElement: }} fill fastField /> diff --git a/packages/webapp/src/hooks/query/bank-rules.ts b/packages/webapp/src/hooks/query/bank-rules.ts index 7f489f92a..77a770ff4 100644 --- a/packages/webapp/src/hooks/query/bank-rules.ts +++ b/packages/webapp/src/hooks/query/bank-rules.ts @@ -235,6 +235,12 @@ export function useExcludeUncategorizedTransaction( queryClient.invalidateQueries( t.CASHFLOW_ACCOUNT_UNCATEGORIZED_TRANSACTIONS_INFINITY, ); + // Invalidate accounts. + queryClient.invalidateQueries(t.ACCOUNTS); + queryClient.invalidateQueries(t.ACCOUNT); + + // invalidate bank account summary. + queryClient.invalidateQueries(QUERY_KEY.BANK_ACCOUNT_SUMMARY_META); }, ...options, }, @@ -282,6 +288,12 @@ export function useUnexcludeUncategorizedTransaction( queryClient.invalidateQueries( t.CASHFLOW_ACCOUNT_UNCATEGORIZED_TRANSACTIONS_INFINITY, ); + // Invalidate accounts. + queryClient.invalidateQueries(t.ACCOUNTS); + queryClient.invalidateQueries(t.ACCOUNT); + + // Invalidate bank account summary. + queryClient.invalidateQueries(QUERY_KEY.BANK_ACCOUNT_SUMMARY_META); }, ...options, }, @@ -323,6 +335,13 @@ export function useMatchUncategorizedTransaction( t.CASHFLOW_ACCOUNT_UNCATEGORIZED_TRANSACTIONS_INFINITY, ); queryClient.invalidateQueries(t.CASHFLOW_ACCOUNT_TRANSACTIONS_INFINITY); + + // Invalidate accounts. + queryClient.invalidateQueries(t.ACCOUNTS); + queryClient.invalidateQueries(t.ACCOUNT); + + // Invalidate bank account summary. + queryClient.invalidateQueries(QUERY_KEY.BANK_ACCOUNT_SUMMARY_META); }, ...props, }); @@ -362,6 +381,13 @@ export function useUnmatchMatchedUncategorizedTransaction( t.CASHFLOW_ACCOUNT_UNCATEGORIZED_TRANSACTIONS_INFINITY, ); queryClient.invalidateQueries(t.CASHFLOW_ACCOUNT_TRANSACTIONS_INFINITY); + + // Invalidate accounts. + queryClient.invalidateQueries(t.ACCOUNTS); + queryClient.invalidateQueries(t.ACCOUNT); + + // Invalidate bank account summary. + queryClient.invalidateQueries(QUERY_KEY.BANK_ACCOUNT_SUMMARY_META); }, ...props, }); diff --git a/packages/webapp/src/hooks/query/cashflowAccounts.tsx b/packages/webapp/src/hooks/query/cashflowAccounts.tsx index 656d7cccf..0681210b7 100644 --- a/packages/webapp/src/hooks/query/cashflowAccounts.tsx +++ b/packages/webapp/src/hooks/query/cashflowAccounts.tsx @@ -253,6 +253,9 @@ export function useCategorizeTransaction(props) { queryClient.invalidateQueries( t.CASHFLOW_ACCOUNT_UNCATEGORIZED_TRANSACTIONS_INFINITY, ); + + // Invalidate bank account summary. + queryClient.invalidateQueries('BANK_ACCOUNT_SUMMARY_META'); }, ...props, }, @@ -276,6 +279,9 @@ export function useUncategorizeTransaction(props) { queryClient.invalidateQueries( t.CASHFLOW_ACCOUNT_UNCATEGORIZED_TRANSACTIONS_INFINITY, ); + + // Invalidate bank account summary. + queryClient.invalidateQueries('BANK_ACCOUNT_SUMMARY_META'); }, ...props, }, From 73acdb6240c5ede439ad5e709e60368caa660eda Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Mon, 8 Jul 2024 21:48:16 +0200 Subject: [PATCH 4/4] fix: add bank rule categories --- .../webapp/src/constants/cashflowOptions.tsx | 9 + .../RuleFormDialog/RuleFormContentForm.tsx | 161 ++++++++++++++---- .../Banking/Rules/RuleFormDialog/_utils.ts | 11 +- 3 files changed, 143 insertions(+), 38 deletions(-) diff --git a/packages/webapp/src/constants/cashflowOptions.tsx b/packages/webapp/src/constants/cashflowOptions.tsx index aa9e477f8..110c8b8ac 100644 --- a/packages/webapp/src/constants/cashflowOptions.tsx +++ b/packages/webapp/src/constants/cashflowOptions.tsx @@ -39,3 +39,12 @@ export const TRANSACRIONS_TYPE = [ 'OtherExpense', 'TransferToAccount', ]; + +export const MoneyCategoryPerCreditAccountRootType = { + OwnerContribution: ['equity'], + OtherIncome: ['income'], + OwnerDrawing: ['equity'], + OtherExpense: ['expense'], + TransferToAccount: ['asset'], + TransferFromAccount: ['asset'], +}; diff --git a/packages/webapp/src/containers/Banking/Rules/RuleFormDialog/RuleFormContentForm.tsx b/packages/webapp/src/containers/Banking/Rules/RuleFormDialog/RuleFormContentForm.tsx index 0cf97da39..96b5f4364 100644 --- a/packages/webapp/src/containers/Banking/Rules/RuleFormDialog/RuleFormContentForm.tsx +++ b/packages/webapp/src/containers/Banking/Rules/RuleFormDialog/RuleFormContentForm.tsx @@ -1,4 +1,5 @@ // @ts-nocheck +import { useCallback, useMemo } from 'react'; import { Form, Formik, FormikHelpers, useFormikContext } from 'formik'; import { Button, Classes, Intent, Radio, Tag } from '@blueprintjs/core'; import * as R from 'ramda'; @@ -16,11 +17,11 @@ import { } from '@/components'; import { useCreateBankRule, useEditBankRule } from '@/hooks/query/bank-rules'; import { - AssignTransactionTypeOptions, FieldCondition, Fields, RuleFormValues, TransactionTypeOptions, + getAccountRootFromMoneyCategory, initialValues, } from './_utils'; import { useRuleFormDialogBoot } from './RuleFormBoot'; @@ -31,6 +32,11 @@ import { } from '@/utils'; import withDialogActions from '@/containers/Dialog/withDialogActions'; import { DialogsName } from '@/constants/dialogs'; +import { getAddMoneyInOptions, getAddMoneyOutOptions } from '@/constants'; + +// Retrieves the add money in button options. +const MoneyInOptions = getAddMoneyInOptions(); +const MoneyOutOptions = getAddMoneyOutOptions(); function RuleFormContentFormRoot({ // #withDialogActions @@ -47,7 +53,6 @@ function RuleFormContentFormRoot({ ...initialValues, ...transformToForm(transformToCamelCase(bankRule), initialValues), }; - // Handles the form submitting. const handleSubmit = ( values: RuleFormValues, @@ -92,8 +97,9 @@ function RuleFormContentFormRoot({ label={'Rule Name'} labelInfo={Required} style={{ maxWidth: 300 }} + fastField > - + Required} style={{ maxWidth: 350 }} + fastField > - - - + @@ -139,34 +138,16 @@ function RuleFormContentFormRoot({ Then Assign - Required} - style={{ maxWidth: 300 }} - > - - - - Required} - style={{ maxWidth: 300 }} - > - - + + - + @@ -203,11 +184,13 @@ function RuleFormConditions() { name={`conditions[${index}].field`} label={'Field'} style={{ marginBottom: 0, flex: '1 0' }} + fastField > @@ -215,11 +198,13 @@ function RuleFormConditions() { name={`conditions[${index}].comparator`} label={'Condition'} style={{ marginBottom: 0, flex: '1 0' }} + fastField > @@ -227,8 +212,9 @@ function RuleFormConditions() { name={`conditions[${index}].value`} label={'Value'} style={{ marginBottom: 0, flex: '1 0 ', width: '40%' }} + fastField > - + ))} @@ -284,3 +270,104 @@ function RuleFormActionsRoot({ } const RuleFormActions = R.compose(withDialogActions)(RuleFormActionsRoot); + +function RuleApplyIfTransactionTypeField() { + const { setFieldValue } = useFormikContext(); + + const handleItemChange = useCallback( + (item: any) => { + setFieldValue('applyIfTransactionType', item.value); + setFieldValue('assignCategory', ''); + setFieldValue('assignAccountId', ''); + }, + [setFieldValue], + ); + + return ( + + + + ); +} + +function RuleAssignCategoryField() { + const { values, setFieldValue } = useFormikContext(); + + // Retrieves the transaction types if it is deposit or withdrawal. + const transactionTypes = useMemo( + () => + values?.applyIfTransactionType === 'deposit' + ? MoneyInOptions + : MoneyOutOptions, + [values?.applyIfTransactionType], + ); + + // Handles the select item change. + const handleItemChange = useCallback( + (item: any) => { + setFieldValue('assignCategory', item.value); + setFieldValue('assignAccountId', ''); + }, + [setFieldValue], + ); + + return ( + Required} + style={{ maxWidth: 300 }} + fastField + > + + + ); +} + +function RuleAssignCategoryAccountField() { + const { values } = useFormikContext(); + const { accounts } = useRuleFormDialogBoot(); + + const accountRoot = useMemo( + () => getAccountRootFromMoneyCategory(values.assignCategory), + [values.assignCategory], + ); + + return ( + Required} + style={{ maxWidth: 300 }} + fastField + shouldUpdateDeps={{ accountRoot }} + > + + + ); +} diff --git a/packages/webapp/src/containers/Banking/Rules/RuleFormDialog/_utils.ts b/packages/webapp/src/containers/Banking/Rules/RuleFormDialog/_utils.ts index 696464b87..db77154e9 100644 --- a/packages/webapp/src/containers/Banking/Rules/RuleFormDialog/_utils.ts +++ b/packages/webapp/src/containers/Banking/Rules/RuleFormDialog/_utils.ts @@ -1,8 +1,11 @@ +import { camelCase, get, upperFirst } from 'lodash'; +import { MoneyCategoryPerCreditAccountRootType } from '@/constants/cashflowOptions'; + export const initialValues = { name: '', order: 0, applyIfAccountId: '', - applyIfTransactionType: '', + applyIfTransactionType: 'deposit', conditionsType: 'and', conditions: [ { @@ -47,3 +50,9 @@ export const FieldCondition = [ export const AssignTransactionTypeOptions = [ { value: 'expense', text: 'Expense' }, ]; + +export const getAccountRootFromMoneyCategory = (category: string): string[] => { + const _category = upperFirst(camelCase(category)); + + return get(MoneyCategoryPerCreditAccountRootType, _category) || []; +};