diff --git a/packages/server/src/database/migrations/20240228183404_create_uncateogrized_cashflow_transactions_table.js b/packages/server/src/database/migrations/20240228183404_create_uncateogrized_cashflow_transactions_table.js index 74aca99ce..29a596772 100644 --- a/packages/server/src/database/migrations/20240228183404_create_uncateogrized_cashflow_transactions_table.js +++ b/packages/server/src/database/migrations/20240228183404_create_uncateogrized_cashflow_transactions_table.js @@ -7,6 +7,7 @@ exports.up = function (knex) { table.decimal('amount'); table.string('currency_code'); table.string('reference_no').index(); + table.string('payee'); table .integer('account_id') .unsigned() diff --git a/packages/server/src/database/migrations/20240304153926_add_uncategorized_transactions_column_to_accounts_table.js b/packages/server/src/database/migrations/20240304153926_add_uncategorized_transactions_column_to_accounts_table.js new file mode 100644 index 000000000..151f5a28b --- /dev/null +++ b/packages/server/src/database/migrations/20240304153926_add_uncategorized_transactions_column_to_accounts_table.js @@ -0,0 +1,7 @@ +exports.up = function (knex) { + return knex.schema.table('accounts', (table) => { + table.integer('uncategorized_transactions').defaultTo(0); + }); +}; + +exports.down = function (knex) {}; diff --git a/packages/server/src/interfaces/CashFlow.ts b/packages/server/src/interfaces/CashFlow.ts index 2f6bf1adf..aab3bb766 100644 --- a/packages/server/src/interfaces/CashFlow.ts +++ b/packages/server/src/interfaces/CashFlow.ts @@ -264,6 +264,7 @@ export interface CreateUncategorizedTransactionDTO { accountId: number; amount: number; currencyCode: string; + payee?: string; description?: string; referenceNo?: string | null; plaidTransactionId?: string | null; diff --git a/packages/server/src/interfaces/Plaid.ts b/packages/server/src/interfaces/Plaid.ts index 88a0ebb3c..a8ad469df 100644 --- a/packages/server/src/interfaces/Plaid.ts +++ b/packages/server/src/interfaces/Plaid.ts @@ -38,7 +38,7 @@ export interface PlaidTransaction { iso_currency_code: string; transaction_id: string; transaction_type: string; - payment_meta: { reference_number: string | null }; + payment_meta: { reference_number: string | null; payee: string | null }; } export interface PlaidFetchedTransactionsUpdates { diff --git a/packages/server/src/models/Account.ts b/packages/server/src/models/Account.ts index 9d4fb053e..c46f9e77b 100644 --- a/packages/server/src/models/Account.ts +++ b/packages/server/src/models/Account.ts @@ -196,6 +196,7 @@ export default class Account extends mixin(TenantModel, [ const Expense = require('models/Expense'); const ExpenseEntry = require('models/ExpenseCategory'); const ItemEntry = require('models/ItemEntry'); + const UncategorizedTransaction = require('models/UncategorizedCashflowTransaction'); return { /** @@ -305,6 +306,21 @@ export default class Account extends mixin(TenantModel, [ to: 'items_entries.sellAccountId', }, }, + + /** + * Associated uncategorized transactions. + */ + uncategorizedTransactions: { + relation: Model.HasManyRelation, + modelClass: UncategorizedTransaction.default, + join: { + from: 'accounts.id', + to: 'uncategorized_cashflow_transactions.accountId', + }, + filter: (query) => { + query.filter('categorized', false); + }, + }, }; } diff --git a/packages/server/src/models/UncategorizedCashflowTransaction.ts b/packages/server/src/models/UncategorizedCashflowTransaction.ts index bb6798504..d8f3db543 100644 --- a/packages/server/src/models/UncategorizedCashflowTransaction.ts +++ b/packages/server/src/models/UncategorizedCashflowTransaction.ts @@ -1,6 +1,7 @@ /* eslint-disable global-require */ import TenantModel from 'models/TenantModel'; import { Model } from 'objection'; +import Account from './Account'; export default class UncategorizedCashflowTransaction extends TenantModel { amount: number; @@ -80,4 +81,29 @@ export default class UncategorizedCashflowTransaction extends TenantModel { }, }; } + + /** + * + * @param queryContext + */ + public async $afterInsert(queryContext) { + await super.$afterInsert(queryContext); + + // Increments the uncategorized transactions count of the associated account. + await Account.query(queryContext.transaction) + .findById(this.accountId) + .increment('uncategorized_transactions', 1); + } + + /** + * + * @param queryContext + */ + public async $afterDelete(queryContext) { + await super.$afterDelete(queryContext); + + await Account.query() + .findById(this.accountId) + .decrement('uncategorized_transactions', 1); + } } diff --git a/packages/server/src/services/Banking/Plaid/PlaidSyncDB.ts b/packages/server/src/services/Banking/Plaid/PlaidSyncDB.ts index 456624493..530ecec2d 100644 --- a/packages/server/src/services/Banking/Plaid/PlaidSyncDB.ts +++ b/packages/server/src/services/Banking/Plaid/PlaidSyncDB.ts @@ -90,7 +90,7 @@ export class PlaidSyncDb { tenantId, uncategoriedDTO ), - { concurrency: CONCURRENCY_ASYNC } + { concurrency: 1 } ); } diff --git a/packages/server/src/services/Banking/Plaid/utils.ts b/packages/server/src/services/Banking/Plaid/utils.ts index 03bf2b50c..de582e852 100644 --- a/packages/server/src/services/Banking/Plaid/utils.ts +++ b/packages/server/src/services/Banking/Plaid/utils.ts @@ -44,8 +44,9 @@ export const transformPlaidTrxsToCashflowCreate = R.curry( ): CreateUncategorizedTransactionDTO => { return { date: plaidTranasction.date, - description: plaidTranasction.name, amount: plaidTranasction.amount, + description: plaidTranasction.name, + payee: plaidTranasction.payment_meta?.payee, currencyCode: plaidTranasction.iso_currency_code, accountId: cashflowAccountId, referenceNo: plaidTranasction.payment_meta?.reference_number, diff --git a/packages/server/src/services/Cashflow/CreateUncategorizedTransaction.ts b/packages/server/src/services/Cashflow/CreateUncategorizedTransaction.ts index 9f40323d9..ccb2aca25 100644 --- a/packages/server/src/services/Cashflow/CreateUncategorizedTransaction.ts +++ b/packages/server/src/services/Cashflow/CreateUncategorizedTransaction.ts @@ -1,6 +1,6 @@ import { Inject, Service } from 'typedi'; import HasTenancyService from '../Tenancy/TenancyService'; -import UnitOfWork from '../UnitOfWork'; +import UnitOfWork, { IsolationLevel } from '../UnitOfWork'; import { Knex } from 'knex'; import { CreateUncategorizedTransactionDTO } from '@/interfaces'; @@ -21,15 +21,20 @@ export class CreateUncategorizedTransaction { tenantId: number, createDTO: CreateUncategorizedTransactionDTO ) { - const { UncategorizedCashflowTransaction } = this.tenancy.models(tenantId); + const { UncategorizedCashflowTransaction, Account } = + this.tenancy.models(tenantId); - return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => { - const transaction = await UncategorizedCashflowTransaction.query( - trx - ).insertAndFetch({ - ...createDTO, - }); - return transaction; - }); + return this.uow.withTransaction( + tenantId, + async (trx: Knex.Transaction) => { + const transaction = await UncategorizedCashflowTransaction.query( + trx + ).insertAndFetch({ + ...createDTO, + }); + + return transaction; + }, + ); } } diff --git a/packages/server/src/services/Cashflow/UncategorizedTransactionTransformer.ts b/packages/server/src/services/Cashflow/UncategorizedTransactionTransformer.ts index 0162c8e0a..bf0a6a1cf 100644 --- a/packages/server/src/services/Cashflow/UncategorizedTransactionTransformer.ts +++ b/packages/server/src/services/Cashflow/UncategorizedTransactionTransformer.ts @@ -29,9 +29,12 @@ export class UncategorizedTransactionTransformer extends Transformer { * @returns {string} */ protected formattetDepositAmount(transaction) { - return formatNumber(transaction.deposit, { - currencyCode: transaction.currencyCode, - }); + if (transaction.isDepositTransaction) { + return formatNumber(transaction.deposit, { + currencyCode: transaction.currencyCode, + }); + } + return ''; } /** @@ -40,8 +43,11 @@ export class UncategorizedTransactionTransformer extends Transformer { * @returns {string} */ protected formattedWithdrawalAmount(transaction) { - return formatNumber(transaction.withdrawal, { - currencyCode: transaction.currencyCode, - }); + if (transaction.isWithdrawalTransaction) { + return formatNumber(transaction.withdrawal, { + currencyCode: transaction.currencyCode, + }); + } + return ''; } } diff --git a/packages/webapp/src/containers/CashFlow/AccountTransactions/AccountTransactionsDetailsBar.tsx b/packages/webapp/src/containers/CashFlow/AccountTransactions/AccountTransactionsDetailsBar.tsx index 7f4d64921..90e10eae0 100644 --- a/packages/webapp/src/containers/CashFlow/AccountTransactions/AccountTransactionsDetailsBar.tsx +++ b/packages/webapp/src/containers/CashFlow/AccountTransactions/AccountTransactionsDetailsBar.tsx @@ -84,6 +84,18 @@ function AccountBankBalanceItem() { ); } +function AccountNumberItem() { + const { currentAccount } = useAccountTransactionsContext(); + + if (!currentAccount.account_mask) return null; + + return ( + + Account Number: xxx{currentAccount.account_mask} + + ); +} + function AccountTransactionsDetailsBarSkeleton() { return ( @@ -101,6 +113,7 @@ function AccountTransactionsDetailsContent() { return ( + diff --git a/packages/webapp/src/containers/CashFlow/AccountTransactions/AccountTransactionsFilterTabs.tsx b/packages/webapp/src/containers/CashFlow/AccountTransactions/AccountTransactionsFilterTabs.tsx index aede21571..c2d40059d 100644 --- a/packages/webapp/src/containers/CashFlow/AccountTransactions/AccountTransactionsFilterTabs.tsx +++ b/packages/webapp/src/containers/CashFlow/AccountTransactions/AccountTransactionsFilterTabs.tsx @@ -8,12 +8,17 @@ const AccountContentTabs = styled(ContentTabs)` `; export function AccountTransactionsFilterTabs() { - const { filterTab, setFilterTab } = useAccountTransactionsContext(); + const { filterTab, setFilterTab, currentAccount } = + useAccountTransactionsContext(); const handleChange = (value) => { setFilterTab(value); }; + const hasUncategorizedTransx = Boolean( + currentAccount.uncategorized_transactions, + ); + return ( - - 20 Uncategorized - Transactions - - } - description={'For Bank Statement'} - /> + {hasUncategorizedTransx && ( + + + {currentAccount.uncategorized_transactions} + {' '} + Uncategorized Transactions + + } + description={'For Bank Statement'} + /> + )}