feat: wip bank transaction matching

This commit is contained in:
Ahmed Bouhuolia
2024-07-03 18:49:21 +02:00
parent 67b519db61
commit a5eb42edaf
16 changed files with 39 additions and 118 deletions

View File

@@ -1,30 +0,0 @@
import { body } from 'express-validator';
import BaseController from '../BaseController';
import { Router } from 'express';
import { Service } from 'typedi';
@Service()
export class BankReconcileController extends BaseController {
/**
* Router constructor.
*/
public router() {
const router = Router();
router.post(
'/',
[
body('amount').exists(),
body('date').exists(),
body('fromAccountId').exists(),
body('toAccountId').exists(),
body('reference').optional({ nullable: true }),
],
this.validationResult,
this.createBankReconcileTransaction.bind(this)
);
return router;
}
createBankReconcileTransaction() {}
}

View File

@@ -24,12 +24,14 @@ export class GetBankAccountSummary {
.findById(bankAccountId) .findById(bankAccountId)
.throwIfNotFound(); .throwIfNotFound();
// Retrieves the uncategorized transactions count of the given bank account.
const uncategorizedTranasctionsCount = const uncategorizedTranasctionsCount =
await UncategorizedCashflowTransaction.query() await UncategorizedCashflowTransaction.query()
.where('accountId', bankAccountId) .where('accountId', bankAccountId)
.count('id as total') .count('id as total')
.first(); .first();
// Retrieves the recognized transactions count of the given bank account.
const recognizedTransactionsCount = await RecognizedBankTransaction.query() const recognizedTransactionsCount = await RecognizedBankTransaction.query()
.whereExists( .whereExists(
UncategorizedCashflowTransaction.query().where( UncategorizedCashflowTransaction.query().where(

View File

@@ -100,7 +100,6 @@ export class GetMatchedTransactions {
moment(match.date).isSame(uncategorizedTransaction.date, 'day'), moment(match.date).isSame(uncategorizedTransaction.date, 'day'),
closestResullts closestResullts
); );
const possibleMatches = R.difference(closestResullts, perfectMatches); const possibleMatches = R.difference(closestResullts, perfectMatches);
return { perfectMatches, possibleMatches }; return { perfectMatches, possibleMatches };

View File

@@ -8,7 +8,6 @@ import {
} from './types'; } from './types';
import { Inject, Service } from 'typedi'; import { Inject, Service } from 'typedi';
// @Service()
export abstract class GetMatchedTransactionsByType { export abstract class GetMatchedTransactionsByType {
@Inject() @Inject()
protected tenancy: HasTenancyService; protected tenancy: HasTenancyService;
@@ -17,6 +16,7 @@ export abstract class GetMatchedTransactionsByType {
* Retrieves the matched transactions. * Retrieves the matched transactions.
* @param {number} tenantId - * @param {number} tenantId -
* @param {GetMatchedTransactionsFilter} filter - * @param {GetMatchedTransactionsFilter} filter -
* @returns {Promise<MatchedTransactionsPOJO>}
*/ */
public async getMatchedTransactions( public async getMatchedTransactions(
tenantId: number, tenantId: number,
@@ -31,6 +31,7 @@ export abstract class GetMatchedTransactionsByType {
* Retrieves the matched transaction details. * Retrieves the matched transaction details.
* @param {number} tenantId - * @param {number} tenantId -
* @param {number} transactionId - * @param {number} transactionId -
* @returns {Promise<MatchedTransactionPOJO>}
*/ */
public async getMatchedTransaction( public async getMatchedTransaction(
tenantId: number, tenantId: number,

View File

@@ -1,25 +1,25 @@
import React from 'react'; import React from 'react';
import { AppShellProvider, useAppShellContext } from './AppShellProvider'; import { AppShellProvider, useAppShellContext } from './AppContentShellProvider';
import { Box } from '../Layout'; import { Box, BoxProps } from '../../Layout';
import styles from './AppShell.module.scss'; import styles from './AppShell.module.scss';
interface AppShellProps { interface AppContentShellProps {
topbarOffset?: number; topbarOffset?: number;
mainProps: any; mainProps?: BoxProps;
asideProps: any; asideProps?: BoxProps;
children: React.ReactNode; children: React.ReactNode;
hideAside?: boolean; hideAside?: boolean;
hideMain?: boolean; hideMain?: boolean;
} }
export function AppShell({ export function AppContentShell({
asideProps, asideProps,
mainProps, mainProps,
topbarOffset = 0, topbarOffset = 0,
hideAside = false, hideAside = false,
hideMain = false, hideMain = false,
...restProps ...restProps
}: AppShellProps) { }: AppContentShellProps) {
return ( return (
<AppShellProvider <AppShellProvider
mainProps={mainProps} mainProps={mainProps}
@@ -33,29 +33,29 @@ export function AppShell({
); );
} }
AppShell.Main = AppShellMain; interface AppContentShellMainProps extends BoxProps {}
AppShell.Aside = AppShellAside;
function AppShellMain({ ...props }) { function AppContentShellMain({ ...props }: AppContentShellMainProps) {
const { hideMain } = useAppShellContext(); const { hideMain } = useAppShellContext();
if (hideMain === true) { if (hideMain === true) {
return null; return null;
} }
return <Box {...props} className={styles.main} />; return <Box {...props} className={styles.main} />;
} }
interface AppShellAsideProps { interface AppContentShellAsideProps extends BoxProps {
children: React.ReactNode; children: React.ReactNode;
} }
function AppShellAside({ ...props }: AppShellAsideProps) { function AppContentShellAside({ ...props }: AppContentShellAsideProps) {
const { hideAside } = useAppShellContext(); const { hideAside } = useAppShellContext();
console.log(hideAside, 'hideAsidehideAsidehideAsidehideAside');
if (hideAside === true) { if (hideAside === true) {
return null; return null;
} }
return <Box {...props} className={styles.aside} />; return <Box {...props} className={styles.aside} />;
} }
AppContentShell.Main = AppContentShellMain;
AppContentShell.Aside = AppContentShellAside;

View File

@@ -0,0 +1 @@
export * from './AppContentShell';

View File

@@ -0,0 +1 @@
export * from './AppContentShell';

View File

@@ -3,7 +3,6 @@ import { Button, Classes, NavbarGroup } from '@blueprintjs/core';
import * as R from 'ramda'; import * as R from 'ramda';
import { Can, DashboardActionsBar, Icon } from '@/components'; import { Can, DashboardActionsBar, Icon } from '@/components';
import { AbilitySubject, BankRuleAction } from '@/constants/abilityOption'; import { AbilitySubject, BankRuleAction } from '@/constants/abilityOption';
import withAlertActions from '@/containers/Alert/withAlertActions';
import withDialogActions from '@/containers/Dialog/withDialogActions'; import withDialogActions from '@/containers/Dialog/withDialogActions';
import { DialogsName } from '@/constants/dialogs'; import { DialogsName } from '@/constants/dialogs';
@@ -31,7 +30,6 @@ function RulesListActionsBarRoot({
); );
} }
export const RulesListActionsBar = R.compose( export const RulesListActionsBar = R.compose(withDialogActions)(
withDialogActions, RulesListActionsBarRoot,
withAlertActions, );
)(RulesListActionsBarRoot);

View File

@@ -8,9 +8,7 @@ import {
} from '@/components'; } from '@/components';
import withAlertsActions from '@/containers/Alert/withAlertActions'; import withAlertsActions from '@/containers/Alert/withAlertActions';
import withDrawerActions from '@/containers/Drawer/withDrawerActions';
import withDialogActions from '@/containers/Dialog/withDialogActions'; import withDialogActions from '@/containers/Dialog/withDialogActions';
import withDashboardActions from '@/containers/Dashboard/withDashboardActions';
import { useBankRulesTableColumns } from './hooks'; import { useBankRulesTableColumns } from './hooks';
import { BankRulesTableActionsMenu } from './_components'; import { BankRulesTableActionsMenu } from './_components';
@@ -25,9 +23,6 @@ function RulesTable({
// #withAlertsActions // #withAlertsActions
openAlert, openAlert,
// #withDrawerActions
openDrawer,
// #withDialogAction // #withDialogAction
openDialog, openDialog,
}) { }) {
@@ -81,8 +76,6 @@ function RulesTable({
} }
export const BankRulesTable = R.compose( export const BankRulesTable = R.compose(
withDashboardActions,
withAlertsActions, withAlertsActions,
withDrawerActions,
withDialogActions, withDialogActions,
)(RulesTable); )(RulesTable);

View File

@@ -15,7 +15,7 @@ import {
import { AccountTransactionsDetailsBar } from './AccountTransactionsDetailsBar'; import { AccountTransactionsDetailsBar } from './AccountTransactionsDetailsBar';
import { AccountTransactionsProgressBar } from './components'; import { AccountTransactionsProgressBar } from './components';
import { AccountTransactionsFilterTabs } from './AccountTransactionsFilterTabs'; import { AccountTransactionsFilterTabs } from './AccountTransactionsFilterTabs';
import { AppShell } from '@/components/AppShell/AppShell'; import { AppContentShell } from '@/components/AppShell';
import { CategorizeTransactionAside } from '../CategorizeTransactionAside/CategorizeTransactionAside'; import { CategorizeTransactionAside } from '../CategorizeTransactionAside/CategorizeTransactionAside';
import { withBanking } from '../withBanking'; import { withBanking } from '../withBanking';
@@ -28,8 +28,8 @@ function AccountTransactionsListRoot({
}) { }) {
return ( return (
<AccountTransactionsProvider> <AccountTransactionsProvider>
<AppShell hideAside={!openMatchingTransactionAside}> <AppContentShell hideAside={!openMatchingTransactionAside}>
<AppShell.Main> <AppContentShell.Main>
<AccountTransactionsActionsBar /> <AccountTransactionsActionsBar />
<AccountTransactionsDetailsBar /> <AccountTransactionsDetailsBar />
<AccountTransactionsProgressBar /> <AccountTransactionsProgressBar />
@@ -41,12 +41,12 @@ function AccountTransactionsListRoot({
<AccountTransactionsContent /> <AccountTransactionsContent />
</Suspense> </Suspense>
</DashboardPageContent> </DashboardPageContent>
</AppShell.Main> </AppContentShell.Main>
<AppShell.Aside> <AppContentShell.Aside>
<CategorizeTransactionAside /> <CategorizeTransactionAside />
</AppShell.Aside> </AppContentShell.Aside>
</AppShell> </AppContentShell>
</AccountTransactionsProvider> </AccountTransactionsProvider>
); );
} }

View File

@@ -1,7 +1,7 @@
// @ts-nocheck // @ts-nocheck
import React from 'react'; import React from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import { Intent } from '@blueprintjs/core';
import { import {
DataTable, DataTable,
TableFastCell, TableFastCell,
@@ -14,7 +14,6 @@ import {
import { TABLES } from '@/constants/tables'; import { TABLES } from '@/constants/tables';
import withSettings from '@/containers/Settings/withSettings'; import withSettings from '@/containers/Settings/withSettings';
import withDrawerActions from '@/containers/Drawer/withDrawerActions';
import { withBankingActions } from '../withBankingActions'; import { withBankingActions } from '../withBankingActions';
import { useMemorizedColumnsWidths } from '@/hooks'; import { useMemorizedColumnsWidths } from '@/hooks';
@@ -26,7 +25,6 @@ import { useAccountUncategorizedTransactionsContext } from './AllTransactionsUnc
import { compose } from '@/utils'; import { compose } from '@/utils';
import { useExcludeUncategorizedTransaction } from '@/hooks/query/bank-rules'; import { useExcludeUncategorizedTransaction } from '@/hooks/query/bank-rules';
import { Intent } from '@blueprintjs/core';
/** /**
* Account transactions data table. * Account transactions data table.
@@ -37,9 +35,6 @@ function AccountTransactionsDataTable({
// #withBankingActions // #withBankingActions
setUncategorizedTransactionIdForMatching, setUncategorizedTransactionIdForMatching,
// #withDrawerActions
openDrawer,
}) { }) {
// Retrieve table columns. // Retrieve table columns.
const columns = useAccountUncategorizedTransactionsColumns(); const columns = useAccountUncategorizedTransactionsColumns();
@@ -102,7 +97,9 @@ function AccountTransactionsDataTable({
vListOverscanRowCount={0} vListOverscanRowCount={0}
initialColumnsWidths={initialColumnsWidths} initialColumnsWidths={initialColumnsWidths}
onColumnResizing={handleColumnResizing} onColumnResizing={handleColumnResizing}
noResults={'There is no uncategorized transactions in the current account.'} noResults={
'There is no uncategorized transactions in the current account.'
}
className="table-constrant" className="table-constrant"
payload={{ payload={{
onExclude: handleExcludeTransaction, onExclude: handleExcludeTransaction,
@@ -116,7 +113,6 @@ export default compose(
withSettings(({ cashflowTransactionsSettings }) => ({ withSettings(({ cashflowTransactionsSettings }) => ({
cashflowTansactionsTableSize: cashflowTransactionsSettings?.tableSize, cashflowTansactionsTableSize: cashflowTransactionsSettings?.tableSize,
})), })),
withDrawerActions,
withBankingActions, withBankingActions,
)(AccountTransactionsDataTable); )(AccountTransactionsDataTable);

View File

@@ -1,37 +0,0 @@
import React from 'react';
interface UncategorizedTransactionsFilterValue {}
const UncategorizedTransactionsFilterContext =
React.createContext<UncategorizedTransactionsFilterValue>(
{} as UncategorizedTransactionsFilterValue,
);
interface UncategorizedTransactionsFilterProviderProps {
children: React.ReactNode;
}
/**
*
*/
function UncategorizedTransactionsFilterProvider({
...props
}: UncategorizedTransactionsFilterProviderProps) {
// Provider payload.
const provider = {};
return (
<UncategorizedTransactionsFilterContext.Provider
value={provider}
{...props}
/>
);
}
const useUncategorizedTransactionsFilter = () =>
React.useContext(UncategorizedTransactionsFilterContext);
export {
UncategorizedTransactionsFilterProvider,
useUncategorizedTransactionsFilter,
};

View File

@@ -6,7 +6,6 @@ import * as R from 'ramda';
import '@/style/pages/CashFlow/AccountTransactions/List.scss'; import '@/style/pages/CashFlow/AccountTransactions/List.scss';
import { AccountTransactionsUncategorizeFilter } from './AccountTransactionsUncategorizeFilter'; import { AccountTransactionsUncategorizeFilter } from './AccountTransactionsUncategorizeFilter';
import { UncategorizedTransactionsFilterProvider } from './AccountUncategorizedTransactionsFilterProvider';
import { import {
WithBankingActionsProps, WithBankingActionsProps,
withBankingActions, withBankingActions,
@@ -23,6 +22,7 @@ function AllTransactionsUncategorizedRoot({
// #withBankingActions // #withBankingActions
closeMatchingTransactionAside, closeMatchingTransactionAside,
}: AllTransactionsUncategorizedProps) { }: AllTransactionsUncategorizedProps) {
// Close the match aside once leaving the page.
useEffect( useEffect(
() => () => { () => () => {
closeMatchingTransactionAside(); closeMatchingTransactionAside();
@@ -31,12 +31,10 @@ function AllTransactionsUncategorizedRoot({
); );
return ( return (
<UncategorizedTransactionsFilterProvider> <Box>
<Box> <AccountTransactionsUncategorizeFilter />
<AccountTransactionsUncategorizeFilter /> <AccountTransactionsSwitcher />
<AccountTransactionsSwitcher /> </Box>
</Box>
</UncategorizedTransactionsFilterProvider>
); );
} }

View File

@@ -14,7 +14,6 @@ import {
} from '@/components'; } from '@/components';
import { TABLES } from '@/constants/tables'; import { TABLES } from '@/constants/tables';
import withSettings from '@/containers/Settings/withSettings';
import withAlertsActions from '@/containers/Alert/withAlertActions'; import withAlertsActions from '@/containers/Alert/withAlertActions';
import withDrawerActions from '@/containers/Drawer/withDrawerActions'; import withDrawerActions from '@/containers/Drawer/withDrawerActions';