mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-18 05:40:31 +00:00
feat: wip pause/resume bank feeds syncing
This commit is contained in:
@@ -3,6 +3,7 @@ import { NextFunction, Request, Response, Router } from 'express';
|
||||
import BaseController from '@/api/controllers/BaseController';
|
||||
import { GetBankAccountSummary } from '@/services/Banking/BankAccounts/GetBankAccountSummary';
|
||||
import { BankAccountsApplication } from '@/services/Banking/BankAccounts/BankAccountsApplication';
|
||||
import { param } from 'express-validator';
|
||||
|
||||
@Service()
|
||||
export class BankAccountsController extends BaseController {
|
||||
@@ -26,10 +27,18 @@ export class BankAccountsController extends BaseController {
|
||||
router.post('/:bankAccountId/update', this.refreshBankAccount.bind(this));
|
||||
router.post(
|
||||
'/:bankAccountId/pause_feeds',
|
||||
[
|
||||
param('bankAccountId').exists().isNumeric().toInt(),
|
||||
],
|
||||
this.validationResult,
|
||||
this.pauseBankAccountFeeds.bind(this)
|
||||
);
|
||||
router.post(
|
||||
'/:bankAccountId/resume_feeds',
|
||||
[
|
||||
param('bankAccountId').exists().isNumeric().toInt(),
|
||||
],
|
||||
this.validationResult,
|
||||
this.resumeBankAccountFeeds.bind(this)
|
||||
);
|
||||
|
||||
@@ -117,6 +126,13 @@ export class BankAccountsController extends BaseController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
* @returns {Promise<Response | void>}
|
||||
*/
|
||||
async resumeBankAccountFeeds(
|
||||
req: Request<{ bankAccountId: number }>,
|
||||
res: Response,
|
||||
@@ -129,13 +145,21 @@ export class BankAccountsController extends BaseController {
|
||||
await this.bankAccountsApp.resumeBankAccount(tenantId, bankAccountId);
|
||||
|
||||
return res.status(200).send({
|
||||
message: '',
|
||||
message: 'The bank account feeds syncing has been resumed.',
|
||||
id: bankAccountId,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
* @returns {Promise<Response | void>}
|
||||
*/
|
||||
async pauseBankAccountFeeds(
|
||||
req: Request<{ bankAccountId: number }>,
|
||||
res: Response,
|
||||
@@ -148,7 +172,8 @@ export class BankAccountsController extends BaseController {
|
||||
await this.bankAccountsApp.pauseBankAccount(tenantId, bankAccountId);
|
||||
|
||||
return res.status(200).send({
|
||||
message: '',
|
||||
message: 'The bank account feeds syncing has been paused.',
|
||||
id: bankAccountId,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
exports.up = function (knex) {
|
||||
return knex.schema.table('plaid_items', (table) => {
|
||||
table.datetime('paused_at');
|
||||
});
|
||||
};
|
||||
|
||||
exports.down = function (knex) {
|
||||
return knex.schema.table('plaid_items', (table) => {
|
||||
table.dropColumn('paused_at');
|
||||
});
|
||||
};
|
||||
@@ -1,6 +1,8 @@
|
||||
import TenantModel from 'models/TenantModel';
|
||||
|
||||
export default class PlaidItem extends TenantModel {
|
||||
pausedAt: Date;
|
||||
|
||||
/**
|
||||
* Table name.
|
||||
*/
|
||||
@@ -21,4 +23,19 @@ export default class PlaidItem extends TenantModel {
|
||||
static get relationMappings() {
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Virtual attributes.
|
||||
*/
|
||||
static get virtualAttributes() {
|
||||
return ['isPaused'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the Plaid item feeds syncing is paused.
|
||||
* @return {boolean}
|
||||
*/
|
||||
get isPaused() {
|
||||
return !!this.pausedAt;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { DisconnectBankAccount } from './DisconnectBankAccount';
|
||||
import { RefreshBankAccountService } from './RefreshBankAccount';
|
||||
import { ResumeBankAccountFeeds } from './PauseBankAccountFeeds';
|
||||
import { PauseBankAccountFeeds } from './PauseBankAccountFeeds';
|
||||
import { ResumeBankAccountFeeds } from './ResumeBankAccountFeeds';
|
||||
|
||||
@Service()
|
||||
export class BankAccountsApplication {
|
||||
@@ -15,7 +16,7 @@ export class BankAccountsApplication {
|
||||
private resumeBankAccountFeedsService: ResumeBankAccountFeeds;
|
||||
|
||||
@Inject()
|
||||
private pauseBankAccountFeedsService: ResumeBankAccountFeeds;
|
||||
private pauseBankAccountFeedsService: PauseBankAccountFeeds;
|
||||
|
||||
/**
|
||||
* Disconnects the given bank account.
|
||||
@@ -45,12 +46,12 @@ export class BankAccountsApplication {
|
||||
|
||||
/**
|
||||
* Pauses the feeds sync of the given bank account.
|
||||
* @param {number} tenantId
|
||||
* @param {number} bankAccountId
|
||||
* @param {number} tenantId
|
||||
* @param {number} bankAccountId
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async pauseBankAccount(tenantId: number, bankAccountId: number) {
|
||||
return this.pauseBankAccountFeedsService.resumeBankAccountFeeds(
|
||||
return this.pauseBankAccountFeedsService.pauseBankAccountFeeds(
|
||||
tenantId,
|
||||
bankAccountId
|
||||
);
|
||||
@@ -58,8 +59,8 @@ export class BankAccountsApplication {
|
||||
|
||||
/**
|
||||
* Resumes the feeds sync of the given bank account.
|
||||
* @param {number} tenantId
|
||||
* @param {number} bankAccountId
|
||||
* @param {number} tenantId
|
||||
* @param {number} bankAccountId
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async resumeBankAccount(tenantId: number, bankAccountId: number) {
|
||||
|
||||
@@ -1,9 +1,33 @@
|
||||
import { Service } from 'typedi';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { Knex } from 'knex';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import UnitOfWork from '@/services/UnitOfWork';
|
||||
|
||||
@Service()
|
||||
export class ResumeBankAccountFeeds {
|
||||
public resumeBankAccountFeeds(tenantId: number, bankAccountId: number) {
|
||||
export class PauseBankAccountFeeds {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private uow: UnitOfWork;
|
||||
|
||||
/**
|
||||
* Pauses the bankfeed syncing of the given bank account.
|
||||
* @param {number} tenantId
|
||||
* @param {number} bankAccountId
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public async pauseBankAccountFeeds(tenantId: number, bankAccountId: number) {
|
||||
const { Account, PlaidItem } = this.tenancy.models(tenantId);
|
||||
|
||||
const oldAccount = await Account.query()
|
||||
.findById(bankAccountId)
|
||||
.withGraphFetched('plaidItem');
|
||||
|
||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||
await PlaidItem.query().findById(oldAccount.plaidItem.id).patch({
|
||||
pausedAt: null,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,32 @@
|
||||
import { Service } from "typedi";
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import UnitOfWork from '@/services/UnitOfWork';
|
||||
import { Inject, Service } from 'typedi';
|
||||
|
||||
@Service()
|
||||
export class ResumeBankAccountFeeds {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private uow: UnitOfWork;
|
||||
|
||||
/**
|
||||
*
|
||||
* Resumes the bank feeds syncing of the bank account.
|
||||
* @param {number} tenantId
|
||||
* @param {number} bankAccountId
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public resumeBankAccountFeeds(tenantId: number, bankAccountId: number) {}
|
||||
public async resumeBankAccountFeeds(tenantId: number, bankAccountId: number) {
|
||||
const { Account, PlaidItem } = this.tenancy.models(tenantId);
|
||||
|
||||
const oldAccount = await Account.query()
|
||||
.findById(bankAccountId)
|
||||
.withGraphFetched('plaidItem');
|
||||
|
||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||
await PlaidItem.query().findById(oldAccount.plaidItem.id).patch({
|
||||
pausedAt: new Date(),
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import TaxRatesAlerts from '@/containers/TaxRates/alerts';
|
||||
import { CashflowAlerts } from '../CashFlow/CashflowAlerts';
|
||||
import { BankRulesAlerts } from '../Banking/Rules/RulesList/BankRulesAlerts';
|
||||
import { SubscriptionAlerts } from '../Subscriptions/alerts/alerts';
|
||||
import { BankAccountAlerts } from '@/containers/CashFlow/AccountTransactions/alerts';
|
||||
|
||||
export default [
|
||||
...AccountsAlerts,
|
||||
@@ -58,5 +59,6 @@ export default [
|
||||
...TaxRatesAlerts,
|
||||
...CashflowAlerts,
|
||||
...BankRulesAlerts,
|
||||
...SubscriptionAlerts
|
||||
...SubscriptionAlerts,
|
||||
...BankAccountAlerts,
|
||||
];
|
||||
|
||||
@@ -46,6 +46,7 @@ import {
|
||||
useUnexcludeUncategorizedTransactions,
|
||||
} from '@/hooks/query/bank-rules';
|
||||
import { withBanking } from '../withBanking';
|
||||
import withAlertActions from '@/containers/Alert/withAlertActions';
|
||||
|
||||
function AccountTransactionsActionsBar({
|
||||
// #withDialogActions
|
||||
@@ -60,6 +61,9 @@ function AccountTransactionsActionsBar({
|
||||
// #withBanking
|
||||
uncategorizedTransationsIdsSelected,
|
||||
excludedTransactionsIdsSelected,
|
||||
|
||||
// #withAlerts
|
||||
openAlert,
|
||||
}) {
|
||||
const history = useHistory();
|
||||
const { accountId, currentAccount } = useAccountTransactionsContext();
|
||||
@@ -191,6 +195,16 @@ function AccountTransactionsActionsBar({
|
||||
});
|
||||
};
|
||||
|
||||
// Handle resume bank feeds syncing.
|
||||
const handleResumeFeedsSyncing = () => {
|
||||
openAlert('resume-feeds-syncing-bank-accounnt');
|
||||
};
|
||||
|
||||
// Handles pause bank feeds syncing.
|
||||
const handlePauseFeedsSyncing = () => {
|
||||
openAlert('pause-feeds-syncing-bank-accounnt');
|
||||
};
|
||||
|
||||
return (
|
||||
<DashboardActionsBar>
|
||||
<NavbarGroup>
|
||||
@@ -284,6 +298,15 @@ function AccountTransactionsActionsBar({
|
||||
}}
|
||||
content={
|
||||
<Menu>
|
||||
<MenuItem
|
||||
onClick={handlePauseFeedsSyncing}
|
||||
text={'Pause bankfeeds syncing'}
|
||||
/>
|
||||
<MenuItem
|
||||
onClick={handleResumeFeedsSyncing}
|
||||
text={'Resume bankfeeds syncing'}
|
||||
/>
|
||||
|
||||
<If condition={isSyncingOwner && isFeedsActive}>
|
||||
<MenuItem onClick={handleBankUpdateClick} text={'Update'} />
|
||||
<MenuDivider />
|
||||
@@ -311,6 +334,7 @@ function AccountTransactionsActionsBar({
|
||||
|
||||
export default compose(
|
||||
withDialogActions,
|
||||
withAlertActions,
|
||||
withSettingsActions,
|
||||
withSettings(({ cashflowTransactionsSettings }) => ({
|
||||
cashflowTansactionsTableSize: cashflowTransactionsSettings?.tableSize,
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import { Intent, Alert } from '@blueprintjs/core';
|
||||
|
||||
import { AppToaster, FormattedMessage as T } from '@/components';
|
||||
import withAlertStoreConnect from '@/containers/Alert/withAlertStoreConnect';
|
||||
import withAlertActions from '@/containers/Alert/withAlertActions';
|
||||
|
||||
import { usePauseFeedsBankAccount } from '@/hooks/query/bank-accounts';
|
||||
import { compose } from '@/utils';
|
||||
|
||||
/**
|
||||
* Item activate alert.
|
||||
*/
|
||||
function PauseFeedsBankAccountAlert({
|
||||
name,
|
||||
|
||||
// #withAlertStoreConnect
|
||||
isOpen,
|
||||
payload: { bankAccountId },
|
||||
|
||||
// #withAlertActions
|
||||
closeAlert,
|
||||
}) {
|
||||
const { mutateAsync: pauseBankAccountFeeds, isLoading } =
|
||||
usePauseFeedsBankAccount();
|
||||
|
||||
// Handle activate item alert cancel.
|
||||
const handleCancelActivateItem = () => {
|
||||
closeAlert(name);
|
||||
};
|
||||
|
||||
// Handle confirm item activated.
|
||||
const handleConfirmItemActivate = () => {
|
||||
pauseBankAccountFeeds(bankAccountId)
|
||||
.then(() => {
|
||||
AppToaster.show({
|
||||
message: 'The bank feeds of the bank account has been paused.',
|
||||
intent: Intent.SUCCESS,
|
||||
});
|
||||
})
|
||||
.catch((error) => {})
|
||||
.finally(() => {
|
||||
closeAlert(name);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Alert
|
||||
cancelButtonText={<T id={'cancel'} />}
|
||||
confirmButtonText={<T id={'activate'} />}
|
||||
intent={Intent.WARNING}
|
||||
isOpen={isOpen}
|
||||
onCancel={handleCancelActivateItem}
|
||||
loading={isLoading}
|
||||
onConfirm={handleConfirmItemActivate}
|
||||
>
|
||||
<p>Are you sure.</p>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withAlertStoreConnect(),
|
||||
withAlertActions,
|
||||
)(PauseFeedsBankAccountAlert);
|
||||
@@ -0,0 +1,67 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import { Intent, Alert } from '@blueprintjs/core';
|
||||
|
||||
import { AppToaster, FormattedMessage as T } from '@/components';
|
||||
import withAlertStoreConnect from '@/containers/Alert/withAlertStoreConnect';
|
||||
import withAlertActions from '@/containers/Alert/withAlertActions';
|
||||
|
||||
import { useResumeFeedsBankAccount } from '@/hooks/query/bank-accounts';
|
||||
import { compose } from '@/utils';
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function ResumeFeedsBankAccountAlert({
|
||||
name,
|
||||
|
||||
// #withAlertStoreConnect
|
||||
isOpen,
|
||||
payload: { bankAccountId },
|
||||
|
||||
// #withAlertActions
|
||||
closeAlert,
|
||||
}) {
|
||||
const { mutateAsync: resumeFeedsBankAccount, isLoading } =
|
||||
useResumeFeedsBankAccount();
|
||||
|
||||
// Handle activate item alert cancel.
|
||||
const handleCancelActivateItem = () => {
|
||||
closeAlert(name);
|
||||
};
|
||||
|
||||
// Handle confirm item activated.
|
||||
const handleConfirmItemActivate = () => {
|
||||
resumeFeedsBankAccount(bankAccountId)
|
||||
.then(() => {
|
||||
AppToaster.show({
|
||||
message: 'The bank feeds of the bank account has been resumed.',
|
||||
intent: Intent.SUCCESS,
|
||||
});
|
||||
})
|
||||
.catch((error) => {})
|
||||
.finally(() => {
|
||||
closeAlert(name);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Alert
|
||||
cancelButtonText={<T id={'cancel'} />}
|
||||
confirmButtonText={<T id={'activate'} />}
|
||||
intent={Intent.WARNING}
|
||||
isOpen={isOpen}
|
||||
onCancel={handleCancelActivateItem}
|
||||
loading={isLoading}
|
||||
onConfirm={handleConfirmItemActivate}
|
||||
>
|
||||
<p>Are you sure.</p>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withAlertStoreConnect(),
|
||||
withAlertActions,
|
||||
)(ResumeFeedsBankAccountAlert);
|
||||
@@ -0,0 +1,24 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
|
||||
const ResumeFeedsBankAccountAlert = React.lazy(
|
||||
() => import('./ResumeFeedsBankAccount'),
|
||||
);
|
||||
|
||||
const PauseFeedsBankAccountAlert = React.lazy(
|
||||
() => import('./PauseFeedsBankAccount'),
|
||||
);
|
||||
|
||||
/**
|
||||
* Bank account alerts.
|
||||
*/
|
||||
export const BankAccountAlerts = [
|
||||
{
|
||||
name: 'resume-feeds-syncing-bank-accounnt',
|
||||
component: ResumeFeedsBankAccountAlert,
|
||||
},
|
||||
{
|
||||
name: 'pause-feeds-syncing-bank-accounnt',
|
||||
component: PauseFeedsBankAccountAlert,
|
||||
},
|
||||
];
|
||||
85
packages/webapp/src/hooks/query/bank-accounts.ts
Normal file
85
packages/webapp/src/hooks/query/bank-accounts.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import {
|
||||
UseMutationOptions,
|
||||
UseMutationResult,
|
||||
useQueryClient,
|
||||
useMutation,
|
||||
} from 'react-query';
|
||||
import useApiRequest from '../useRequest';
|
||||
|
||||
type PuaseFeedsBankAccountValues = { bankAccountId: number };
|
||||
|
||||
interface PuaseFeedsBankAccountResponse {}
|
||||
|
||||
/**
|
||||
* Resumes the feeds syncing of the bank account.
|
||||
* @param {UseMutationResult<PuaseFeedsBankAccountResponse, Error, ExcludeBankTransactionValue>} options
|
||||
* @returns {UseMutationResult<PuaseFeedsBankAccountResponse, Error, ExcludeBankTransactionValue>}
|
||||
*/
|
||||
export function usePauseFeedsBankAccount(
|
||||
options?: UseMutationOptions<
|
||||
PuaseFeedsBankAccountResponse,
|
||||
Error,
|
||||
PuaseFeedsBankAccountValues
|
||||
>,
|
||||
): UseMutationResult<
|
||||
PuaseFeedsBankAccountResponse,
|
||||
Error,
|
||||
PuaseFeedsBankAccountValues
|
||||
> {
|
||||
const queryClient = useQueryClient();
|
||||
const apiRequest = useApiRequest();
|
||||
|
||||
return useMutation<
|
||||
PuaseFeedsBankAccountResponse,
|
||||
Error,
|
||||
PuaseFeedsBankAccountValues
|
||||
>(
|
||||
(values) =>
|
||||
apiRequest.post(
|
||||
`/banking/bank_accounts/${values.bankAccountId}/pause_feeds`,
|
||||
),
|
||||
{
|
||||
onSuccess: (res, id) => {},
|
||||
...options,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
type ResumeFeedsBankAccountValues = { bankAccountId: number };
|
||||
|
||||
interface ResumeFeedsBankAccountResponse {}
|
||||
|
||||
/**
|
||||
* Resumes the feeds syncing of the bank account.
|
||||
* @param {UseMutationResult<ResumeFeedsBankAccountResponse, Error, ResumeFeedsBankAccountValues>} options
|
||||
* @returns {UseMutationResult<ResumeFeedsBankAccountResponse, Error, ResumeFeedsBankAccountValues>}
|
||||
*/
|
||||
export function useResumeFeedsBankAccount(
|
||||
options?: UseMutationOptions<
|
||||
ResumeFeedsBankAccountResponse,
|
||||
Error,
|
||||
ResumeFeedsBankAccountValues
|
||||
>,
|
||||
): UseMutationResult<
|
||||
ResumeFeedsBankAccountResponse,
|
||||
Error,
|
||||
ResumeFeedsBankAccountValues
|
||||
> {
|
||||
const queryClient = useQueryClient();
|
||||
const apiRequest = useApiRequest();
|
||||
|
||||
return useMutation<
|
||||
ResumeFeedsBankAccountResponse,
|
||||
Error,
|
||||
ResumeFeedsBankAccountValues
|
||||
>(
|
||||
(values) =>
|
||||
apiRequest.post(
|
||||
`/banking/bank_accounts/${values.bankAccountId}/resume_feeds`,
|
||||
),
|
||||
{
|
||||
onSuccess: (res, id) => {},
|
||||
...options,
|
||||
},
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user