mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-20 14:50:32 +00:00
refactor(nestjs): plaid banking syncing
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
import { BullModule } from '@nestjs/bullmq';
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { PlaidUpdateTransactionsOnItemCreatedSubscriber } from './subscribers/PlaidUpdateTransactionsOnItemCreatedSubscriber';
|
import { PlaidUpdateTransactionsOnItemCreatedSubscriber } from './subscribers/PlaidUpdateTransactionsOnItemCreatedSubscriber';
|
||||||
import { PlaidUpdateTransactions } from './command/PlaidUpdateTransactions';
|
import { PlaidUpdateTransactions } from './command/PlaidUpdateTransactions';
|
||||||
@@ -16,6 +17,10 @@ import { TenancyContext } from '../Tenancy/TenancyContext.service';
|
|||||||
import { InjectSystemModel } from '../System/SystemModels/SystemModels.module';
|
import { InjectSystemModel } from '../System/SystemModels/SystemModels.module';
|
||||||
import { SystemPlaidItem } from './models/SystemPlaidItem';
|
import { SystemPlaidItem } from './models/SystemPlaidItem';
|
||||||
import { BankingPlaidController } from './BankingPlaid.controller';
|
import { BankingPlaidController } from './BankingPlaid.controller';
|
||||||
|
import { BankingPlaidWebhooksController } from './BankingPlaidWebhooks.controller';
|
||||||
|
import { SetupPlaidItemTenantService } from './command/SetupPlaidItemTenant.service';
|
||||||
|
import { UpdateBankingPlaidTransitionsQueueJob } from './types/BankingPlaid.types';
|
||||||
|
import { PlaidFetchTransactionsProcessor } from './jobs/PlaidFetchTransactionsJob';
|
||||||
|
|
||||||
const models = [RegisterTenancyModel(PlaidItem)];
|
const models = [RegisterTenancyModel(PlaidItem)];
|
||||||
|
|
||||||
@@ -25,6 +30,7 @@ const models = [RegisterTenancyModel(PlaidItem)];
|
|||||||
AccountsModule,
|
AccountsModule,
|
||||||
BankingCategorizeModule,
|
BankingCategorizeModule,
|
||||||
BankingTransactionsModule,
|
BankingTransactionsModule,
|
||||||
|
BullModule.registerQueue({ name: UpdateBankingPlaidTransitionsQueueJob }),
|
||||||
...models,
|
...models,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
@@ -35,10 +41,12 @@ const models = [RegisterTenancyModel(PlaidItem)];
|
|||||||
PlaidWebooks,
|
PlaidWebooks,
|
||||||
PlaidLinkTokenService,
|
PlaidLinkTokenService,
|
||||||
PlaidApplication,
|
PlaidApplication,
|
||||||
PlaidUpdateTransactionsOnItemCreatedSubscriber,
|
SetupPlaidItemTenantService,
|
||||||
TenancyContext,
|
TenancyContext,
|
||||||
|
PlaidFetchTransactionsProcessor,
|
||||||
|
PlaidUpdateTransactionsOnItemCreatedSubscriber,
|
||||||
],
|
],
|
||||||
exports: [...models],
|
exports: [...models],
|
||||||
controllers: [BankingPlaidController]
|
controllers: [BankingPlaidController, BankingPlaidWebhooksController],
|
||||||
})
|
})
|
||||||
export class BankingPlaidModule {}
|
export class BankingPlaidModule {}
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { Body, Controller, Post } from '@nestjs/common';
|
||||||
|
import { PlaidWebhookDto } from './dtos/PlaidItem.dto';
|
||||||
|
import { ApiOperation } from '@nestjs/swagger';
|
||||||
|
import { PlaidApplication } from './PlaidApplication';
|
||||||
|
import { PublicRoute } from '../Auth/guards/jwt.guard';
|
||||||
|
import { SetupPlaidItemTenantService } from './command/SetupPlaidItemTenant.service';
|
||||||
|
|
||||||
|
@Controller('banking/plaid')
|
||||||
|
@PublicRoute()
|
||||||
|
export class BankingPlaidWebhooksController {
|
||||||
|
constructor(
|
||||||
|
private readonly plaidApplication: PlaidApplication,
|
||||||
|
private readonly setupPlaidItemTenantService: SetupPlaidItemTenantService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@Post('webhooks')
|
||||||
|
@ApiOperation({ summary: 'Listen to Plaid webhooks' })
|
||||||
|
webhooks(@Body() { itemId, webhookType, webhookCode }: PlaidWebhookDto) {
|
||||||
|
return this.setupPlaidItemTenantService.setupPlaidTenant(
|
||||||
|
itemId,
|
||||||
|
() => {
|
||||||
|
return this.plaidApplication.webhooks(
|
||||||
|
itemId,
|
||||||
|
webhookType,
|
||||||
|
webhookCode,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,12 @@
|
|||||||
|
import { ClsService } from 'nestjs-cls';
|
||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { PlaidLinkTokenService } from './queries/GetPlaidLinkToken.service';
|
import { PlaidLinkTokenService } from './queries/GetPlaidLinkToken.service';
|
||||||
import { PlaidItemService } from './command/PlaidItem';
|
import { PlaidItemService } from './command/PlaidItem';
|
||||||
import { PlaidWebooks } from './command/PlaidWebhooks';
|
import { PlaidWebooks } from './command/PlaidWebhooks';
|
||||||
import { Injectable } from '@nestjs/common';
|
|
||||||
import { PlaidItemDTO } from './types/BankingPlaid.types';
|
|
||||||
import { PlaidItemDto } from './dtos/PlaidItem.dto';
|
import { PlaidItemDto } from './dtos/PlaidItem.dto';
|
||||||
|
import { SystemPlaidItem } from './models/SystemPlaidItem';
|
||||||
|
import { TenantModel } from '../System/models/TenantModel';
|
||||||
|
import { SystemUser } from '../System/models/SystemUser';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PlaidApplication {
|
export class PlaidApplication {
|
||||||
@@ -11,6 +14,16 @@ export class PlaidApplication {
|
|||||||
private readonly getLinkTokenService: PlaidLinkTokenService,
|
private readonly getLinkTokenService: PlaidLinkTokenService,
|
||||||
private readonly plaidItemService: PlaidItemService,
|
private readonly plaidItemService: PlaidItemService,
|
||||||
private readonly plaidWebhooks: PlaidWebooks,
|
private readonly plaidWebhooks: PlaidWebooks,
|
||||||
|
private readonly clsService: ClsService,
|
||||||
|
|
||||||
|
@Inject(SystemPlaidItem.name)
|
||||||
|
private readonly systemPlaidItemModel: typeof SystemPlaidItem,
|
||||||
|
|
||||||
|
@Inject(TenantModel.name)
|
||||||
|
private readonly tenantModel: typeof TenantModel,
|
||||||
|
|
||||||
|
@Inject(SystemUser.name)
|
||||||
|
private readonly systemUserModel: typeof SystemUser,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -42,10 +55,33 @@ export class PlaidApplication {
|
|||||||
webhookType: string,
|
webhookType: string,
|
||||||
webhookCode: string,
|
webhookCode: string,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
return this.plaidWebhooks.webhooks(
|
return this.plaidWebhooks.webhooks(plaidItemId, webhookType, webhookCode);
|
||||||
plaidItemId,
|
}
|
||||||
webhookType,
|
|
||||||
webhookCode,
|
public async setupPlaidTenant(plaidItemId: string, callback: () => void) {
|
||||||
);
|
const plaidItem = await this.systemPlaidItemModel
|
||||||
|
.query()
|
||||||
|
.findOne({ plaidItemId });
|
||||||
|
|
||||||
|
if (!plaidItem) {
|
||||||
|
throw new Error('Plaid item not found');
|
||||||
|
}
|
||||||
|
const tenant = await this.tenantModel
|
||||||
|
.query()
|
||||||
|
.findOne({ id: plaidItem.tenantId })
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
const user = await this.systemUserModel
|
||||||
|
.query()
|
||||||
|
.findOne({
|
||||||
|
tenantId: tenant.id,
|
||||||
|
})
|
||||||
|
.modify('active')
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
this.clsService.set('organizationId', tenant.organizationId);
|
||||||
|
this.clsService.set('userId', user.id);
|
||||||
|
|
||||||
|
return callback();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
// import { Request, Response, NextFunction } from 'express';
|
|
||||||
// import { SystemPlaidItem, Tenant } from '@/system/models';
|
|
||||||
// import tenantDependencyInjection from '@/api/middleware/TenantDependencyInjection';
|
|
||||||
|
|
||||||
// export const PlaidWebhookTenantBootMiddleware = async (
|
|
||||||
// req: Request,
|
|
||||||
// res: Response,
|
|
||||||
// next: NextFunction
|
|
||||||
// ) => {
|
|
||||||
// const { item_id: plaidItemId } = req.body;
|
|
||||||
// const plaidItem = await SystemPlaidItem.query().findOne({ plaidItemId });
|
|
||||||
|
|
||||||
// const notFoundOrganization = () => {
|
|
||||||
// return res.boom.unauthorized('Organization identication not found.', {
|
|
||||||
// errors: [{ type: 'ORGANIZATION.ID.NOT.FOUND', code: 100 }],
|
|
||||||
// });
|
|
||||||
// };
|
|
||||||
// // In case the given organization not found.
|
|
||||||
// if (!plaidItem) {
|
|
||||||
// return notFoundOrganization();
|
|
||||||
// }
|
|
||||||
// const tenant = await Tenant.query()
|
|
||||||
// .findById(plaidItem.tenantId)
|
|
||||||
// .withGraphFetched('metadata');
|
|
||||||
|
|
||||||
// // When the given organization id not found on the system storage.
|
|
||||||
// if (!tenant) {
|
|
||||||
// return notFoundOrganization();
|
|
||||||
// }
|
|
||||||
// tenantDependencyInjection(req, tenant);
|
|
||||||
// next();
|
|
||||||
// };
|
|
||||||
@@ -17,9 +17,7 @@ export class PlaidItemService {
|
|||||||
private readonly tenancyContext: TenancyContext,
|
private readonly tenancyContext: TenancyContext,
|
||||||
|
|
||||||
@Inject(SystemPlaidItem.name)
|
@Inject(SystemPlaidItem.name)
|
||||||
private readonly systemPlaidItemModel: TenantModelProxy<
|
private readonly systemPlaidItemModel: typeof SystemPlaidItem,
|
||||||
typeof SystemPlaidItem
|
|
||||||
>,
|
|
||||||
|
|
||||||
@Inject(PlaidItem.name)
|
@Inject(PlaidItem.name)
|
||||||
private readonly plaidItemModel: TenantModelProxy<typeof PlaidItem>,
|
private readonly plaidItemModel: TenantModelProxy<typeof PlaidItem>,
|
||||||
@@ -55,7 +53,7 @@ export class PlaidItemService {
|
|||||||
plaidInstitutionId: institutionId,
|
plaidInstitutionId: institutionId,
|
||||||
});
|
});
|
||||||
// Stores the Plaid item id on system scope.
|
// Stores the Plaid item id on system scope.
|
||||||
await this.systemPlaidItemModel().query().insert({ tenantId, plaidItemId });
|
await this.systemPlaidItemModel.query().insert({ tenantId, plaidItemId });
|
||||||
|
|
||||||
// Triggers `onPlaidItemCreated` event.
|
// Triggers `onPlaidItemCreated` event.
|
||||||
await this.eventEmitter.emitAsync(events.plaid.onItemCreated, {
|
await this.eventEmitter.emitAsync(events.plaid.onItemCreated, {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import * as R from 'ramda';
|
import * as R from 'ramda';
|
||||||
import bluebird from 'bluebird';
|
import * as bluebird from 'bluebird';
|
||||||
|
import * as uniqid from 'uniqid';
|
||||||
import { entries, groupBy } from 'lodash';
|
import { entries, groupBy } from 'lodash';
|
||||||
import {
|
import {
|
||||||
AccountBase as PlaidAccountBase,
|
AccountBase as PlaidAccountBase,
|
||||||
@@ -12,7 +13,6 @@ import {
|
|||||||
transformPlaidTrxsToCashflowCreate,
|
transformPlaidTrxsToCashflowCreate,
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
import { Knex } from 'knex';
|
import { Knex } from 'knex';
|
||||||
import uniqid from 'uniqid';
|
|
||||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||||
import { RemovePendingUncategorizedTransaction } from '../../BankingTransactions/commands/RemovePendingUncategorizedTransaction.service';
|
import { RemovePendingUncategorizedTransaction } from '../../BankingTransactions/commands/RemovePendingUncategorizedTransaction.service';
|
||||||
import { CreateAccountService } from '../../Accounts/CreateAccount.service';
|
import { CreateAccountService } from '../../Accounts/CreateAccount.service';
|
||||||
|
|||||||
@@ -103,7 +103,6 @@ export class PlaidUpdateTransactions {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches transactions from the `Plaid API` for a given item.
|
* Fetches transactions from the `Plaid API` for a given item.
|
||||||
* @param {number} tenantId - Tenant ID.
|
|
||||||
* @param {string} plaidItemId - The Plaid ID for the item.
|
* @param {string} plaidItemId - The Plaid ID for the item.
|
||||||
* @returns {Promise<PlaidFetchedTransactionsUpdates>}
|
* @returns {Promise<PlaidFetchedTransactionsUpdates>}
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||||
import { PlaidItem } from '../models/PlaidItem';
|
import { PlaidItem } from '../models/PlaidItem';
|
||||||
import { PlaidUpdateTransactions } from './PlaidUpdateTransactions';
|
import { PlaidUpdateTransactions } from './PlaidUpdateTransactions';
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
@@ -8,7 +9,7 @@ export class PlaidWebooks {
|
|||||||
private readonly updateTransactionsService: PlaidUpdateTransactions,
|
private readonly updateTransactionsService: PlaidUpdateTransactions,
|
||||||
|
|
||||||
@Inject(PlaidItem.name)
|
@Inject(PlaidItem.name)
|
||||||
private readonly plaidItemModel: typeof PlaidItem,
|
private readonly plaidItemModel: TenantModelProxy<typeof PlaidItem>,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -76,11 +77,10 @@ export class PlaidWebooks {
|
|||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
public async handleTransactionsWebooks(
|
public async handleTransactionsWebooks(
|
||||||
tenantId: number,
|
|
||||||
plaidItemId: string,
|
plaidItemId: string,
|
||||||
webhookCode: string,
|
webhookCode: string,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const plaidItem = await this.plaidItemModel
|
const plaidItem = await this.plaidItemModel()
|
||||||
.query()
|
.query()
|
||||||
.findOne({ plaidItemId })
|
.findOne({ plaidItemId })
|
||||||
.throwIfNotFound();
|
.throwIfNotFound();
|
||||||
@@ -122,9 +122,8 @@ export class PlaidWebooks {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles all Item webhook events.
|
* Handles all Item webhook events.
|
||||||
* @param {number} tenantId - Tenant ID
|
|
||||||
* @param {string} webhookCode - The webhook code
|
|
||||||
* @param {string} plaidItemId - The Plaid ID for the item
|
* @param {string} plaidItemId - The Plaid ID for the item
|
||||||
|
* @param {string} webhookCode - The webhook code
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
public async itemsHandler(
|
public async itemsHandler(
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import { ClsService } from 'nestjs-cls';
|
||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { SystemPlaidItem } from '../models/SystemPlaidItem';
|
||||||
|
import { TenantModel } from '@/modules/System/models/TenantModel';
|
||||||
|
import { SystemUser } from '@/modules/System/models/SystemUser';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class SetupPlaidItemTenantService {
|
||||||
|
constructor(
|
||||||
|
private readonly clsService: ClsService,
|
||||||
|
|
||||||
|
@Inject(SystemPlaidItem.name)
|
||||||
|
private readonly systemPlaidItemModel: typeof SystemPlaidItem,
|
||||||
|
|
||||||
|
@Inject(TenantModel.name)
|
||||||
|
private readonly tenantModel: typeof TenantModel,
|
||||||
|
|
||||||
|
@Inject(SystemUser.name)
|
||||||
|
private readonly systemUserModel: typeof SystemUser,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the Plaid tenant.
|
||||||
|
* @param {string} plaidItemId - The Plaid item id.
|
||||||
|
* @param {() => void} callback - The callback function to execute after setting up the Plaid tenant.
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
public async setupPlaidTenant(plaidItemId: string, callback: () => void) {
|
||||||
|
const plaidItem = await this.systemPlaidItemModel
|
||||||
|
.query()
|
||||||
|
.findOne({ plaidItemId });
|
||||||
|
|
||||||
|
if (!plaidItem) {
|
||||||
|
throw new Error('Plaid item not found');
|
||||||
|
}
|
||||||
|
const tenant = await this.tenantModel
|
||||||
|
.query()
|
||||||
|
.findOne({ id: plaidItem.tenantId })
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
const user = await this.systemUserModel
|
||||||
|
.query()
|
||||||
|
.findOne({
|
||||||
|
tenantId: tenant.id,
|
||||||
|
})
|
||||||
|
.modify('active')
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
this.clsService.set('organizationId', tenant.organizationId);
|
||||||
|
this.clsService.set('userId', user.id);
|
||||||
|
|
||||||
|
return callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,3 +9,18 @@ export class PlaidItemDto {
|
|||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
institutionId: string;
|
institutionId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class PlaidWebhookDto {
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
itemId: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
webhookType: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
webhookCode: string;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,43 +1,46 @@
|
|||||||
// import Container, { Service } from 'typedi';
|
import { Process } from '@nestjs/bull';
|
||||||
// import { PlaidUpdateTransactions } from './PlaidUpdateTransactions';
|
import { UseCls } from 'nestjs-cls';
|
||||||
// import { IPlaidItemCreatedEventPayload } from '@/interfaces';
|
import { Processor, WorkerHost } from '@nestjs/bullmq';
|
||||||
|
import { Scope } from '@nestjs/common';
|
||||||
|
import { Job } from 'bullmq';
|
||||||
|
import {
|
||||||
|
PlaidFetchTransitonsEventPayload,
|
||||||
|
UpdateBankingPlaidTransitionsJob,
|
||||||
|
UpdateBankingPlaidTransitionsQueueJob,
|
||||||
|
} from '../types/BankingPlaid.types';
|
||||||
|
import { PlaidUpdateTransactions } from '../command/PlaidUpdateTransactions';
|
||||||
|
import { SetupPlaidItemTenantService } from '../command/SetupPlaidItemTenant.service';
|
||||||
|
|
||||||
// @Service()
|
@Processor({
|
||||||
// export class PlaidFetchTransactionsJob {
|
name: UpdateBankingPlaidTransitionsQueueJob,
|
||||||
// /**
|
scope: Scope.REQUEST,
|
||||||
// * Constructor method.
|
})
|
||||||
// */
|
export class PlaidFetchTransactionsProcessor extends WorkerHost {
|
||||||
// constructor(agenda) {
|
constructor(
|
||||||
// agenda.define(
|
private readonly plaidFetchTransactionsService: PlaidUpdateTransactions,
|
||||||
// 'plaid-update-account-transactions',
|
private readonly setupPlaidItemService: SetupPlaidItemTenantService,
|
||||||
// { priority: 'high', concurrency: 2 },
|
) {
|
||||||
// this.handler
|
super();
|
||||||
// );
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
// /**
|
/**
|
||||||
// * Triggers the function.
|
* Triggers the function.
|
||||||
// */
|
*/
|
||||||
// private handler = async (job, done: Function) => {
|
@Process(UpdateBankingPlaidTransitionsJob)
|
||||||
// const { tenantId, plaidItemId } = job.attrs
|
@UseCls()
|
||||||
// .data as IPlaidItemCreatedEventPayload;
|
async process(job: Job<PlaidFetchTransitonsEventPayload>) {
|
||||||
|
const { plaidItemId } = job.data;
|
||||||
|
|
||||||
// const plaidFetchTransactionsService = Container.get(
|
try {
|
||||||
// PlaidUpdateTransactions
|
await this.setupPlaidItemService.setupPlaidTenant(plaidItemId, () => {
|
||||||
// );
|
return this.plaidFetchTransactionsService.updateTransactions(
|
||||||
// const io = Container.get('socket');
|
plaidItemId,
|
||||||
|
);
|
||||||
// try {
|
});
|
||||||
// await plaidFetchTransactionsService.updateTransactions(
|
// Notify the frontend to reflect the new transactions changes.
|
||||||
// tenantId,
|
// io.emit('NEW_TRANSACTIONS_DATA', { plaidItemId });
|
||||||
// plaidItemId
|
} catch (error) {
|
||||||
// );
|
console.log(error);
|
||||||
// // Notify the frontend to reflect the new transactions changes.
|
}
|
||||||
// io.emit('NEW_TRANSACTIONS_DATA', { plaidItemId });
|
}
|
||||||
// done();
|
}
|
||||||
// } catch (error) {
|
|
||||||
// console.log(error);
|
|
||||||
// done(error);
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export class SystemPlaidItem extends BaseModel {
|
|||||||
* Relationship mapping.
|
* Relationship mapping.
|
||||||
*/
|
*/
|
||||||
static get relationMappings() {
|
static get relationMappings() {
|
||||||
const Tenant = require('system/models/Tenant');
|
const { TenantModel } = require('../../System/models/TenantModel');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
/**
|
/**
|
||||||
@@ -38,7 +38,7 @@ export class SystemPlaidItem extends BaseModel {
|
|||||||
*/
|
*/
|
||||||
tenant: {
|
tenant: {
|
||||||
relation: Model.BelongsToOneRelation,
|
relation: Model.BelongsToOneRelation,
|
||||||
modelClass: Tenant.default,
|
modelClass: TenantModel,
|
||||||
join: {
|
join: {
|
||||||
from: 'users.tenantId',
|
from: 'users.tenantId',
|
||||||
to: 'tenants.id',
|
to: 'tenants.id',
|
||||||
|
|||||||
@@ -1,22 +1,34 @@
|
|||||||
import { events } from '@/common/events/events';
|
import { events } from '@/common/events/events';
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { OnEvent } from '@nestjs/event-emitter';
|
import { OnEvent } from '@nestjs/event-emitter';
|
||||||
import { IPlaidItemCreatedEventPayload } from '../types/BankingPlaid.types';
|
import {
|
||||||
|
IPlaidItemCreatedEventPayload,
|
||||||
|
UpdateBankingPlaidTransitionsJob,
|
||||||
|
UpdateBankingPlaidTransitionsQueueJob,
|
||||||
|
} from '../types/BankingPlaid.types';
|
||||||
|
import { Queue } from 'bullmq';
|
||||||
|
import { InjectQueue } from '@nestjs/bullmq';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PlaidUpdateTransactionsOnItemCreatedSubscriber {
|
export class PlaidUpdateTransactionsOnItemCreatedSubscriber {
|
||||||
|
constructor(
|
||||||
|
@InjectQueue(UpdateBankingPlaidTransitionsQueueJob)
|
||||||
|
private readonly updateTransitionsQueue: Queue,
|
||||||
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the Plaid item transactions
|
* Updates the Plaid item transactions
|
||||||
* @param {IPlaidItemCreatedEventPayload} payload - Event payload.
|
* @param {IPlaidItemCreatedEventPayload} payload - Event payload.
|
||||||
*/
|
*/
|
||||||
@OnEvent(events.plaid.onItemCreated)
|
@OnEvent(events.plaid.onItemCreated)
|
||||||
public async handleUpdateTransactionsOnItemCreated({
|
public async handleUpdateTransactionsOnItemCreated({
|
||||||
tenantId,
|
|
||||||
plaidItemId,
|
plaidItemId,
|
||||||
plaidAccessToken,
|
|
||||||
plaidInstitutionId,
|
|
||||||
}: IPlaidItemCreatedEventPayload) {
|
}: IPlaidItemCreatedEventPayload) {
|
||||||
const payload = { tenantId, plaidItemId };
|
const payload = { plaidItemId };
|
||||||
// await this.agenda.now('plaid-update-account-transactions', payload);
|
|
||||||
};
|
await this.updateTransitionsQueue.add(
|
||||||
|
UpdateBankingPlaidTransitionsJob,
|
||||||
|
payload,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { Knex } from "knex";
|
import { Knex } from 'knex';
|
||||||
import { RemovedTransaction, Transaction } from "plaid";
|
import { RemovedTransaction, Transaction } from 'plaid';
|
||||||
|
|
||||||
export interface IPlaidTransactionsSyncedEventPayload {
|
export interface IPlaidTransactionsSyncedEventPayload {
|
||||||
// tenantId: number;
|
// tenantId: number;
|
||||||
plaidAccountId: number;
|
plaidAccountId: number;
|
||||||
batch: string;
|
batch: string;
|
||||||
trx?: Knex.Transaction
|
trx?: Knex.Transaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PlaidItemDTO {
|
export interface PlaidItemDTO {
|
||||||
@@ -13,7 +13,6 @@ export interface PlaidItemDTO {
|
|||||||
institutionId: string;
|
institutionId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface PlaidFetchedTransactionsUpdates {
|
export interface PlaidFetchedTransactionsUpdates {
|
||||||
added: Transaction[];
|
added: Transaction[];
|
||||||
modified: Transaction[];
|
modified: Transaction[];
|
||||||
@@ -22,11 +21,20 @@ export interface PlaidFetchedTransactionsUpdates {
|
|||||||
cursor: string;
|
cursor: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export interface IPlaidItemCreatedEventPayload {
|
export interface IPlaidItemCreatedEventPayload {
|
||||||
tenantId: number;
|
tenantId: number;
|
||||||
plaidAccessToken: string;
|
plaidAccessToken: string;
|
||||||
plaidItemId: string;
|
plaidItemId: string;
|
||||||
plaidInstitutionId: string;
|
plaidInstitutionId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const UpdateBankingPlaidTransitionsJob =
|
||||||
|
'update-banking-plaid-transitions-job';
|
||||||
|
|
||||||
|
export const UpdateBankingPlaidTransitionsQueueJob =
|
||||||
|
'update-banking-plaid-transitions-query';
|
||||||
|
|
||||||
|
|
||||||
|
export interface PlaidFetchTransitonsEventPayload {
|
||||||
|
plaidItemId: string;
|
||||||
|
}
|
||||||
@@ -8,12 +8,13 @@ import { Inject, Injectable } from '@nestjs/common';
|
|||||||
import { events } from '@/common/events/events';
|
import { events } from '@/common/events/events';
|
||||||
import { Account } from '@/modules/Accounts/models/Account.model';
|
import { Account } from '@/modules/Accounts/models/Account.model';
|
||||||
import { UncategorizedBankTransaction } from '../models/UncategorizedBankTransaction';
|
import { UncategorizedBankTransaction } from '../models/UncategorizedBankTransaction';
|
||||||
|
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DecrementUncategorizedTransactionOnCategorizeSubscriber {
|
export class DecrementUncategorizedTransactionOnCategorizeSubscriber {
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(Account.name)
|
@Inject(Account.name)
|
||||||
private readonly accountModel: typeof Account,
|
private readonly accountModel: TenantModelProxy<typeof Account>,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -33,7 +34,7 @@ export class DecrementUncategorizedTransactionOnCategorizeSubscriber {
|
|||||||
if (uncategorizedTransaction.isPending) {
|
if (uncategorizedTransaction.isPending) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await this.accountModel
|
await this.accountModel()
|
||||||
.query(trx)
|
.query(trx)
|
||||||
.findById(uncategorizedTransaction.accountId)
|
.findById(uncategorizedTransaction.accountId)
|
||||||
.decrement('uncategorizedTransactions', 1);
|
.decrement('uncategorizedTransactions', 1);
|
||||||
@@ -58,7 +59,7 @@ export class DecrementUncategorizedTransactionOnCategorizeSubscriber {
|
|||||||
if (uncategorizedTransaction.isPending) {
|
if (uncategorizedTransaction.isPending) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await this.accountModel
|
await this.accountModel()
|
||||||
.query(trx)
|
.query(trx)
|
||||||
.findById(uncategorizedTransaction.accountId)
|
.findById(uncategorizedTransaction.accountId)
|
||||||
.increment('uncategorizedTransactions', 1);
|
.increment('uncategorizedTransactions', 1);
|
||||||
@@ -80,7 +81,7 @@ export class DecrementUncategorizedTransactionOnCategorizeSubscriber {
|
|||||||
// Cannot continue if the transaction is still pending.
|
// Cannot continue if the transaction is still pending.
|
||||||
if (uncategorizedTransaction.isPending) return;
|
if (uncategorizedTransaction.isPending) return;
|
||||||
|
|
||||||
await this.accountModel
|
await this.accountModel()
|
||||||
.query(trx)
|
.query(trx)
|
||||||
.findById(uncategorizedTransaction.accountId)
|
.findById(uncategorizedTransaction.accountId)
|
||||||
.increment('uncategorizedTransactions', 1);
|
.increment('uncategorizedTransactions', 1);
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ export class PaymentReceived extends TenantBaseModel {
|
|||||||
const { Customer } = require('../../Customers/models/Customer');
|
const { Customer } = require('../../Customers/models/Customer');
|
||||||
const { Account } = require('../../Accounts/models/Account.model');
|
const { Account } = require('../../Accounts/models/Account.model');
|
||||||
const { Branch } = require('../../Branches/models/Branch.model');
|
const { Branch } = require('../../Branches/models/Branch.model');
|
||||||
// const Document = require('../../Documents/models/Document');
|
const { DocumentModel } = require('../../Attachments/models/Document.model');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
customer: {
|
customer: {
|
||||||
@@ -139,21 +139,21 @@ export class PaymentReceived extends TenantBaseModel {
|
|||||||
/**
|
/**
|
||||||
* Payment transaction may has many attached attachments.
|
* Payment transaction may has many attached attachments.
|
||||||
*/
|
*/
|
||||||
// attachments: {
|
attachments: {
|
||||||
// relation: Model.ManyToManyRelation,
|
relation: Model.ManyToManyRelation,
|
||||||
// modelClass: Document.default,
|
modelClass: DocumentModel,
|
||||||
// join: {
|
join: {
|
||||||
// from: 'payment_receives.id',
|
from: 'payment_receives.id',
|
||||||
// through: {
|
through: {
|
||||||
// from: 'document_links.modelId',
|
from: 'document_links.modelId',
|
||||||
// to: 'document_links.documentId',
|
to: 'document_links.documentId',
|
||||||
// },
|
},
|
||||||
// to: 'documents.id',
|
to: 'documents.id',
|
||||||
// },
|
},
|
||||||
// filter(query) {
|
filter(query) {
|
||||||
// query.where('model_ref', 'PaymentReceive');
|
query.where('model_ref', 'PaymentReceive');
|
||||||
// },
|
},
|
||||||
// },
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
|
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { SaleEstimateTransfromer } from './SaleEstimate.transformer';
|
import { SaleEstimateTransfromer } from './SaleEstimate.transformer';
|
||||||
import { SaleEstimateValidators } from '../commands/SaleEstimateValidators.service';
|
import { SaleEstimateValidators } from '../commands/SaleEstimateValidators.service';
|
||||||
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
|
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
|
||||||
import { SaleEstimate } from '../models/SaleEstimate';
|
import { SaleEstimate } from '../models/SaleEstimate';
|
||||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
|
||||||
import { events } from '@/common/events/events';
|
import { events } from '@/common/events/events';
|
||||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,10 @@ export class SystemUser extends BaseModel {
|
|||||||
inviteAccepted(query) {
|
inviteAccepted(query) {
|
||||||
query.whereNotNull('invite_accepted_at');
|
query.whereNotNull('invite_accepted_at');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
active(query) {
|
||||||
|
query.where('active', true);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -552,7 +552,7 @@ export function useGetRecognizedBankTransaction(
|
|||||||
() =>
|
() =>
|
||||||
apiRequest
|
apiRequest
|
||||||
.get(`/banking/recognized/transactions/${uncategorizedTransactionId}`)
|
.get(`/banking/recognized/transactions/${uncategorizedTransactionId}`)
|
||||||
.then((res) => transformToCamelCase(res.data?.data)),
|
.then((res) => transformToCamelCase(res.data)),
|
||||||
options,
|
options,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -580,7 +580,7 @@ export function useGetBankAccountSummaryMeta(
|
|||||||
() =>
|
() =>
|
||||||
apiRequest
|
apiRequest
|
||||||
.get(`/banking/accounts/${bankAccountId}/summary`)
|
.get(`/banking/accounts/${bankAccountId}/summary`)
|
||||||
.then((res) => transformToCamelCase(res.data?.data)),
|
.then((res) => transformToCamelCase(res.data)),
|
||||||
{ ...options },
|
{ ...options },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -616,7 +616,7 @@ export function useGetAutofillCategorizeTransaction(
|
|||||||
.get(`/banking/categorize/autofill`, {
|
.get(`/banking/categorize/autofill`, {
|
||||||
params: { uncategorizedTransactionIds },
|
params: { uncategorizedTransactionIds },
|
||||||
})
|
})
|
||||||
.then((res) => transformToCamelCase(res.data?.data)),
|
.then((res) => transformToCamelCase(res.data)),
|
||||||
{ ...options },
|
{ ...options },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user