mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-17 05:10:31 +00:00
Merge pull request #564 from bigcapitalhq/fix-banking-bugs
fix: Banking service bugs
This commit is contained in:
@@ -20,6 +20,7 @@ export default class UncategorizedCashflowTransaction extends mixin(
|
||||
description!: string;
|
||||
plaidTransactionId!: string;
|
||||
recognizedTransactionId!: number;
|
||||
excludedAt: Date;
|
||||
|
||||
/**
|
||||
* Table name.
|
||||
@@ -45,6 +46,7 @@ export default class UncategorizedCashflowTransaction extends mixin(
|
||||
'isDepositTransaction',
|
||||
'isWithdrawalTransaction',
|
||||
'isRecognized',
|
||||
'isExcluded'
|
||||
];
|
||||
}
|
||||
|
||||
@@ -89,6 +91,14 @@ export default class UncategorizedCashflowTransaction extends mixin(
|
||||
return !!this.recognizedTransactionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the transaction is excluded.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
public get isExcluded(): boolean {
|
||||
return !!this.excludedAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Model modifiers.
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { initialize } from 'objection';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { UncategorizedTransactionTransformer } from '@/services/Cashflow/UncategorizedTransactionTransformer';
|
||||
|
||||
@Service()
|
||||
export class GetBankAccountSummary {
|
||||
@@ -31,17 +32,21 @@ export class GetBankAccountSummary {
|
||||
.findById(bankAccountId)
|
||||
.throwIfNotFound();
|
||||
|
||||
const commonQuery = (q) => {
|
||||
// Include just the given account.
|
||||
q.where('accountId', bankAccountId);
|
||||
|
||||
// Only the not excluded.
|
||||
q.modify('notExcluded');
|
||||
|
||||
// Only the not categorized.
|
||||
q.modify('notCategorized');
|
||||
};
|
||||
|
||||
// Retrieves the uncategorized transactions count of the given bank account.
|
||||
const uncategorizedTranasctionsCount =
|
||||
await UncategorizedCashflowTransaction.query().onBuild((q) => {
|
||||
// Include just the given account.
|
||||
q.where('accountId', bankAccountId);
|
||||
|
||||
// Only the not excluded.
|
||||
q.modify('notExcluded');
|
||||
|
||||
// Only the not categorized.
|
||||
q.modify('notCategorized');
|
||||
commonQuery(q);
|
||||
|
||||
// Only the not matched bank transactions.
|
||||
q.withGraphJoined('matchedBankTransactions');
|
||||
@@ -52,25 +57,40 @@ export class GetBankAccountSummary {
|
||||
q.first();
|
||||
});
|
||||
|
||||
// Retrieves the recognized transactions count of the given bank account.
|
||||
const recognizedTransactionsCount = await RecognizedBankTransaction.query()
|
||||
.whereExists(
|
||||
UncategorizedCashflowTransaction.query().where(
|
||||
'accountId',
|
||||
bankAccountId
|
||||
)
|
||||
)
|
||||
.count('id as total')
|
||||
.first();
|
||||
// Retrives the recognized transactions count.
|
||||
const recognizedTransactionsCount =
|
||||
await UncategorizedCashflowTransaction.query().onBuild((q) => {
|
||||
commonQuery(q);
|
||||
|
||||
q.withGraphJoined('recognizedTransaction');
|
||||
q.whereNotNull('recognizedTransaction.id');
|
||||
|
||||
// Count the results.
|
||||
q.count('uncategorized_cashflow_transactions.id as total');
|
||||
q.first();
|
||||
});
|
||||
|
||||
const excludedTransactionsCount =
|
||||
await UncategorizedCashflowTransaction.query().onBuild((q) => {
|
||||
q.where('accountId', bankAccountId);
|
||||
q.modify('excluded');
|
||||
|
||||
// Count the results.
|
||||
q.count('uncategorized_cashflow_transactions.id as total');
|
||||
q.first();
|
||||
});
|
||||
|
||||
const totalUncategorizedTransactions =
|
||||
uncategorizedTranasctionsCount?.total || 0;
|
||||
const totalRecognizedTransactions = recognizedTransactionsCount?.total || 0;
|
||||
|
||||
const totalExcludedTransactions = excludedTransactionsCount?.total || 0;
|
||||
|
||||
return {
|
||||
name: bankAccount.name,
|
||||
totalUncategorizedTransactions,
|
||||
totalRecognizedTransactions,
|
||||
totalExcludedTransactions,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import { Knex } from 'knex';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import UnitOfWork from '@/services/UnitOfWork';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { validateTransactionNotCategorized } from './utils';
|
||||
import {
|
||||
validateTransactionNotCategorized,
|
||||
validateTransactionNotExcluded,
|
||||
} from './utils';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import events from '@/subscribers/events';
|
||||
import {
|
||||
@@ -37,9 +41,13 @@ export class ExcludeBankTransaction {
|
||||
.findById(uncategorizedTransactionId)
|
||||
.throwIfNotFound();
|
||||
|
||||
// Validate the transaction shouldn't be excluded.
|
||||
validateTransactionNotExcluded(oldUncategorizedTransaction);
|
||||
|
||||
// Validate the transaction shouldn't be categorized.
|
||||
validateTransactionNotCategorized(oldUncategorizedTransaction);
|
||||
|
||||
return this.uow.withTransaction(tenantId, async (trx) => {
|
||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||
await this.eventPublisher.emitAsync(events.bankTransactions.onExcluding, {
|
||||
tenantId,
|
||||
uncategorizedTransactionId,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import PromisePool from '@supercharge/promise-pool';
|
||||
import { castArray } from 'lodash';
|
||||
import { castArray, uniq } from 'lodash';
|
||||
import { ExcludeBankTransaction } from './ExcludeBankTransaction';
|
||||
|
||||
@Service()
|
||||
@@ -18,7 +18,7 @@ export class ExcludeBankTransactions {
|
||||
tenantId: number,
|
||||
bankTransactionIds: Array<number> | number
|
||||
) {
|
||||
const _bankTransactionIds = castArray(bankTransactionIds);
|
||||
const _bankTransactionIds = uniq(castArray(bankTransactionIds));
|
||||
|
||||
await PromisePool.withConcurrency(1)
|
||||
.for(_bankTransactionIds)
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import { Knex } from 'knex';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import UnitOfWork from '@/services/UnitOfWork';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { validateTransactionNotCategorized } from './utils';
|
||||
import {
|
||||
validateTransactionNotCategorized,
|
||||
validateTransactionShouldBeExcluded,
|
||||
} from './utils';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import events from '@/subscribers/events';
|
||||
import {
|
||||
@@ -37,9 +41,13 @@ export class UnexcludeBankTransaction {
|
||||
.findById(uncategorizedTransactionId)
|
||||
.throwIfNotFound();
|
||||
|
||||
// Validate the transaction should be excludded.
|
||||
validateTransactionShouldBeExcluded(oldUncategorizedTransaction);
|
||||
|
||||
// Validate the transaction shouldn't be categorized.
|
||||
validateTransactionNotCategorized(oldUncategorizedTransaction);
|
||||
|
||||
return this.uow.withTransaction(tenantId, async (trx) => {
|
||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||
await this.eventPublisher.emitAsync(
|
||||
events.bankTransactions.onUnexcluding,
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import PromisePool from '@supercharge/promise-pool';
|
||||
import { UnexcludeBankTransaction } from './UnexcludeBankTransaction';
|
||||
import { castArray } from 'lodash';
|
||||
import { castArray, uniq } from 'lodash';
|
||||
|
||||
@Service()
|
||||
export class UnexcludeBankTransactions {
|
||||
@@ -17,7 +17,7 @@ export class UnexcludeBankTransactions {
|
||||
tenantId: number,
|
||||
bankTransactionIds: Array<number> | number
|
||||
) {
|
||||
const _bankTransactionIds = castArray(bankTransactionIds);
|
||||
const _bankTransactionIds = uniq(castArray(bankTransactionIds));
|
||||
|
||||
await PromisePool.withConcurrency(1)
|
||||
.for(_bankTransactionIds)
|
||||
|
||||
@@ -3,6 +3,8 @@ import UncategorizedCashflowTransaction from '@/models/UncategorizedCashflowTran
|
||||
|
||||
const ERRORS = {
|
||||
TRANSACTION_ALREADY_CATEGORIZED: 'TRANSACTION_ALREADY_CATEGORIZED',
|
||||
TRANSACTION_ALREADY_EXCLUDED: 'TRANSACTION_ALREADY_EXCLUDED',
|
||||
TRANSACTION_NOT_EXCLUDED: 'TRANSACTION_NOT_EXCLUDED',
|
||||
};
|
||||
|
||||
export const validateTransactionNotCategorized = (
|
||||
@@ -12,3 +14,19 @@ export const validateTransactionNotCategorized = (
|
||||
throw new ServiceError(ERRORS.TRANSACTION_ALREADY_CATEGORIZED);
|
||||
}
|
||||
};
|
||||
|
||||
export const validateTransactionNotExcluded = (
|
||||
transaction: UncategorizedCashflowTransaction
|
||||
) => {
|
||||
if (transaction.isExcluded) {
|
||||
throw new ServiceError(ERRORS.TRANSACTION_ALREADY_EXCLUDED);
|
||||
}
|
||||
};
|
||||
|
||||
export const validateTransactionShouldBeExcluded = (
|
||||
transaction: UncategorizedCashflowTransaction
|
||||
) => {
|
||||
if (!transaction.isExcluded) {
|
||||
throw new ServiceError(ERRORS.TRANSACTION_NOT_EXCLUDED);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -34,6 +34,7 @@ export class GetRecognizedTransactionsService {
|
||||
q.withGraphFetched('recognizedTransaction.assignAccount');
|
||||
q.withGraphFetched('recognizedTransaction.bankRule');
|
||||
q.whereNotNull('recognizedTransactionId');
|
||||
q.modify('notExcluded');
|
||||
|
||||
if (_filter.accountId) {
|
||||
q.where('accountId', _filter.accountId);
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import React from 'react';
|
||||
import { AppShellProvider, useAppShellContext } from './AppContentShellProvider';
|
||||
// @ts-nocheck
|
||||
import React, { forwardRef, Ref } from 'react';
|
||||
import {
|
||||
AppShellProvider,
|
||||
useAppShellContext,
|
||||
} from './AppContentShellProvider';
|
||||
import { Box, BoxProps } from '../../Layout';
|
||||
import styles from './AppContentShell.module.scss';
|
||||
|
||||
@@ -12,50 +16,73 @@ interface AppContentShellProps {
|
||||
hideMain?: boolean;
|
||||
}
|
||||
|
||||
export function AppContentShell({
|
||||
asideProps,
|
||||
mainProps,
|
||||
topbarOffset = 0,
|
||||
hideAside = false,
|
||||
hideMain = false,
|
||||
...restProps
|
||||
}: AppContentShellProps) {
|
||||
return (
|
||||
<AppShellProvider
|
||||
mainProps={mainProps}
|
||||
asideProps={asideProps}
|
||||
topbarOffset={topbarOffset}
|
||||
hideAside={hideAside}
|
||||
hideMain={hideMain}
|
||||
>
|
||||
<Box {...restProps} className={styles.root} />
|
||||
</AppShellProvider>
|
||||
);
|
||||
}
|
||||
export const AppContentShell = forwardRef(
|
||||
(
|
||||
{
|
||||
asideProps,
|
||||
mainProps,
|
||||
topbarOffset = 0,
|
||||
hideAside = false,
|
||||
hideMain = false,
|
||||
...restProps
|
||||
}: AppContentShellProps,
|
||||
ref: Ref<HTMLDivElement>,
|
||||
) => {
|
||||
return (
|
||||
<AppShellProvider
|
||||
mainProps={mainProps}
|
||||
asideProps={asideProps}
|
||||
topbarOffset={topbarOffset}
|
||||
hideAside={hideAside}
|
||||
hideMain={hideMain}
|
||||
>
|
||||
<Box {...restProps} className={styles.root} ref={ref} />
|
||||
</AppShellProvider>
|
||||
);
|
||||
},
|
||||
);
|
||||
AppContentShell.displayName = 'AppContentShell';
|
||||
|
||||
interface AppContentShellMainProps extends BoxProps {}
|
||||
|
||||
function AppContentShellMain({ ...props }: AppContentShellMainProps) {
|
||||
const { hideMain } = useAppShellContext();
|
||||
/**
|
||||
* Main content of the app shell.
|
||||
* @param {AppContentShellMainProps} props -
|
||||
* @returns {React.ReactNode}
|
||||
*/
|
||||
const AppContentShellMain = forwardRef(
|
||||
({ ...props }: AppContentShellMainProps, ref: Ref<HTMLDivElement>) => {
|
||||
const { hideMain } = useAppShellContext();
|
||||
|
||||
if (hideMain === true) {
|
||||
return null;
|
||||
}
|
||||
return <Box {...props} className={styles.main} />;
|
||||
}
|
||||
if (hideMain === true) {
|
||||
return null;
|
||||
}
|
||||
return <Box {...props} className={styles.main} ref={ref} />;
|
||||
},
|
||||
);
|
||||
|
||||
AppContentShellMain.displayName = 'AppContentShellMain';
|
||||
|
||||
interface AppContentShellAsideProps extends BoxProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
function AppContentShellAside({ ...props }: AppContentShellAsideProps) {
|
||||
const { hideAside } = useAppShellContext();
|
||||
/**
|
||||
* Aside content of the app shell.
|
||||
* @param {AppContentShellAsideProps} props
|
||||
* @returns {React.ReactNode}
|
||||
*/
|
||||
const AppContentShellAside = forwardRef(
|
||||
({ ...props }: AppContentShellAsideProps, ref: Ref<HTMLDivElement>) => {
|
||||
const { hideAside } = useAppShellContext();
|
||||
|
||||
if (hideAside === true) {
|
||||
return null;
|
||||
}
|
||||
return <Box {...props} className={styles.aside} />;
|
||||
}
|
||||
if (hideAside === true) {
|
||||
return null;
|
||||
}
|
||||
return <Box {...props} className={styles.aside} ref={ref} />;
|
||||
},
|
||||
);
|
||||
AppContentShellAside.displayName = 'AppContentShellAside';
|
||||
|
||||
AppContentShell.Main = AppContentShellMain;
|
||||
AppContentShell.Aside = AppContentShellAside;
|
||||
|
||||
@@ -25,14 +25,13 @@ function TableVirtualizedListRow({ index, isScrolling, isVisible, style }) {
|
||||
export function TableVirtualizedListRows() {
|
||||
const {
|
||||
table: { page },
|
||||
props: { vListrowHeight, vListOverscanRowCount },
|
||||
props: { vListrowHeight, vListOverscanRowCount, windowScrollerProps },
|
||||
} = useContext(TableContext);
|
||||
|
||||
// Dashboard content pane.
|
||||
const dashboardContentPane = React.useMemo(
|
||||
() => document.querySelector(`.${CLASSES.DASHBOARD_CONTENT_PANE}`),
|
||||
[],
|
||||
);
|
||||
const scrollElement =
|
||||
windowScrollerProps?.scrollElement ||
|
||||
document.querySelector(`.${CLASSES.DASHBOARD_CONTENT_PANE}`);
|
||||
|
||||
const rowRenderer = React.useCallback(
|
||||
({ key, ...args }) => <TableVirtualizedListRow {...args} key={key} />,
|
||||
@@ -40,7 +39,7 @@ export function TableVirtualizedListRows() {
|
||||
);
|
||||
|
||||
return (
|
||||
<WindowScroller scrollElement={dashboardContentPane}>
|
||||
<WindowScroller scrollElement={scrollElement}>
|
||||
{({ height, isScrolling, onChildScroll, scrollTop }) => (
|
||||
<AutoSizer disableHeight>
|
||||
{({ width }) => (
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import React from 'react';
|
||||
import React, { forwardRef, Ref } from 'react';
|
||||
import { HTMLDivProps, Props } from '@blueprintjs/core';
|
||||
|
||||
export interface BoxProps extends Props, HTMLDivProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function Box({ className, ...rest }: BoxProps) {
|
||||
const Element = 'div';
|
||||
export const Box = forwardRef(
|
||||
({ className, ...rest }: BoxProps, ref: Ref<HTMLDivElement>) => {
|
||||
const Element = 'div';
|
||||
|
||||
return <Element className={className} {...rest} />;
|
||||
}
|
||||
return <Element className={className} ref={ref} {...rest} />;
|
||||
},
|
||||
);
|
||||
Box.displayName = '@bigcapital/Box';
|
||||
|
||||
10
packages/webapp/src/constants/query-keys/banking.ts
Normal file
10
packages/webapp/src/constants/query-keys/banking.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export const BANK_QUERY_KEY = {
|
||||
BANK_RULES: 'BANK_RULE',
|
||||
BANK_TRANSACTION_MATCHES: 'BANK_TRANSACTION_MATCHES',
|
||||
RECOGNIZED_BANK_TRANSACTION: 'RECOGNIZED_BANK_TRANSACTION',
|
||||
EXCLUDED_BANK_TRANSACTIONS_INFINITY: 'EXCLUDED_BANK_TRANSACTIONS_INFINITY',
|
||||
RECOGNIZED_BANK_TRANSACTIONS_INFINITY:
|
||||
'RECOGNIZED_BANK_TRANSACTIONS_INFINITY',
|
||||
BANK_ACCOUNT_SUMMARY_META: 'BANK_ACCOUNT_SUMMARY_META',
|
||||
AUTOFILL_CATEGORIZE_BANK_TRANSACTION: 'AUTOFILL_CATEGORIZE_BANK_TRANSACTION',
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
// @ts-nocheck
|
||||
import { useMemo } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { ContentTabs } from '@/components/ContentTabs/ContentTabs';
|
||||
import { useAccountTransactionsContext } from './AccountTransactionsProvider';
|
||||
@@ -8,15 +9,19 @@ const AccountContentTabs = styled(ContentTabs)`
|
||||
`;
|
||||
|
||||
export function AccountTransactionsFilterTabs() {
|
||||
const { filterTab, setFilterTab, currentAccount } =
|
||||
const { filterTab, setFilterTab, bankAccountMetaSummary, currentAccount } =
|
||||
useAccountTransactionsContext();
|
||||
|
||||
const handleChange = (value) => {
|
||||
setFilterTab(value);
|
||||
};
|
||||
|
||||
const hasUncategorizedTransx = Boolean(
|
||||
currentAccount.uncategorized_transactions,
|
||||
// Detarmines whether show the uncategorized transactions tab.
|
||||
const hasUncategorizedTransx = useMemo(
|
||||
() =>
|
||||
bankAccountMetaSummary?.totalUncategorizedTransactions > 0 ||
|
||||
bankAccountMetaSummary?.totalExcludedTransactions > 0,
|
||||
[bankAccountMetaSummary],
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -29,28 +29,41 @@ function AccountTransactionsListRoot({
|
||||
return (
|
||||
<AccountTransactionsProvider>
|
||||
<AppContentShell hideAside={!openMatchingTransactionAside}>
|
||||
<AppContentShell.Main>
|
||||
<AccountTransactionsActionsBar />
|
||||
<AccountTransactionsDetailsBar />
|
||||
<AccountTransactionsProgressBar />
|
||||
|
||||
<DashboardPageContent>
|
||||
<AccountTransactionsFilterTabs />
|
||||
|
||||
<Suspense fallback={<Spinner size={30} />}>
|
||||
<AccountTransactionsContent />
|
||||
</Suspense>
|
||||
</DashboardPageContent>
|
||||
</AppContentShell.Main>
|
||||
|
||||
<AppContentShell.Aside>
|
||||
<CategorizeTransactionAside />
|
||||
</AppContentShell.Aside>
|
||||
<AccountTransactionsMain />
|
||||
<AccountTransactionsAside />
|
||||
</AppContentShell>
|
||||
</AccountTransactionsProvider>
|
||||
);
|
||||
}
|
||||
|
||||
function AccountTransactionsMain() {
|
||||
const { setScrollableRef } = useAccountTransactionsContext();
|
||||
|
||||
return (
|
||||
<AppContentShell.Main ref={(e) => setScrollableRef(e)}>
|
||||
<AccountTransactionsActionsBar />
|
||||
<AccountTransactionsDetailsBar />
|
||||
<AccountTransactionsProgressBar />
|
||||
|
||||
<DashboardPageContent>
|
||||
<AccountTransactionsFilterTabs />
|
||||
|
||||
<Suspense fallback={<Spinner size={30} />}>
|
||||
<AccountTransactionsContent />
|
||||
</Suspense>
|
||||
</DashboardPageContent>
|
||||
</AppContentShell.Main>
|
||||
);
|
||||
}
|
||||
|
||||
function AccountTransactionsAside() {
|
||||
return (
|
||||
<AppContentShell.Aside>
|
||||
<CategorizeTransactionAside />
|
||||
</AppContentShell.Aside>
|
||||
);
|
||||
}
|
||||
|
||||
export default R.compose(
|
||||
withBanking(
|
||||
({ selectedUncategorizedTransactionId, openMatchingTransactionAside }) => ({
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import React, { useRef, useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { DashboardInsider } from '@/components';
|
||||
import { useCashflowAccounts, useAccount } from '@/hooks/query';
|
||||
@@ -41,6 +41,8 @@ function AccountTransactionsProvider({ query, ...props }) {
|
||||
isLoading: isBankAccountMetaSummaryLoading,
|
||||
} = useGetBankAccountSummaryMeta(accountId);
|
||||
|
||||
const [scrollableRef, setScrollableRef] = useState();
|
||||
|
||||
// Provider payload.
|
||||
const provider = {
|
||||
accountId,
|
||||
@@ -56,6 +58,9 @@ function AccountTransactionsProvider({ query, ...props }) {
|
||||
|
||||
filterTab,
|
||||
setFilterTab,
|
||||
|
||||
scrollableRef,
|
||||
setScrollableRef
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -16,6 +16,7 @@ import { TABLES } from '@/constants/tables';
|
||||
import { useMemorizedColumnsWidths } from '@/hooks';
|
||||
import { useExcludedTransactionsColumns } from './_utils';
|
||||
import { useExcludedTransactionsBoot } from './ExcludedTransactionsTableBoot';
|
||||
import { useAccountTransactionsContext } from '../AccountTransactionsProvider';
|
||||
|
||||
import { ActionsMenu } from './_components';
|
||||
import { useUnexcludeUncategorizedTransaction } from '@/hooks/query/bank-rules';
|
||||
@@ -37,6 +38,8 @@ function ExcludedTransactionsTableRoot({
|
||||
const { mutateAsync: unexcludeBankTransaction } =
|
||||
useUnexcludeUncategorizedTransaction();
|
||||
|
||||
const { scrollableRef } = useAccountTransactionsContext();
|
||||
|
||||
// Retrieve table columns.
|
||||
const columns = useExcludedTransactionsColumns();
|
||||
|
||||
@@ -97,6 +100,7 @@ function ExcludedTransactionsTableRoot({
|
||||
className="table-constrant"
|
||||
selectionColumn={true}
|
||||
onSelectedRowsChange={handleSelectedRowsChange}
|
||||
windowScrollerProps={{ scrollElement: scrollableRef }}
|
||||
payload={{
|
||||
onRestore: handleRestoreClick,
|
||||
}}
|
||||
|
||||
@@ -20,6 +20,7 @@ import { useRecognizedTransactionsBoot } from './RecognizedTransactionsTableBoot
|
||||
|
||||
import { ActionsMenu } from './_components';
|
||||
import { compose } from '@/utils';
|
||||
import { useAccountTransactionsContext } from '../AccountTransactionsProvider';
|
||||
import { useExcludeUncategorizedTransaction } from '@/hooks/query/bank-rules';
|
||||
import {
|
||||
WithBankingActionsProps,
|
||||
@@ -33,8 +34,8 @@ interface RecognizedTransactionsTableProps extends WithBankingActionsProps {}
|
||||
* Renders the recognized account transactions datatable.
|
||||
*/
|
||||
function RecognizedTransactionsTableRoot({
|
||||
// #withBanking
|
||||
setUncategorizedTransactionIdForMatching,
|
||||
// #withBankingActions
|
||||
setTransactionsToCategorizeSelected,
|
||||
}: RecognizedTransactionsTableProps) {
|
||||
const { mutateAsync: excludeBankTransaction } =
|
||||
useExcludeUncategorizedTransaction();
|
||||
@@ -49,9 +50,11 @@ function RecognizedTransactionsTableRoot({
|
||||
const [initialColumnsWidths, , handleColumnResizing] =
|
||||
useMemorizedColumnsWidths(TABLES.UNCATEGORIZED_ACCOUNT_TRANSACTIONS);
|
||||
|
||||
const { scrollableRef } = useAccountTransactionsContext();
|
||||
|
||||
// Handle cell click.
|
||||
const handleCellClick = (cell, event) => {
|
||||
setUncategorizedTransactionIdForMatching(
|
||||
setTransactionsToCategorizeSelected(
|
||||
cell.row.original.uncategorized_transaction_id,
|
||||
);
|
||||
};
|
||||
@@ -74,7 +77,7 @@ function RecognizedTransactionsTableRoot({
|
||||
|
||||
// Handles categorize button click.
|
||||
const handleCategorizeClick = (transaction) => {
|
||||
setUncategorizedTransactionIdForMatching(
|
||||
setTransactionsToCategorizeSelected(
|
||||
transaction.uncategorized_transaction_id,
|
||||
);
|
||||
};
|
||||
@@ -102,6 +105,7 @@ function RecognizedTransactionsTableRoot({
|
||||
vListOverscanRowCount={0}
|
||||
initialColumnsWidths={initialColumnsWidths}
|
||||
onColumnResizing={handleColumnResizing}
|
||||
windowScrollerProps={{ scrollElement: scrollableRef }}
|
||||
noResults={<RecognizedTransactionsTableNoResults />}
|
||||
className="table-constrant"
|
||||
payload={{
|
||||
|
||||
@@ -22,6 +22,7 @@ import { useMemorizedColumnsWidths } from '@/hooks';
|
||||
import { useAccountUncategorizedTransactionsContext } from '../AllTransactionsUncategorizedBoot';
|
||||
import { useExcludeUncategorizedTransaction } from '@/hooks/query/bank-rules';
|
||||
import { useAccountUncategorizedTransactionsColumns } from './hooks';
|
||||
import { useAccountTransactionsContext } from '../AccountTransactionsProvider';
|
||||
|
||||
import { compose } from '@/utils';
|
||||
import { withBanking } from '../../withBanking';
|
||||
@@ -48,6 +49,8 @@ function AccountTransactionsDataTable({
|
||||
// Retrieve table columns.
|
||||
const columns = useAccountUncategorizedTransactionsColumns();
|
||||
|
||||
const { scrollableRef } = useAccountTransactionsContext();
|
||||
|
||||
// Retrieve list context.
|
||||
const { uncategorizedTransactions, isUncategorizedTransactionsLoading } =
|
||||
useAccountUncategorizedTransactionsContext();
|
||||
@@ -71,6 +74,11 @@ function AccountTransactionsDataTable({
|
||||
const handleCategorizeBtnClick = (transaction) => {
|
||||
setUncategorizedTransactionIdForMatching(transaction.id);
|
||||
};
|
||||
// handles table selected rows change.
|
||||
const handleSelectedRowsChange = (selected) => {
|
||||
const transactionIds = selected.map((r) => r.original.id);
|
||||
setUncategorizedTransactionsSelected(transactionIds);
|
||||
};
|
||||
// Handle exclude transaction.
|
||||
const handleExcludeTransaction = (transaction) => {
|
||||
excludeTransaction(transaction.id)
|
||||
@@ -118,6 +126,8 @@ function AccountTransactionsDataTable({
|
||||
onExclude: handleExcludeTransaction,
|
||||
onCategorize: handleCategorizeBtnClick,
|
||||
}}
|
||||
onSelectedRowsChange={handleSelectedRowsChange}
|
||||
windowScrollerProps={{ scrollElement: scrollableRef }}
|
||||
className={clsx('table-constrant', styles.table, {
|
||||
[styles.showCategorizeColumn]: enableMultipleCategorization,
|
||||
})}
|
||||
|
||||
@@ -13,21 +13,12 @@ import {
|
||||
import useApiRequest from '../useRequest';
|
||||
import { transformToCamelCase } from '@/utils';
|
||||
import t from './types';
|
||||
import { BANK_QUERY_KEY } from '@/constants/query-keys/banking';
|
||||
|
||||
const QUERY_KEY = {
|
||||
BANK_RULES: 'BANK_RULE',
|
||||
BANK_TRANSACTION_MATCHES: 'BANK_TRANSACTION_MATCHES',
|
||||
RECOGNIZED_BANK_TRANSACTION: 'RECOGNIZED_BANK_TRANSACTION',
|
||||
EXCLUDED_BANK_TRANSACTIONS_INFINITY: 'EXCLUDED_BANK_TRANSACTIONS_INFINITY',
|
||||
RECOGNIZED_BANK_TRANSACTIONS_INFINITY:
|
||||
'RECOGNIZED_BANK_TRANSACTIONS_INFINITY',
|
||||
BANK_ACCOUNT_SUMMARY_META: 'BANK_ACCOUNT_SUMMARY_META',
|
||||
AUTOFILL_CATEGORIZE_BANK_TRANSACTION: 'AUTOFILL_CATEGORIZE_BANK_TRANSACTION',
|
||||
};
|
||||
|
||||
// Common cache invalidator.
|
||||
const commonInvalidateQueries = (query: QueryClient) => {
|
||||
query.invalidateQueries(QUERY_KEY.BANK_RULES);
|
||||
query.invalidateQueries(QUERY_KEY.RECOGNIZED_BANK_TRANSACTIONS_INFINITY);
|
||||
query.invalidateQueries(BANK_QUERY_KEY.BANK_RULES);
|
||||
query.invalidateQueries(BANK_QUERY_KEY.RECOGNIZED_BANK_TRANSACTIONS_INFINITY);
|
||||
};
|
||||
|
||||
interface CreateBankRuleValues {
|
||||
@@ -185,7 +176,7 @@ export function useDeleteBankRule(
|
||||
commonInvalidateQueries(queryClient);
|
||||
|
||||
queryClient.invalidateQueries(
|
||||
QUERY_KEY.RECOGNIZED_BANK_TRANSACTIONS_INFINITY,
|
||||
BANK_QUERY_KEY.RECOGNIZED_BANK_TRANSACTIONS_INFINITY,
|
||||
);
|
||||
queryClient.invalidateQueries([
|
||||
t.CASHFLOW_ACCOUNT_UNCATEGORIZED_TRANSACTIONS_INFINITY,
|
||||
@@ -209,7 +200,7 @@ export function useBankRules(
|
||||
const apiRequest = useApiRequest();
|
||||
|
||||
return useQuery<BankRulesResponse, Error>(
|
||||
[QUERY_KEY.BANK_RULES],
|
||||
[BANK_QUERY_KEY.BANK_RULES],
|
||||
() => apiRequest.get('/banking/rules').then((res) => res.data.bank_rules),
|
||||
{ ...options },
|
||||
);
|
||||
@@ -230,7 +221,7 @@ export function useBankRule(
|
||||
const apiRequest = useApiRequest();
|
||||
|
||||
return useQuery<GetBankRuleRes, Error>(
|
||||
[QUERY_KEY.BANK_RULES, bankRuleId],
|
||||
[BANK_QUERY_KEY.BANK_RULES, bankRuleId],
|
||||
() =>
|
||||
apiRequest
|
||||
.get(`/banking/rules/${bankRuleId}`)
|
||||
@@ -260,7 +251,7 @@ export function useGetBankTransactionsMatches(
|
||||
const apiRequest = useApiRequest();
|
||||
|
||||
return useQuery<GetBankTransactionsMatchesResponse, Error>(
|
||||
[QUERY_KEY.BANK_TRANSACTION_MATCHES, uncategorizeTransactionsIds],
|
||||
[BANK_QUERY_KEY.BANK_TRANSACTION_MATCHES, uncategorizeTransactionsIds],
|
||||
() =>
|
||||
apiRequest
|
||||
.get(`/cashflow/transactions/matches`, {
|
||||
@@ -273,7 +264,9 @@ export function useGetBankTransactionsMatches(
|
||||
|
||||
const onValidateExcludeUncategorizedTransaction = (queryClient) => {
|
||||
// Invalidate queries.
|
||||
queryClient.invalidateQueries(QUERY_KEY.EXCLUDED_BANK_TRANSACTIONS_INFINITY);
|
||||
queryClient.invalidateQueries(
|
||||
BANK_QUERY_KEY.EXCLUDED_BANK_TRANSACTIONS_INFINITY,
|
||||
);
|
||||
queryClient.invalidateQueries(
|
||||
t.CASHFLOW_ACCOUNT_UNCATEGORIZED_TRANSACTIONS_INFINITY,
|
||||
);
|
||||
@@ -282,7 +275,7 @@ const onValidateExcludeUncategorizedTransaction = (queryClient) => {
|
||||
queryClient.invalidateQueries(t.ACCOUNT);
|
||||
|
||||
// invalidate bank account summary.
|
||||
queryClient.invalidateQueries(QUERY_KEY.BANK_ACCOUNT_SUMMARY_META);
|
||||
queryClient.invalidateQueries(BANK_QUERY_KEY.BANK_ACCOUNT_SUMMARY_META);
|
||||
};
|
||||
|
||||
type ExcludeUncategorizedTransactionValue = number;
|
||||
@@ -319,6 +312,10 @@ export function useExcludeUncategorizedTransaction(
|
||||
{
|
||||
onSuccess: (res, id) => {
|
||||
onValidateExcludeUncategorizedTransaction(queryClient);
|
||||
queryClient.invalidateQueries([
|
||||
BANK_QUERY_KEY.BANK_ACCOUNT_SUMMARY_META,
|
||||
id,
|
||||
]);
|
||||
},
|
||||
...options,
|
||||
},
|
||||
@@ -360,6 +357,10 @@ export function useUnexcludeUncategorizedTransaction(
|
||||
{
|
||||
onSuccess: (res, id) => {
|
||||
onValidateExcludeUncategorizedTransaction(queryClient);
|
||||
queryClient.invalidateQueries([
|
||||
BANK_QUERY_KEY.BANK_ACCOUNT_SUMMARY_META,
|
||||
id,
|
||||
]);
|
||||
},
|
||||
...options,
|
||||
},
|
||||
@@ -483,7 +484,7 @@ export function useMatchUncategorizedTransaction(
|
||||
queryClient.invalidateQueries(t.ACCOUNT);
|
||||
|
||||
// Invalidate bank account summary.
|
||||
queryClient.invalidateQueries(QUERY_KEY.BANK_ACCOUNT_SUMMARY_META);
|
||||
queryClient.invalidateQueries(BANK_QUERY_KEY.BANK_ACCOUNT_SUMMARY_META);
|
||||
},
|
||||
...props,
|
||||
});
|
||||
@@ -529,7 +530,7 @@ export function useUnmatchMatchedUncategorizedTransaction(
|
||||
queryClient.invalidateQueries(t.ACCOUNT);
|
||||
|
||||
// Invalidate bank account summary.
|
||||
queryClient.invalidateQueries(QUERY_KEY.BANK_ACCOUNT_SUMMARY_META);
|
||||
queryClient.invalidateQueries(BANK_QUERY_KEY.BANK_ACCOUNT_SUMMARY_META);
|
||||
},
|
||||
...props,
|
||||
});
|
||||
@@ -550,7 +551,7 @@ export function useGetRecognizedBankTransaction(
|
||||
const apiRequest = useApiRequest();
|
||||
|
||||
return useQuery<GetRecognizedBankTransactionRes, Error>(
|
||||
[QUERY_KEY.RECOGNIZED_BANK_TRANSACTION, uncategorizedTransactionId],
|
||||
[BANK_QUERY_KEY.RECOGNIZED_BANK_TRANSACTION, uncategorizedTransactionId],
|
||||
() =>
|
||||
apiRequest
|
||||
.get(`/banking/recognized/transactions/${uncategorizedTransactionId}`)
|
||||
@@ -578,7 +579,7 @@ export function useGetBankAccountSummaryMeta(
|
||||
const apiRequest = useApiRequest();
|
||||
|
||||
return useQuery<GetBankAccountSummaryMetaRes, Error>(
|
||||
[QUERY_KEY.BANK_ACCOUNT_SUMMARY_META, bankAccountId],
|
||||
[BANK_QUERY_KEY.BANK_ACCOUNT_SUMMARY_META, bankAccountId],
|
||||
() =>
|
||||
apiRequest
|
||||
.get(`/banking/bank_accounts/${bankAccountId}/meta`)
|
||||
@@ -610,7 +611,7 @@ export function useGetAutofillCategorizeTransaction(
|
||||
|
||||
return useQuery<GetAutofillCategorizeTransaction, Error>(
|
||||
[
|
||||
QUERY_KEY.AUTOFILL_CATEGORIZE_BANK_TRANSACTION,
|
||||
BANK_QUERY_KEY.AUTOFILL_CATEGORIZE_BANK_TRANSACTION,
|
||||
uncategorizedTransactionIds,
|
||||
],
|
||||
() =>
|
||||
@@ -634,7 +635,7 @@ export function useRecognizedBankTransactionsInfinity(
|
||||
const apiRequest = useApiRequest();
|
||||
|
||||
return useInfiniteQuery(
|
||||
[QUERY_KEY.RECOGNIZED_BANK_TRANSACTIONS_INFINITY, query],
|
||||
[BANK_QUERY_KEY.RECOGNIZED_BANK_TRANSACTIONS_INFINITY, query],
|
||||
async ({ pageParam = 1 }) => {
|
||||
const response = await apiRequest.http({
|
||||
...axios,
|
||||
@@ -666,7 +667,7 @@ export function useExcludedBankTransactionsInfinity(
|
||||
const apiRequest = useApiRequest();
|
||||
|
||||
return useInfiniteQuery(
|
||||
[QUERY_KEY.EXCLUDED_BANK_TRANSACTIONS_INFINITY, query],
|
||||
[BANK_QUERY_KEY.EXCLUDED_BANK_TRANSACTIONS_INFINITY, query],
|
||||
async ({ pageParam = 1 }) => {
|
||||
const response = await apiRequest.http({
|
||||
...axios,
|
||||
|
||||
@@ -9,6 +9,7 @@ import useApiRequest from '../useRequest';
|
||||
import { transformToCamelCase } from '@/utils';
|
||||
import { downloadFile, useDownloadFile } from '../useDownloadFile';
|
||||
import T from './types';
|
||||
import { BANK_QUERY_KEY } from '@/constants/query-keys/banking';
|
||||
|
||||
const QueryKeys = {
|
||||
ImportPreview: 'ImportPreview',
|
||||
@@ -127,8 +128,8 @@ export const useSampleSheetImport = () => {
|
||||
|
||||
/**
|
||||
* Invalidates resources cached queries based on the given resource name,
|
||||
* @param queryClient
|
||||
* @param resource
|
||||
* @param queryClient
|
||||
* @param resource
|
||||
*/
|
||||
const invalidateResourcesOnImport = (
|
||||
queryClient: QueryClient,
|
||||
@@ -215,6 +216,7 @@ const invalidateResourcesOnImport = (
|
||||
T.CASHFLOW_ACCOUNT_UNCATEGORIZED_TRANSACTIONS_INFINITY,
|
||||
);
|
||||
queryClient.invalidateQueries(T.CASHFLOW_UNCAATEGORIZED_TRANSACTION);
|
||||
queryClient.invalidateQueries(BANK_QUERY_KEY.BANK_ACCOUNT_SUMMARY_META);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user