feat(banking): Filter uncategorized bank transactions

This commit is contained in:
Ahmed Bouhuolia
2024-08-11 18:34:45 +02:00
parent c7c021c969
commit df8b68fda6
7 changed files with 175 additions and 10 deletions

View File

@@ -167,11 +167,18 @@ export interface CategorizeTransactionAsExpenseDTO {
export interface IGetUncategorizedTransactionsQuery {
page?: number;
pageSize?: number;
minDate?: Date;
maxDate?: Date;
minAmount?: number;
maxAmount?: number;
}
export interface IGetRecognizedTransactionsQuery {
page?: number;
pageSize?: number;
accountId?: number;
}
minDate?: Date;
maxDate?: Date;
minAmount?: number;
maxAmount?: number;
}

View File

@@ -1,5 +1,5 @@
/* eslint-disable global-require */
import * as R from 'ramda';
import moment from 'moment';
import { Model, ModelOptions, QueryContext, mixin } from 'objection';
import TenantModel from 'models/TenantModel';
import ModelSettings from './ModelSetting';
@@ -46,7 +46,7 @@ export default class UncategorizedCashflowTransaction extends mixin(
'isDepositTransaction',
'isWithdrawalTransaction',
'isRecognized',
'isExcluded'
'isExcluded',
];
}
@@ -143,6 +143,42 @@ export default class UncategorizedCashflowTransaction extends mixin(
query.whereNull('categorizeRefType');
query.whereNull('categorizeRefId');
},
/**
* Filters the not pending transactions.
*/
notPending(query) {
query.where('pending', false);
},
/**
* Filters the pending transactions.
*/
pending(query) {
query.where('pending', true);
},
minAmount(query, minAmount) {
query.where('amount', '>=', minAmount);
},
maxAmount(query, maxAmount) {
query.where('amount', '<=', maxAmount);
},
toDate(query, toDate) {
const dateFormat = 'YYYY-MM-DD';
const _toDate = moment(toDate).endOf('day').format(dateFormat);
query.where('date', '<=', _toDate);
},
fromDate(query, fromDate) {
const dateFormat = 'YYYY-MM-DD';
const _fromDate = moment(fromDate).startOf('day').format(dateFormat);
query.where('date', '>=', _fromDate);
},
};
}

View File

@@ -1,5 +1,6 @@
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { Inject, Service } from 'typedi';
import moment from 'moment';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { ExcludedBankTransactionsQuery } from './_types';
import { UncategorizedTransactionTransformer } from '@/services/Cashflow/UncategorizedTransactionTransformer';
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
@@ -39,6 +40,18 @@ export class GetExcludedBankTransactionsService {
if (_query.accountId) {
q.where('account_id', _query.accountId);
}
if (_query.minDate) {
q.modify('fromDate', _query.minDate);
}
if (_query.maxDate) {
q.modify('toDate', _query.maxDate);
}
if (_query.minAmount) {
q.modify('minAmount', _query.minAmount);
}
if (_query.maxAmount) {
q.modify('maxAmount', _query.maxAmount);
}
})
.pagination(_query.page - 1, _query.pageSize);

View File

@@ -4,6 +4,10 @@ export interface ExcludedBankTransactionsQuery {
page?: number;
pageSize?: number;
accountId?: number;
minDate?: Date;
maxDate?: Date;
minAmount?: number;
maxAmount?: number;
}
export interface IBankTransactionUnexcludingEventPayload {

View File

@@ -23,7 +23,7 @@ export class GetRecognizedTransactionsService {
) {
const { UncategorizedCashflowTransaction } = this.tenancy.models(tenantId);
const _filter = {
const _query = {
page: 1,
pageSize: 20,
...filter,
@@ -36,11 +36,23 @@ export class GetRecognizedTransactionsService {
q.whereNotNull('recognizedTransactionId');
q.modify('notExcluded');
if (_filter.accountId) {
q.where('accountId', _filter.accountId);
if (_query.accountId) {
q.where('accountId', _query.accountId);
}
if (_query.minDate) {
q.modify('fromDate', _query.minDate);
}
if (_query.maxDate) {
q.modify('toDate', _query.maxDate);
}
if (_query.minAmount) {
q.modify('minAmount', _query.minAmount);
}
if (_query.maxAmount) {
q.modify('maxAmount', _query.maxAmount);
}
})
.pagination(_filter.page - 1, _filter.pageSize);
.pagination(_query.page - 1, _query.pageSize);
const data = await this.transformer.transform(
tenantId,

View File

@@ -60,6 +60,19 @@ export class GetUncategorizedTransactions {
q.whereNull('matchedBankTransactions.id');
q.orderBy('date', 'DESC');
if (_query.minDate) {
q.modify('fromDate', _query.minDate);
}
if (_query.maxDate) {
q.modify('toDate', _query.maxDate);
}
if (_query.minAmount) {
q.modify('minAmount', _query.minAmount);
}
if (_query.maxAmount) {
q.modify('maxAmount', _query.maxAmount);
}
})
.pagination(_query.page - 1, _query.pageSize);

View File

@@ -1,8 +1,21 @@
// @ts-nocheck
import * as R from 'ramda';
import * as Yup from 'yup';
import { useMemo } from 'react';
import { useAppQueryString } from '@/hooks';
import { Group } from '@/components';
import { Box, FDateInput, FFormGroup, Group, Icon, Stack } from '@/components';
import { useAccountTransactionsContext } from './AccountTransactionsProvider';
import { TagsControl } from '@/components/TagsControl';
import {
Button,
Classes,
FormGroup,
Intent,
Popover,
Position,
} from '@blueprintjs/core';
import { DateInput } from '@blueprintjs/datetime';
import { Form, Formik } from 'formik';
export function AccountTransactionsUncategorizeFilter() {
const { bankAccountMetaSummary } = useAccountTransactionsContext();
@@ -48,3 +61,70 @@ export function AccountTransactionsUncategorizeFilter() {
</Group>
);
}
const initialValues = {
fromDate: '',
toDate: '',
};
const validationSchema = Yup.object().shape({
fromDate: Yup.date()
.nullable()
.required('From Date is required')
.max(Yup.ref('toDate'), 'From Date cannot be after To Date'),
toDate: Yup.date()
.nullable()
.required('To Date is required')
.min(Yup.ref('fromDate'), 'To Date cannot be before From Date'),
});
function UncategorizedTransactionsDateFilter() {
const handleSubmit = () => {};
return (
<Formik
initialValues={initialValues}
onSubmit={handleSubmit}
validationSchema={validationSchema}
>
<Form>
<Stack>
<Group>
<FFormGroup name={'fromDate'} label={'From Date'}>
<FDateInput
name={'fromDate'}
popoverProps={{ position: Position.BOTTOM, minimal: true }}
formatDate={(date) => date.toLocaleDateString()}
parseDate={(str) => new Date(str)}
inputProps={{
fill: true,
leftElement: <Icon icon={'date-range'} />,
}}
style={{ marginBottom: 0 }}
/>
</FFormGroup>
<FormGroup label={'To Date'} name={'toDate'}>
<FDateInput
name={'toDate'}
popoverProps={{ position: Position.BOTTOM, minimal: true }}
formatDate={(date) => date.toLocaleDateString()}
parseDate={(str) => new Date(str)}
inputProps={{
fill: true,
leftElement: <Icon icon={'date-range'} />,
}}
style={{ marginBottom: 0 }}
/>
</FormGroup>
</Group>
<Group spacing={10}>
<Button intent={Intent.PRIMARY}>Filter</Button>
<Button>Cancel</Button>
</Group>
</Stack>
</Form>
</Formik>
);
}