feat: wip matching bank transactions

This commit is contained in:
Ahmed Bouhuolia
2024-07-01 12:02:59 +02:00
parent da0fab9a58
commit c27458ebcc
12 changed files with 107 additions and 15 deletions

View File

@@ -89,6 +89,27 @@ export default class UncategorizedCashflowTransaction extends mixin(
return !!this.recognizedTransactionId; return !!this.recognizedTransactionId;
} }
/**
* Model modifiers.
*/
static get modifiers() {
return {
/**
* Filters the not excluded transactions.
*/
notExcluded(query) {
query.whereNull('excluded');
},
/**
* Filters the excluded transactions.
*/
excluded(query) {
query.where('excluded', true)
}
};
},
/** /**
* Relationship mapping. * Relationship mapping.
*/ */

View File

@@ -34,7 +34,7 @@ export class UnexcludeBankTransaction {
await UncategorizedCashflowTransaction.query(trx) await UncategorizedCashflowTransaction.query(trx)
.findById(uncategorizedTransactionId) .findById(uncategorizedTransactionId)
.patch({ .patch({
excluded: false, excluded: null,
}); });
}); });
} }

View File

@@ -1,7 +1,7 @@
import { Inject, Service } from 'typedi'; import { Inject, Service } from 'typedi';
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable'; import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
import { GetMatchedTransactionBillsTransformer } from './GetMatchedTransactionBillsTransformer'; import { GetMatchedTransactionBillsTransformer } from './GetMatchedTransactionBillsTransformer';
import { GetMatchedTransactionsFilter } from './types'; import { GetMatchedTransactionsFilter, MatchedTransactionPOJO } from './types';
import HasTenancyService from '@/services/Tenancy/TenancyService'; import HasTenancyService from '@/services/Tenancy/TenancyService';
import { GetMatchedTransactionsByType } from './GetMatchedTransactionsByType'; import { GetMatchedTransactionsByType } from './GetMatchedTransactionsByType';
@@ -24,7 +24,9 @@ export class GetMatchedTransactionsByBills extends GetMatchedTransactionsByType
) { ) {
const { Bill } = this.tenancy.models(tenantId); const { Bill } = this.tenancy.models(tenantId);
const bills = await Bill.query(); const bills = await Bill.query().onBuild((q) => {
q.whereNotExists(Bill.relatedQuery('matchedBankTransaction'));
});
return this.transformer.transform( return this.transformer.transform(
tenantId, tenantId,
@@ -32,4 +34,25 @@ export class GetMatchedTransactionsByBills extends GetMatchedTransactionsByType
new GetMatchedTransactionBillsTransformer() new GetMatchedTransactionBillsTransformer()
); );
} }
/**
* Retrieves the given bill matched transaction.
* @param {number} tenantId
* @param {number} transactionId
* @returns {Promise<MatchedTransactionPOJO>}
*/
public async getMatchedTransaction(
tenantId: number,
transactionId: number
): Promise<MatchedTransactionPOJO> {
const { Bill } = this.tenancy.models(tenantId);
const bill = await Bill.query().findById(transactionId).throwIfNotFound();
return this.transformer.transform(
tenantId,
bill,
new GetMatchedTransactionBillsTransformer()
);
}
} }

View File

@@ -1,5 +1,5 @@
import { Inject, Service } from 'typedi'; import { Inject, Service } from 'typedi';
import { GetMatchedTransactionsFilter } from './types'; import { GetMatchedTransactionsFilter, MatchedTransactionPOJO } from './types';
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable'; import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
import HasTenancyService from '@/services/Tenancy/TenancyService'; import HasTenancyService from '@/services/Tenancy/TenancyService';
import { GetMatchedTransactionsByType } from './GetMatchedTransactionsByType'; import { GetMatchedTransactionsByType } from './GetMatchedTransactionsByType';
@@ -46,4 +46,27 @@ export class GetMatchedTransactionsByExpenses extends GetMatchedTransactionsByTy
new GetMatchedTransactionExpensesTransformer() new GetMatchedTransactionExpensesTransformer()
); );
} }
/**
* Retrieves the given matched expense transaction.
* @param {number} tenantId
* @param {number} transactionId
* @returns {GetMatchedTransactionExpensesTransformer-}
*/
public async getMatchedTransaction(
tenantId: number,
transactionId: number
): Promise<MatchedTransactionPOJO> {
const { Expense } = this.tenancy.models(tenantId);
const expense = await Expense.query()
.findById(transactionId)
.throwIfNotFound();
return this.transformer.transform(
tenantId,
expense,
new GetMatchedTransactionExpensesTransformer()
);
}
} }

View File

@@ -29,7 +29,9 @@ export class GetMatchedTransactionsByInvoices extends GetMatchedTransactionsByTy
): Promise<MatchedTransactionsPOJO> { ): Promise<MatchedTransactionsPOJO> {
const { SaleInvoice } = this.tenancy.models(tenantId); const { SaleInvoice } = this.tenancy.models(tenantId);
const invoices = await SaleInvoice.query(); const invoices = await SaleInvoice.query().onBuild((q) => {
q.whereNotExists(SaleInvoice.relatedQuery('matchedBankTransaction'));
});
return this.transformer.transform( return this.transformer.transform(
tenantId, tenantId,

View File

@@ -34,6 +34,7 @@ export class GetUncategorizedTransactions {
await UncategorizedCashflowTransaction.query() await UncategorizedCashflowTransaction.query()
.where('accountId', accountId) .where('accountId', accountId)
.where('categorized', false) .where('categorized', false)
.modify('notExcluded')
.withGraphFetched('account') .withGraphFetched('account')
.orderBy('date', 'DESC') .orderBy('date', 'DESC')
.pagination(_query.page - 1, _query.pageSize); .pagination(_query.page - 1, _query.pageSize);

View File

@@ -1,11 +1,16 @@
// @ts-nocheck // @ts-nocheck
import { Menu, MenuItem, MenuDivider } from '@blueprintjs/core'; import { Menu, MenuItem, MenuDivider } from '@blueprintjs/core';
import { safeCallback } from '@/utils'; import { safeCallback } from '@/utils';
import { Icon } from '@/components';
export function ActionsMenu({ payload: { onRestore }, row: { original } }) { export function ActionsMenu({ payload: { onRestore }, row: { original } }) {
return ( return (
<Menu> <Menu>
<MenuItem text={'Restore'} onClick={safeCallback(onRestore, original)} /> <MenuItem
text={'Restore'}
icon={<Icon icon="redo" iconSize={16} />}
onClick={safeCallback(onRestore, original)}
/>
</Menu> </Menu>
); );
} }

View File

@@ -40,7 +40,7 @@ export function ActionsMenu({
<MenuItem <MenuItem
text={'Exclude'} text={'Exclude'}
onClick={safeCallback(onExclude, original)} onClick={safeCallback(onExclude, original)}
// icon={<Icon icon="trash-16" iconSize={16} />} icon={<Icon icon="disable" iconSize={16} />}
/> />
</Menu> </Menu>
); );

View File

@@ -1,29 +1,28 @@
// @ts-nocheck // @ts-nocheck
import { Formik, Form } from 'formik'; import { Formik, Form } from 'formik';
import { Intent } from '@blueprintjs/core';
import styled from 'styled-components'; import styled from 'styled-components';
import { CreateCategorizeTransactionSchema } from './CategorizeTransactionForm.schema'; import { CreateCategorizeTransactionSchema } from './CategorizeTransactionForm.schema';
import { CategorizeTransactionFormContent } from './CategorizeTransactionFormContent'; import { CategorizeTransactionFormContent } from './CategorizeTransactionFormContent';
import { CategorizeTransactionFormFooter } from './CategorizeTransactionFormFooter'; import { CategorizeTransactionFormFooter } from './CategorizeTransactionFormFooter';
import { useCategorizeTransaction } from '@/hooks/query'; import { useCategorizeTransaction } from '@/hooks/query';
import { useCategorizeTransactionBoot } from './CategorizeTransactionBoot'; import { useCategorizeTransactionBoot } from './CategorizeTransactionBoot';
import { DRAWERS } from '@/constants/drawers';
import { import {
transformToCategorizeForm, transformToCategorizeForm,
defaultInitialValues, defaultInitialValues,
tranformToRequest, tranformToRequest,
} from './_utils'; } from './_utils';
import { compose } from '@/utils'; import { compose } from '@/utils';
import withDrawerActions from '@/containers/Drawer/withDrawerActions'; import { withBankingActions } from '@/containers/CashFlow/withBankingActions';
import { AppToaster } from '@/components'; import { AppToaster } from '@/components';
import { Intent } from '@blueprintjs/core';
import { useCategorizeTransactionTabsBoot } from '@/containers/CashFlow/CategorizeTransactionAside/CategorizeTransactionTabsBoot'; import { useCategorizeTransactionTabsBoot } from '@/containers/CashFlow/CategorizeTransactionAside/CategorizeTransactionTabsBoot';
/** /**
* Categorize cashflow transaction form dialog content. * Categorize cashflow transaction form dialog content.
*/ */
function CategorizeTransactionFormRoot({ function CategorizeTransactionFormRoot({
// #withDrawerActions // #withBankingActions
closeDrawer, closeMatchingTransactionAside,
}) { }) {
const { uncategorizedTransactionId, uncategorizedTransaction } = const { uncategorizedTransactionId, uncategorizedTransaction } =
useCategorizeTransactionTabsBoot(); useCategorizeTransactionTabsBoot();
@@ -38,12 +37,12 @@ function CategorizeTransactionFormRoot({
categorizeTransaction([uncategorizedTransactionId, transformedValues]) categorizeTransaction([uncategorizedTransactionId, transformedValues])
.then(() => { .then(() => {
setSubmitting(false); setSubmitting(false);
closeDrawer(DRAWERS.CATEGORIZE_TRANSACTION);
AppToaster.show({ AppToaster.show({
message: 'The uncategorized transaction has been categorized.', message: 'The uncategorized transaction has been categorized.',
intent: Intent.SUCCESS, intent: Intent.SUCCESS,
}); });
closeMatchingTransactionAside();
}) })
.catch((err) => { .catch((err) => {
setSubmitting(false); setSubmitting(false);
@@ -93,7 +92,7 @@ function CategorizeTransactionFormRoot({
); );
} }
export const CategorizeTransactionForm = compose(withDrawerActions)( export const CategorizeTransactionForm = compose(withBankingActions)(
CategorizeTransactionFormRoot, CategorizeTransactionFormRoot,
); );

View File

@@ -38,7 +38,7 @@ export function MatchingBankTransaction() {
}); });
return; return;
} }
matchTransaction({ id: uncategorizedTransactionId, values: _values }) matchTransaction({ id: uncategorizedTransactionId, value: _values })
.then(() => { .then(() => {
AppToaster.show({ AppToaster.show({
intent: Intent.SUCCESS, intent: Intent.SUCCESS,

View File

@@ -223,6 +223,9 @@ export function useExcludeUncategorizedTransaction(
queryClient.invalidateQueries( queryClient.invalidateQueries(
QUERY_KEY.EXCLUDED_BANK_TRANSACTIONS_INFINITY, QUERY_KEY.EXCLUDED_BANK_TRANSACTIONS_INFINITY,
); );
queryClient.invalidateQueries(
t.CASHFLOW_ACCOUNT_UNCATEGORIZED_TRANSACTIONS_INFINITY,
);
}, },
...options, ...options,
}, },
@@ -267,6 +270,9 @@ export function useUnexcludeUncategorizedTransaction(
queryClient.invalidateQueries( queryClient.invalidateQueries(
QUERY_KEY.EXCLUDED_BANK_TRANSACTIONS_INFINITY, QUERY_KEY.EXCLUDED_BANK_TRANSACTIONS_INFINITY,
); );
queryClient.invalidateQueries(
t.CASHFLOW_ACCOUNT_UNCATEGORIZED_TRANSACTIONS_INFINITY,
);
}, },
...options, ...options,
}, },

View File

@@ -617,4 +617,16 @@ export default {
], ],
viewBox: '0 0 16 16', viewBox: '0 0 16 16',
}, },
disable: {
path: [
'M7.99-0.01c-4.42,0-8,3.58-8,8s3.58,8,8,8s8-3.58,8-8S12.41-0.01,7.99-0.01zM1.99,7.99c0-3.31,2.69-6,6-6c1.3,0,2.49,0.42,3.47,1.12l-8.35,8.35C2.41,10.48,1.99,9.29,1.99,7.99z M7.99,13.99c-1.3,0-2.49-0.42-3.47-1.12l8.35-8.35c0.7,0.98,1.12,2.17,1.12,3.47C13.99,11.31,11.31,13.99,7.99,13.99z',
],
viewBox: '0 0 16 16',
},
redo: {
path: [
'M4,11c-1.1,0-2,0.9-2,2s0.9,2,2,2s2-0.9,2-2S5.1,11,4,11z M11,4H3.41l1.29-1.29C4.89,2.53,5,2.28,5,2c0-0.55-0.45-1-1-1C3.72,1,3.47,1.11,3.29,1.29l-3,3C0.11,4.47,0,4.72,0,5c0,0.28,0.11,0.53,0.29,0.71l3,3C3.47,8.89,3.72,9,4,9c0.55,0,1-0.45,1-1c0-0.28-0.11-0.53-0.29-0.71L3.41,6H11c1.66,0,3,1.34,3,3s-1.34,3-3,3H7v2h4c2.76,0,5-2.24,5-5S13.76,4,11,4z',
],
viewBox: '0 0 16 16',
},
}; };