mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-20 14:50:32 +00:00
feat(server): sync Plaid transactions to uncategorized transactions
This commit is contained in:
@@ -23,7 +23,7 @@ export default class NewCashflowTransactionController extends BaseController {
|
|||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/transactions/uncategorized',
|
'/transactions/:id/uncategorized',
|
||||||
this.asyncMiddleware(this.getUncategorizedCashflowTransactions),
|
this.asyncMiddleware(this.getUncategorizedCashflowTransactions),
|
||||||
this.catchServiceErrors
|
this.catchServiceErrors
|
||||||
);
|
);
|
||||||
@@ -237,10 +237,12 @@ export default class NewCashflowTransactionController extends BaseController {
|
|||||||
next: NextFunction
|
next: NextFunction
|
||||||
) => {
|
) => {
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
|
const { id: accountId } = req.params;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const data = await this.cashflowApplication.getUncategorizedTransactions(
|
const data = await this.cashflowApplication.getUncategorizedTransactions(
|
||||||
tenantId
|
tenantId,
|
||||||
|
accountId
|
||||||
);
|
);
|
||||||
|
|
||||||
return res.status(200).send(data);
|
return res.status(200).send(data);
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ exports.up = function (knex) {
|
|||||||
table.string('categorize_ref_type');
|
table.string('categorize_ref_type');
|
||||||
table.integer('categorize_ref_id').unsigned();
|
table.integer('categorize_ref_id').unsigned();
|
||||||
table.boolean('categorized').defaultTo(false);
|
table.boolean('categorized').defaultTo(false);
|
||||||
|
table.string('plaid_transaction_id');
|
||||||
table.timestamps();
|
table.timestamps();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -257,3 +257,14 @@ export interface IUncategorizedCashflowTransaction {
|
|||||||
categorizeRefId: number;
|
categorizeRefId: number;
|
||||||
categorized: boolean;
|
categorized: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface CreateUncategorizedTransactionDTO {
|
||||||
|
date: Date | string;
|
||||||
|
accountId: number;
|
||||||
|
amount: number;
|
||||||
|
currencyCode: string;
|
||||||
|
description?: string;
|
||||||
|
referenceNo?: string | null;
|
||||||
|
plaidTransactionId?: string | null;
|
||||||
|
}
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ export interface PlaidTransaction {
|
|||||||
iso_currency_code: string;
|
iso_currency_code: string;
|
||||||
transaction_id: string;
|
transaction_id: string;
|
||||||
transaction_type: string;
|
transaction_type: string;
|
||||||
|
payment_meta: { reference_number: string | null };
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PlaidFetchedTransactionsUpdates {
|
export interface PlaidFetchedTransactionsUpdates {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
import NewCashflowTransactionService from '@/services/Cashflow/NewCashflowTransactionService';
|
import NewCashflowTransactionService from '@/services/Cashflow/NewCashflowTransactionService';
|
||||||
import { DeleteCashflowTransaction } from '@/services/Cashflow/DeleteCashflowTransactionService';
|
import { DeleteCashflowTransaction } from '@/services/Cashflow/DeleteCashflowTransactionService';
|
||||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
|
import { CashflowApplication } from '@/services/Cashflow/CashflowApplication';
|
||||||
|
|
||||||
const CONCURRENCY_ASYNC = 10;
|
const CONCURRENCY_ASYNC = 10;
|
||||||
|
|
||||||
@@ -22,6 +23,9 @@ export class PlaidSyncDb {
|
|||||||
@Inject()
|
@Inject()
|
||||||
private createAccountService: CreateAccount;
|
private createAccountService: CreateAccount;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private cashflowApp: CashflowApplication;
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
private createCashflowTransactionService: NewCashflowTransactionService;
|
private createCashflowTransactionService: NewCashflowTransactionService;
|
||||||
|
|
||||||
@@ -75,15 +79,16 @@ export class PlaidSyncDb {
|
|||||||
cashflowAccount.id,
|
cashflowAccount.id,
|
||||||
openingEquityBalance.id
|
openingEquityBalance.id
|
||||||
);
|
);
|
||||||
const accountsCashflowDTO = R.map(transformTransaction)(plaidTranasctions);
|
const uncategorizedTransDTOs =
|
||||||
|
R.map(transformTransaction)(plaidTranasctions);
|
||||||
|
|
||||||
// Creating account transaction queue.
|
// Creating account transaction queue.
|
||||||
await bluebird.map(
|
await bluebird.map(
|
||||||
accountsCashflowDTO,
|
uncategorizedTransDTOs,
|
||||||
(cashflowDTO) =>
|
(uncategoriedDTO) =>
|
||||||
this.createCashflowTransactionService.newCashflowTransaction(
|
this.cashflowApp.createUncategorizedTransaction(
|
||||||
tenantId,
|
tenantId,
|
||||||
cashflowDTO
|
uncategoriedDTO
|
||||||
),
|
),
|
||||||
{ concurrency: CONCURRENCY_ASYNC }
|
{ concurrency: CONCURRENCY_ASYNC }
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import * as R from 'ramda';
|
import * as R from 'ramda';
|
||||||
import {
|
import {
|
||||||
|
CreateUncategorizedTransactionDTO,
|
||||||
IAccountCreateDTO,
|
IAccountCreateDTO,
|
||||||
ICashflowNewCommandDTO,
|
ICashflowNewCommandDTO,
|
||||||
|
IUncategorizedCashflowTransaction,
|
||||||
PlaidAccount,
|
PlaidAccount,
|
||||||
PlaidTransaction,
|
PlaidTransaction,
|
||||||
} from '@/interfaces';
|
} from '@/interfaces';
|
||||||
@@ -32,30 +34,22 @@ export const transformPlaidAccountToCreateAccount = (
|
|||||||
* @param {number} cashflowAccountId - Cashflow account ID.
|
* @param {number} cashflowAccountId - Cashflow account ID.
|
||||||
* @param {number} creditAccountId - Credit account ID.
|
* @param {number} creditAccountId - Credit account ID.
|
||||||
* @param {PlaidTransaction} plaidTranasction - Plaid transaction.
|
* @param {PlaidTransaction} plaidTranasction - Plaid transaction.
|
||||||
* @returns {ICashflowNewCommandDTO}
|
* @returns {CreateUncategorizedTransactionDTO}
|
||||||
*/
|
*/
|
||||||
export const transformPlaidTrxsToCashflowCreate = R.curry(
|
export const transformPlaidTrxsToCashflowCreate = R.curry(
|
||||||
(
|
(
|
||||||
cashflowAccountId: number,
|
cashflowAccountId: number,
|
||||||
creditAccountId: number,
|
creditAccountId: number,
|
||||||
plaidTranasction: PlaidTransaction
|
plaidTranasction: PlaidTransaction
|
||||||
): ICashflowNewCommandDTO => {
|
): CreateUncategorizedTransactionDTO => {
|
||||||
return {
|
return {
|
||||||
date: plaidTranasction.date,
|
date: plaidTranasction.date,
|
||||||
|
|
||||||
transactionType: 'OwnerContribution',
|
|
||||||
description: plaidTranasction.name,
|
description: plaidTranasction.name,
|
||||||
|
|
||||||
amount: plaidTranasction.amount,
|
amount: plaidTranasction.amount,
|
||||||
exchangeRate: 1,
|
|
||||||
currencyCode: plaidTranasction.iso_currency_code,
|
currencyCode: plaidTranasction.iso_currency_code,
|
||||||
creditAccountId,
|
accountId: cashflowAccountId,
|
||||||
cashflowAccountId,
|
referenceNo: plaidTranasction.payment_meta?.reference_number,
|
||||||
|
|
||||||
// transactionNumber: string;
|
|
||||||
// referenceNo: string;
|
|
||||||
plaidTransactionId: plaidTranasction.transaction_id,
|
plaidTransactionId: plaidTranasction.transaction_id,
|
||||||
publish: true,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,10 +4,13 @@ import { UncategorizeCashflowTransaction } from './UncategorizeCashflowTransacti
|
|||||||
import { CategorizeCashflowTransaction } from './CategorizeCashflowTransaction';
|
import { CategorizeCashflowTransaction } from './CategorizeCashflowTransaction';
|
||||||
import {
|
import {
|
||||||
CategorizeTransactionAsExpenseDTO,
|
CategorizeTransactionAsExpenseDTO,
|
||||||
|
CreateUncategorizedTransactionDTO,
|
||||||
ICategorizeCashflowTransactioDTO,
|
ICategorizeCashflowTransactioDTO,
|
||||||
|
IUncategorizedCashflowTransaction,
|
||||||
} from '@/interfaces';
|
} from '@/interfaces';
|
||||||
import { CategorizeTransactionAsExpense } from './CategorizeTransactionAsExpense';
|
import { CategorizeTransactionAsExpense } from './CategorizeTransactionAsExpense';
|
||||||
import { GetUncategorizedTransactions } from './GetUncategorizedTransactions';
|
import { GetUncategorizedTransactions } from './GetUncategorizedTransactions';
|
||||||
|
import { CreateUncategorizedTransaction } from './CreateUncategorizedTransaction';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class CashflowApplication {
|
export class CashflowApplication {
|
||||||
@@ -26,6 +29,9 @@ export class CashflowApplication {
|
|||||||
@Inject()
|
@Inject()
|
||||||
private getUncategorizedTransactionsService: GetUncategorizedTransactions;
|
private getUncategorizedTransactionsService: GetUncategorizedTransactions;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private createUncategorizedTransactionService: CreateUncategorizedTransaction;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes the given cashflow transaction.
|
* Deletes the given cashflow transaction.
|
||||||
* @param {number} tenantId
|
* @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.
|
* Uncategorize the given cashflow transaction.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
@@ -98,7 +120,10 @@ export class CashflowApplication {
|
|||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
* @returns {}
|
* @returns {}
|
||||||
*/
|
*/
|
||||||
public getUncategorizedTransactions(tenantId: number) {
|
public getUncategorizedTransactions(tenantId: number, accountId: number) {
|
||||||
return this.getUncategorizedTransactionsService.getTransactions(tenantId);
|
return this.getUncategorizedTransactionsService.getTransactions(
|
||||||
|
tenantId,
|
||||||
|
accountId
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,13 +13,15 @@ export class GetUncategorizedTransactions {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the uncategorized cashflow transactions.
|
* 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 { UncategorizedCashflowTransaction } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
const { results, pagination } =
|
const { results, pagination } =
|
||||||
await UncategorizedCashflowTransaction.query()
|
await UncategorizedCashflowTransaction.query()
|
||||||
|
.where('accountId', accountId)
|
||||||
.where('categorized', false)
|
.where('categorized', false)
|
||||||
.withGraphFetched('account')
|
.withGraphFetched('account')
|
||||||
.pagination(0, 10);
|
.pagination(0, 10);
|
||||||
|
|||||||
@@ -7,9 +7,22 @@ export class UncategorizedTransactionTransformer extends Transformer {
|
|||||||
* @returns {string[]}
|
* @returns {string[]}
|
||||||
*/
|
*/
|
||||||
public includeAttributes = (): 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.
|
* Formatted deposit amount.
|
||||||
* @param transaction
|
* @param transaction
|
||||||
|
|||||||
Reference in New Issue
Block a user