diff --git a/packages/server/src/api/controllers/Cashflow/NewCashflowTransaction.ts b/packages/server/src/api/controllers/Cashflow/NewCashflowTransaction.ts index a3bd70b4f..fbc510051 100644 --- a/packages/server/src/api/controllers/Cashflow/NewCashflowTransaction.ts +++ b/packages/server/src/api/controllers/Cashflow/NewCashflowTransaction.ts @@ -23,7 +23,7 @@ export default class NewCashflowTransactionController extends BaseController { const router = Router(); router.get( - '/transactions/uncategorized', + '/transactions/:id/uncategorized', this.asyncMiddleware(this.getUncategorizedCashflowTransactions), this.catchServiceErrors ); @@ -237,10 +237,12 @@ export default class NewCashflowTransactionController extends BaseController { next: NextFunction ) => { const { tenantId } = req; + const { id: accountId } = req.params; try { const data = await this.cashflowApplication.getUncategorizedTransactions( - tenantId + tenantId, + accountId ); return res.status(200).send(data); 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 aab649b63..74aca99ce 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 @@ -16,6 +16,7 @@ exports.up = function (knex) { table.string('categorize_ref_type'); table.integer('categorize_ref_id').unsigned(); table.boolean('categorized').defaultTo(false); + table.string('plaid_transaction_id'); table.timestamps(); } ); diff --git a/packages/server/src/interfaces/CashFlow.ts b/packages/server/src/interfaces/CashFlow.ts index 8bcc1eafc..2f6bf1adf 100644 --- a/packages/server/src/interfaces/CashFlow.ts +++ b/packages/server/src/interfaces/CashFlow.ts @@ -257,3 +257,14 @@ export interface IUncategorizedCashflowTransaction { categorizeRefId: number; categorized: boolean; } + + +export interface CreateUncategorizedTransactionDTO { + date: Date | string; + accountId: number; + amount: number; + currencyCode: 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 9b19e37a9..88a0ebb3c 100644 --- a/packages/server/src/interfaces/Plaid.ts +++ b/packages/server/src/interfaces/Plaid.ts @@ -38,6 +38,7 @@ export interface PlaidTransaction { iso_currency_code: string; transaction_id: string; transaction_type: string; + payment_meta: { reference_number: string | null }; } export interface PlaidFetchedTransactionsUpdates { diff --git a/packages/server/src/services/Banking/Plaid/PlaidSyncDB.ts b/packages/server/src/services/Banking/Plaid/PlaidSyncDB.ts index a9f9d14fd..456624493 100644 --- a/packages/server/src/services/Banking/Plaid/PlaidSyncDB.ts +++ b/packages/server/src/services/Banking/Plaid/PlaidSyncDB.ts @@ -11,6 +11,7 @@ import { import NewCashflowTransactionService from '@/services/Cashflow/NewCashflowTransactionService'; import { DeleteCashflowTransaction } from '@/services/Cashflow/DeleteCashflowTransactionService'; import HasTenancyService from '@/services/Tenancy/TenancyService'; +import { CashflowApplication } from '@/services/Cashflow/CashflowApplication'; const CONCURRENCY_ASYNC = 10; @@ -22,6 +23,9 @@ export class PlaidSyncDb { @Inject() private createAccountService: CreateAccount; + @Inject() + private cashflowApp: CashflowApplication; + @Inject() private createCashflowTransactionService: NewCashflowTransactionService; @@ -75,15 +79,16 @@ export class PlaidSyncDb { cashflowAccount.id, openingEquityBalance.id ); - const accountsCashflowDTO = R.map(transformTransaction)(plaidTranasctions); + const uncategorizedTransDTOs = + R.map(transformTransaction)(plaidTranasctions); // Creating account transaction queue. await bluebird.map( - accountsCashflowDTO, - (cashflowDTO) => - this.createCashflowTransactionService.newCashflowTransaction( + uncategorizedTransDTOs, + (uncategoriedDTO) => + this.cashflowApp.createUncategorizedTransaction( tenantId, - cashflowDTO + uncategoriedDTO ), { concurrency: CONCURRENCY_ASYNC } ); diff --git a/packages/server/src/services/Banking/Plaid/utils.ts b/packages/server/src/services/Banking/Plaid/utils.ts index 1fe18bee7..03bf2b50c 100644 --- a/packages/server/src/services/Banking/Plaid/utils.ts +++ b/packages/server/src/services/Banking/Plaid/utils.ts @@ -1,7 +1,9 @@ import * as R from 'ramda'; import { + CreateUncategorizedTransactionDTO, IAccountCreateDTO, ICashflowNewCommandDTO, + IUncategorizedCashflowTransaction, PlaidAccount, PlaidTransaction, } from '@/interfaces'; @@ -32,30 +34,22 @@ export const transformPlaidAccountToCreateAccount = ( * @param {number} cashflowAccountId - Cashflow account ID. * @param {number} creditAccountId - Credit account ID. * @param {PlaidTransaction} plaidTranasction - Plaid transaction. - * @returns {ICashflowNewCommandDTO} + * @returns {CreateUncategorizedTransactionDTO} */ export const transformPlaidTrxsToCashflowCreate = R.curry( ( cashflowAccountId: number, creditAccountId: number, plaidTranasction: PlaidTransaction - ): ICashflowNewCommandDTO => { + ): CreateUncategorizedTransactionDTO => { return { date: plaidTranasction.date, - - transactionType: 'OwnerContribution', description: plaidTranasction.name, - amount: plaidTranasction.amount, - exchangeRate: 1, currencyCode: plaidTranasction.iso_currency_code, - creditAccountId, - cashflowAccountId, - - // transactionNumber: string; - // referenceNo: string; + accountId: cashflowAccountId, + referenceNo: plaidTranasction.payment_meta?.reference_number, plaidTransactionId: plaidTranasction.transaction_id, - publish: true, }; } ); diff --git a/packages/server/src/services/Cashflow/CashflowApplication.ts b/packages/server/src/services/Cashflow/CashflowApplication.ts index ee7548fee..7828ae489 100644 --- a/packages/server/src/services/Cashflow/CashflowApplication.ts +++ b/packages/server/src/services/Cashflow/CashflowApplication.ts @@ -4,10 +4,13 @@ import { UncategorizeCashflowTransaction } from './UncategorizeCashflowTransacti import { CategorizeCashflowTransaction } from './CategorizeCashflowTransaction'; import { CategorizeTransactionAsExpenseDTO, + CreateUncategorizedTransactionDTO, ICategorizeCashflowTransactioDTO, + IUncategorizedCashflowTransaction, } from '@/interfaces'; import { CategorizeTransactionAsExpense } from './CategorizeTransactionAsExpense'; import { GetUncategorizedTransactions } from './GetUncategorizedTransactions'; +import { CreateUncategorizedTransaction } from './CreateUncategorizedTransaction'; @Service() export class CashflowApplication { @@ -26,6 +29,9 @@ export class CashflowApplication { @Inject() private getUncategorizedTransactionsService: GetUncategorizedTransactions; + @Inject() + private createUncategorizedTransactionService: CreateUncategorizedTransaction; + /** * Deletes the given cashflow transaction. * @param {number} tenantId @@ -39,6 +45,22 @@ export class CashflowApplication { ); } + /** + * Creates a new uncategorized cash transaction. + * @param {number} tenantId + * @param {CreateUncategorizedTransactionDTO} createUncategorizedTransactionDTO + * @returns {IUncategorizedCashflowTransaction} + */ + public createUncategorizedTransaction( + tenantId: number, + createUncategorizedTransactionDTO: CreateUncategorizedTransactionDTO + ) { + return this.createUncategorizedTransactionService.create( + tenantId, + createUncategorizedTransactionDTO + ); + } + /** * Uncategorize the given cashflow transaction. * @param {number} tenantId @@ -98,7 +120,10 @@ export class CashflowApplication { * @param {number} tenantId * @returns {} */ - public getUncategorizedTransactions(tenantId: number) { - return this.getUncategorizedTransactionsService.getTransactions(tenantId); + public getUncategorizedTransactions(tenantId: number, accountId: number) { + return this.getUncategorizedTransactionsService.getTransactions( + tenantId, + accountId + ); } } diff --git a/packages/server/src/services/Cashflow/CreateUncategorizedTransaction.ts b/packages/server/src/services/Cashflow/CreateUncategorizedTransaction.ts new file mode 100644 index 000000000..9f40323d9 --- /dev/null +++ b/packages/server/src/services/Cashflow/CreateUncategorizedTransaction.ts @@ -0,0 +1,35 @@ +import { Inject, Service } from 'typedi'; +import HasTenancyService from '../Tenancy/TenancyService'; +import UnitOfWork from '../UnitOfWork'; +import { Knex } from 'knex'; +import { CreateUncategorizedTransactionDTO } from '@/interfaces'; + +@Service() +export class CreateUncategorizedTransaction { + @Inject() + private tenancy: HasTenancyService; + + @Inject() + private uow: UnitOfWork; + + /** + * Creates an uncategorized cashflow transaction. + * @param {number} tenantId + * @param {CreateUncategorizedTransactionDTO} createDTO + */ + public create( + tenantId: number, + createDTO: CreateUncategorizedTransactionDTO + ) { + const { UncategorizedCashflowTransaction } = this.tenancy.models(tenantId); + + 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/GetUncategorizedTransactions.ts b/packages/server/src/services/Cashflow/GetUncategorizedTransactions.ts index 955a5538a..9b273920f 100644 --- a/packages/server/src/services/Cashflow/GetUncategorizedTransactions.ts +++ b/packages/server/src/services/Cashflow/GetUncategorizedTransactions.ts @@ -13,13 +13,15 @@ export class GetUncategorizedTransactions { /** * Retrieves the uncategorized cashflow transactions. - * @param {number} tenantId + * @param {number} tenantId - Tenant id. + * @param {number} accountId - Account Id. */ - public async getTransactions(tenantId: number) { + public async getTransactions(tenantId: number, accountId: number) { const { UncategorizedCashflowTransaction } = this.tenancy.models(tenantId); const { results, pagination } = await UncategorizedCashflowTransaction.query() + .where('accountId', accountId) .where('categorized', false) .withGraphFetched('account') .pagination(0, 10); diff --git a/packages/server/src/services/Cashflow/UncategorizedTransactionTransformer.ts b/packages/server/src/services/Cashflow/UncategorizedTransactionTransformer.ts index 2a4ceb4a4..0162c8e0a 100644 --- a/packages/server/src/services/Cashflow/UncategorizedTransactionTransformer.ts +++ b/packages/server/src/services/Cashflow/UncategorizedTransactionTransformer.ts @@ -7,9 +7,22 @@ export class UncategorizedTransactionTransformer extends Transformer { * @returns {string[]} */ public includeAttributes = (): string[] => { - return ['formattetDepositAmount', 'formattedWithdrawalAmount']; + return [ + 'formattedDate', + 'formattetDepositAmount', + 'formattedWithdrawalAmount', + ]; }; + /** + * Formattes the transaction date. + * @param transaction + * @returns {string} + */ + public formattedDate(transaction) { + return this.formatDate(transaction.date); + } + /** * Formatted deposit amount. * @param transaction