mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-17 13:20:31 +00:00
feat: excluded bank transactions
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { param } from 'express-validator';
|
||||
import { NextFunction, Request, Response, Router } from 'express';
|
||||
import { NextFunction, Request, Response, Router, query } from 'express';
|
||||
import BaseController from '../BaseController';
|
||||
import { ExcludeBankTransactionsApplication } from '@/services/Banking/Exclude/ExcludeBankTransactionsApplication';
|
||||
|
||||
@@ -27,6 +27,12 @@ export class ExcludeBankTransactionsController extends BaseController {
|
||||
this.validationResult,
|
||||
this.unexcludeBankTransaction.bind(this)
|
||||
);
|
||||
router.get(
|
||||
'/excluded',
|
||||
[],
|
||||
this.validationResult,
|
||||
this.getExcludedBankTransactions.bind(this)
|
||||
);
|
||||
return router;
|
||||
}
|
||||
|
||||
@@ -87,4 +93,32 @@ export class ExcludeBankTransactionsController extends BaseController {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the excluded uncategorized bank transactions.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
* @returns {Promise<Response|null>}
|
||||
*/
|
||||
private async getExcludedBankTransactions(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<Response | void> {
|
||||
const { tenantId } = req;
|
||||
const filter = this.matchedBodyData(req);
|
||||
|
||||
console.log('123');
|
||||
try {
|
||||
const data =
|
||||
await this.excludeBankTransactionApp.getExcludedBankTransactions(
|
||||
tenantId,
|
||||
filter
|
||||
);
|
||||
return res.status(200).send(data);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,14 +14,11 @@ export class RecognizedTransactionsController extends BaseController {
|
||||
router() {
|
||||
const router = Router();
|
||||
|
||||
router.get(
|
||||
'/accounts/:accountId',
|
||||
this.getRecognizedTransactions.bind(this)
|
||||
);
|
||||
router.get('/', this.getRecognizedTransactions.bind(this));
|
||||
|
||||
return router;
|
||||
}
|
||||
k;
|
||||
|
||||
/**
|
||||
* Retrieves the recognized bank transactions.
|
||||
* @param {Request} req
|
||||
@@ -34,15 +31,15 @@ export class RecognizedTransactionsController extends BaseController {
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
const { accountId } = req.params;
|
||||
const filter = this.matchedQueryData(req);
|
||||
const { tenantId } = req;
|
||||
|
||||
try {
|
||||
const data = await this.cashflowApplication.getRecognizedTransactions(
|
||||
tenantId,
|
||||
accountId
|
||||
filter
|
||||
);
|
||||
return res.status(200).send({ data });
|
||||
return res.status(200).send(data);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
|
||||
@@ -164,3 +164,10 @@ export interface IGetUncategorizedTransactionsQuery {
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
}
|
||||
|
||||
|
||||
export interface IGetRecognizedTransactionsQuery {
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
accountId?: number;
|
||||
}
|
||||
@@ -29,6 +29,7 @@ export class RecognizedBankTransaction extends TenantModel {
|
||||
static get relationMappings() {
|
||||
const UncategorizedCashflowTransaction = require('./UncategorizedCashflowTransaction');
|
||||
const Account = require('./Account');
|
||||
const { BankRule } = require('./BankRule');
|
||||
|
||||
return {
|
||||
/**
|
||||
@@ -54,6 +55,18 @@ export class RecognizedBankTransaction extends TenantModel {
|
||||
to: 'accounts.id',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Recognized bank transaction may belongs to bank rule.
|
||||
*/
|
||||
bankRule: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: BankRule,
|
||||
join: {
|
||||
from: 'recognized_bank_transactions.bankRuleId',
|
||||
to: 'bank_rules.id',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { ExcludeBankTransaction } from './ExcludeBankTransaction';
|
||||
import { UnexcludeBankTransaction } from './UnexcludeBankTransaction';
|
||||
import { GetExcludedBankTransactionsService } from './GetExcludedBankTransactions';
|
||||
import { ExcludedBankTransactionsQuery } from './_types';
|
||||
|
||||
@Service()
|
||||
export class ExcludeBankTransactionsApplication {
|
||||
@@ -10,6 +12,9 @@ export class ExcludeBankTransactionsApplication {
|
||||
@Inject()
|
||||
private unexcludeBankTransactionService: UnexcludeBankTransaction;
|
||||
|
||||
@Inject()
|
||||
private getExcludedBankTransactionsService: GetExcludedBankTransactionsService;
|
||||
|
||||
/**
|
||||
* Marks a bank transaction as excluded.
|
||||
* @param {number} tenantId - The ID of the tenant.
|
||||
@@ -35,4 +40,20 @@ export class ExcludeBankTransactionsApplication {
|
||||
bankTransactionId
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the excluded bank transactions.
|
||||
* @param {number} tenantId
|
||||
* @param {ExcludedBankTransactionsQuery} filter
|
||||
* @returns {}
|
||||
*/
|
||||
public getExcludedBankTransactions(
|
||||
tenantId: number,
|
||||
filter: ExcludedBankTransactionsQuery
|
||||
) {
|
||||
return this.getExcludedBankTransactionsService.getExcludedBankTransactions(
|
||||
tenantId,
|
||||
filter
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { ExcludedBankTransactionsQuery } from './_types';
|
||||
import { UncategorizedTransactionTransformer } from '@/services/Cashflow/UncategorizedTransactionTransformer';
|
||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||
|
||||
@Service()
|
||||
export class GetExcludedBankTransactionsService {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private transformer: TransformerInjectable;
|
||||
|
||||
/**
|
||||
* Retrieves the excluded uncategorized bank transactions.
|
||||
* @param {number} tenantId
|
||||
* @param {ExcludedBankTransactionsQuery} filter
|
||||
* @returns
|
||||
*/
|
||||
public async getExcludedBankTransactions(
|
||||
tenantId: number,
|
||||
filter: ExcludedBankTransactionsQuery
|
||||
) {
|
||||
const { UncategorizedCashflowTransaction } = this.tenancy.models(tenantId);
|
||||
|
||||
// Parsed query with default values.
|
||||
const _query = {
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
...filter,
|
||||
};
|
||||
const { results, pagination } =
|
||||
await UncategorizedCashflowTransaction.query()
|
||||
.onBuild((q) => {
|
||||
q.where('excluded', true);
|
||||
q.orderBy('date', 'DESC');
|
||||
|
||||
if (_query.accountId) {
|
||||
q.where('account_id', _query.accountId);
|
||||
}
|
||||
})
|
||||
.pagination(_query.page - 1, _query.pageSize);
|
||||
|
||||
const data = await this.transformer.transform(
|
||||
tenantId,
|
||||
results,
|
||||
new UncategorizedTransactionTransformer()
|
||||
);
|
||||
return { data, pagination };
|
||||
}
|
||||
}
|
||||
6
packages/server/src/services/Banking/Exclude/_types.ts
Normal file
6
packages/server/src/services/Banking/Exclude/_types.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
|
||||
export interface ExcludedBankTransactionsQuery {
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
accountId?: number;
|
||||
}
|
||||
@@ -65,7 +65,6 @@ export class RecognizeTranasctionsService {
|
||||
|
||||
if (batch) query.where('batch', batch);
|
||||
});
|
||||
|
||||
const bankRules = await BankRule.query().withGraphFetched('conditions');
|
||||
const bankRulesByAccountId = transformToMapBy(
|
||||
bankRules,
|
||||
@@ -92,7 +91,7 @@ export class RecognizeTranasctionsService {
|
||||
);
|
||||
}
|
||||
};
|
||||
await PromisePool.withConcurrency(MIGRATION_CONCURRENCY)
|
||||
const result = await PromisePool.withConcurrency(MIGRATION_CONCURRENCY)
|
||||
.for(uncategorizedTranasctions)
|
||||
.process((transaction: UncategorizedCashflowTransaction, index, pool) => {
|
||||
return regonizeTransaction(transaction);
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
ICashflowAccountsFilter,
|
||||
ICashflowNewCommandDTO,
|
||||
ICategorizeCashflowTransactioDTO,
|
||||
IGetRecognizedTransactionsQuery,
|
||||
IGetUncategorizedTransactionsQuery,
|
||||
} from '@/interfaces';
|
||||
import { CategorizeTransactionAsExpense } from './CategorizeTransactionAsExpense';
|
||||
@@ -18,6 +19,7 @@ import { GetUncategorizedTransaction } from './GetUncategorizedTransaction';
|
||||
import NewCashflowTransactionService from './NewCashflowTransactionService';
|
||||
import GetCashflowAccountsService from './GetCashflowAccountsService';
|
||||
import { GetCashflowTransactionService } from './GetCashflowTransactionsService';
|
||||
import { GetRecognizedTransactionsService } from './GetRecongizedTransactions';
|
||||
|
||||
@Service()
|
||||
export class CashflowApplication {
|
||||
@@ -51,6 +53,9 @@ export class CashflowApplication {
|
||||
@Inject()
|
||||
private createUncategorizedTransactionService: CreateUncategorizedTransaction;
|
||||
|
||||
@Inject()
|
||||
private getRecognizedTranasctionsService: GetRecognizedTransactionsService;
|
||||
|
||||
/**
|
||||
* Creates a new cashflow transaction.
|
||||
* @param {number} tenantId
|
||||
@@ -213,4 +218,20 @@ export class CashflowApplication {
|
||||
uncategorizedTransactionId
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the recognized bank transactions.
|
||||
* @param {number} tenantId
|
||||
* @param {number} accountId
|
||||
* @returns
|
||||
*/
|
||||
public getRecognizedTransactions(
|
||||
tenantId: number,
|
||||
filter?: IGetRecognizedTransactionsQuery
|
||||
) {
|
||||
return this.getRecognizedTranasctionsService.getRecognizedTranactions(
|
||||
tenantId,
|
||||
filter
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,262 @@
|
||||
import { Transformer } from '@/lib/Transformer/Transformer';
|
||||
import { formatNumber } from '@/utils';
|
||||
|
||||
export class GetRecognizedTransactionTransformer extends Transformer {
|
||||
/**
|
||||
* Include these attributes to sale credit note object.
|
||||
* @returns {Array}
|
||||
*/
|
||||
public includeAttributes = (): string[] => {
|
||||
return [
|
||||
'uncategorizedTransactionId',
|
||||
'referenceNo',
|
||||
'description',
|
||||
'payee',
|
||||
'amount',
|
||||
'formattedAmount',
|
||||
'date',
|
||||
'formattedDate',
|
||||
'assignedAccountId',
|
||||
'assignedAccountName',
|
||||
'assignedAccountCode',
|
||||
'assignedPayee',
|
||||
'assignedMemo',
|
||||
'assignedCategory',
|
||||
'assignedCategoryFormatted',
|
||||
'withdrawal',
|
||||
'deposit',
|
||||
'isDepositTransaction',
|
||||
'isWithdrawalTransaction',
|
||||
'formattedDepositAmount',
|
||||
'formattedWithdrawalAmount',
|
||||
'bankRuleId',
|
||||
'bankRuleName',
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* Exclude all attributes.
|
||||
* @returns {Array<string>}
|
||||
*/
|
||||
public excludeAttributes = (): string[] => {
|
||||
return ['*'];
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the uncategorized transaction id.
|
||||
* @param transaction
|
||||
* @returns {number}
|
||||
*/
|
||||
public uncategorizedTransactionId = (transaction): number => {
|
||||
return transaction.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the reference number of the transaction.
|
||||
* @param {object} transaction
|
||||
* @returns {string}
|
||||
*/
|
||||
public referenceNo(transaction: any): string {
|
||||
return transaction.referenceNo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the description of the transaction.
|
||||
* @param {object} transaction
|
||||
* @returns {string}
|
||||
*/
|
||||
public description(transaction: any): string {
|
||||
return transaction.description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the payee of the transaction.
|
||||
* @param {object} transaction
|
||||
* @returns {string}
|
||||
*/
|
||||
public payee(transaction: any): string {
|
||||
return transaction.payee;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the amount of the transaction.
|
||||
* @param {object} transaction
|
||||
* @returns {number}
|
||||
*/
|
||||
public amount(transaction: any): number {
|
||||
return transaction.amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the formatted amount of the transaction.
|
||||
* @param {object} transaction
|
||||
* @returns {string}
|
||||
*/
|
||||
public formattedAmount(transaction: any): string {
|
||||
return this.formatNumber(transaction.formattedAmount, {
|
||||
money: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the date of the transaction.
|
||||
* @param {object} transaction
|
||||
* @returns {string}
|
||||
*/
|
||||
public date(transaction: any): string {
|
||||
return transaction.date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the formatted date of the transaction.
|
||||
* @param {object} transaction
|
||||
* @returns {string}
|
||||
*/
|
||||
public formattedDate(transaction: any): string {
|
||||
return this.formatDate(transaction.date);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the assigned account ID of the transaction.
|
||||
* @param {object} transaction
|
||||
* @returns {number}
|
||||
*/
|
||||
public assignedAccountId(transaction: any): number {
|
||||
return transaction.recognizedTransaction.assignedAccountId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the assigned account name of the transaction.
|
||||
* @param {object} transaction
|
||||
* @returns {string}
|
||||
*/
|
||||
public assignedAccountName(transaction: any): string {
|
||||
return transaction.recognizedTransaction.assignAccount.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the assigned account code of the transaction.
|
||||
* @param {object} transaction
|
||||
* @returns {string}
|
||||
*/
|
||||
public assignedAccountCode(transaction: any): string {
|
||||
return transaction.recognizedTransaction.assignAccount.code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the assigned payee of the transaction.
|
||||
* @param {object} transaction
|
||||
* @returns {string}
|
||||
*/
|
||||
public getAssignedPayee(transaction: any): string {
|
||||
return transaction.recognizedTransaction.assignedPayee;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the assigned memo of the transaction.
|
||||
* @param {object} transaction
|
||||
* @returns {string}
|
||||
*/
|
||||
public assignedMemo(transaction: any): string {
|
||||
return transaction.recognizedTransaction.assignedMemo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the assigned category of the transaction.
|
||||
* @param {object} transaction
|
||||
* @returns {string}
|
||||
*/
|
||||
public assignedCategory(transaction: any): string {
|
||||
return transaction.recognizedTransaction.assignedCategory;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
public assignedCategoryFormatted() {
|
||||
return 'Other Income'
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the transaction is a withdrawal.
|
||||
* @param {object} transaction
|
||||
* @returns {boolean}
|
||||
*/
|
||||
public isWithdrawal(transaction: any): boolean {
|
||||
return transaction.withdrawal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the transaction is a deposit.
|
||||
* @param {object} transaction
|
||||
* @returns {boolean}
|
||||
*/
|
||||
public isDeposit(transaction: any): boolean {
|
||||
return transaction.deposit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the transaction is a deposit transaction.
|
||||
* @param {object} transaction
|
||||
* @returns {boolean}
|
||||
*/
|
||||
public isDepositTransaction(transaction: any): boolean {
|
||||
return transaction.isDepositTransaction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the transaction is a withdrawal transaction.
|
||||
* @param {object} transaction
|
||||
* @returns {boolean}
|
||||
*/
|
||||
public isWithdrawalTransaction(transaction: any): boolean {
|
||||
return transaction.isWithdrawalTransaction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get formatted deposit amount.
|
||||
* @param {any} transaction
|
||||
* @returns {string}
|
||||
*/
|
||||
protected formattedDepositAmount(transaction) {
|
||||
if (transaction.isDepositTransaction) {
|
||||
return formatNumber(transaction.deposit, {
|
||||
currencyCode: transaction.currencyCode,
|
||||
});
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get formatted withdrawal amount.
|
||||
* @param transaction
|
||||
* @returns {string}
|
||||
*/
|
||||
protected formattedWithdrawalAmount(transaction) {
|
||||
if (transaction.isWithdrawalTransaction) {
|
||||
return formatNumber(transaction.withdrawal, {
|
||||
currencyCode: transaction.currencyCode,
|
||||
});
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the transaction bank rule id.
|
||||
* @param transaction
|
||||
* @returns {string}
|
||||
*/
|
||||
protected bankRuleId(transaction) {
|
||||
return transaction.recognizedTransaction.bankRuleId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the transaction bank rule name.
|
||||
* @param transaction
|
||||
* @returns {string}
|
||||
*/
|
||||
protected bankRuleName(transaction) {
|
||||
return transaction.recognizedTransaction.bankRule.name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import HasTenancyService from '../Tenancy/TenancyService';
|
||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||
import { GetRecognizedTransactionTransformer } from './GetRecognizedTransactionTransformer';
|
||||
import { IGetRecognizedTransactionsQuery } from '@/interfaces';
|
||||
|
||||
@Service()
|
||||
export class GetRecognizedTransactionsService {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private transformer: TransformerInjectable;
|
||||
|
||||
/**
|
||||
* Retrieves the recognized transactions of the given account.
|
||||
* @param {number} tenantId
|
||||
* @param {IGetRecognizedTransactionsQuery} filter -
|
||||
*/
|
||||
async getRecognizedTranactions(
|
||||
tenantId: number,
|
||||
filter?: IGetRecognizedTransactionsQuery
|
||||
) {
|
||||
const { UncategorizedCashflowTransaction } = this.tenancy.models(tenantId);
|
||||
|
||||
const _filter = {
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
...filter,
|
||||
};
|
||||
const { results, pagination } =
|
||||
await UncategorizedCashflowTransaction.query()
|
||||
.onBuild((q) => {
|
||||
q.withGraphFetched('recognizedTransaction.assignAccount');
|
||||
q.withGraphFetched('recognizedTransaction.bankRule');
|
||||
q.whereNotNull('recognizedTransactionId');
|
||||
|
||||
if (_filter.accountId) {
|
||||
q.where('accountId', _filter.accountId);
|
||||
}
|
||||
})
|
||||
.pagination(_filter.page - 1, _filter.pageSize);
|
||||
|
||||
const data = await this.transformer.transform(
|
||||
tenantId,
|
||||
results,
|
||||
new GetRecognizedTransactionTransformer()
|
||||
);
|
||||
return { data, pagination };
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ export class UncategorizedTransactionTransformer extends Transformer {
|
||||
return [
|
||||
'formattedAmount',
|
||||
'formattedDate',
|
||||
'formattetDepositAmount',
|
||||
'formattedDepositAmount',
|
||||
'formattedWithdrawalAmount',
|
||||
];
|
||||
};
|
||||
@@ -40,7 +40,7 @@ export class UncategorizedTransactionTransformer extends Transformer {
|
||||
* @param transaction
|
||||
* @returns {string}
|
||||
*/
|
||||
protected formattetDepositAmount(transaction) {
|
||||
protected formattedDepositAmount(transaction) {
|
||||
if (transaction.isDepositTransaction) {
|
||||
return formatNumber(transaction.deposit, {
|
||||
currencyCode: transaction.currencyCode,
|
||||
|
||||
@@ -22,6 +22,8 @@ export const TABLES = {
|
||||
PROJECTS: 'projects',
|
||||
TIMESHEETS: 'timesheets',
|
||||
PROJECT_TASKS: 'project_tasks',
|
||||
UNCATEGORIZED_ACCOUNT_TRANSACTIONS: 'UNCATEGORIZED_ACCOUNT_TRANSACTIONS',
|
||||
EXCLUDED_BANK_TRANSACTIONS: 'EXCLUDED_BANK_TRANSACTIONS'
|
||||
};
|
||||
|
||||
export const TABLE_SIZE = {
|
||||
|
||||
@@ -28,6 +28,7 @@ export function AccountTransactionsUncategorizeFilter() {
|
||||
<FilterTag round interactive>
|
||||
All <strong>(2)</strong>
|
||||
</FilterTag>
|
||||
|
||||
<FilterTag round minimal interactive>
|
||||
Recognized <strong>(0)</strong>
|
||||
</FilterTag>
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
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,
|
||||
};
|
||||
@@ -4,7 +4,6 @@ import styled from 'styled-components';
|
||||
import '@/style/pages/CashFlow/AccountTransactions/List.scss';
|
||||
|
||||
import AccountTransactionsDataTable from './AccountTransactionsDataTable';
|
||||
import { AccountTransactionsUncategorizeFilter } from './AccountTransactionsUncategorizeFilter';
|
||||
import { AccountTransactionsAllProvider } from './AccountTransactionsAllBoot';
|
||||
|
||||
const Box = styled.div`
|
||||
@@ -23,8 +22,6 @@ export default function AccountTransactionsAll() {
|
||||
return (
|
||||
<AccountTransactionsAllProvider>
|
||||
<Box>
|
||||
<AccountTransactionsUncategorizeFilter />
|
||||
|
||||
<CashflowTransactionsTableCard>
|
||||
<AccountTransactionsDataTable />
|
||||
</CashflowTransactionsTableCard>
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
// @ts-nocheck
|
||||
import { useEffect } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import * as R from 'ramda';
|
||||
|
||||
import '@/style/pages/CashFlow/AccountTransactions/List.scss';
|
||||
|
||||
import AccountTransactionsUncategorizedTable from './AccountTransactionsUncategorizedTable';
|
||||
import { AccountUncategorizedTransactionsBoot } from './AllTransactionsUncategorizedBoot';
|
||||
import { AccountTransactionsUncategorizeFilter } from './AccountTransactionsUncategorizeFilter';
|
||||
import { UncategorizedTransactionsFilterProvider } from './AccountUncategorizedTransactionsFilterProvider';
|
||||
import { RecognizedTransactionsTableBoot } from './RecognizedTransactions/RecognizedTransactionsTableBoot';
|
||||
import { RecognizedTransactionsTable } from './RecognizedTransactions/RecognizedTransactionsTable';
|
||||
import {
|
||||
WithBankingActionsProps,
|
||||
withBankingActions,
|
||||
} from '../withBankingActions';
|
||||
import { useEffect } from 'react';
|
||||
import { AccountUncategorizedTransactionsBoot } from './AllTransactionsUncategorizedBoot';
|
||||
import AccountTransactionsUncategorizedTable from './AccountTransactionsUncategorizedTable';
|
||||
import { ExcludedBankTransactionsTableBoot } from './ExcludedTransactions/ExcludedTransactionsTableBoot';
|
||||
import { ExcludedTransactionsTable } from './ExcludedTransactions/ExcludedTransactionsTable';
|
||||
|
||||
const Box = styled.div`
|
||||
margin: 30px 15px;
|
||||
@@ -36,14 +42,31 @@ function AllTransactionsUncategorizedRoot({
|
||||
},
|
||||
[closeMatchingTransactionAside],
|
||||
);
|
||||
|
||||
return (
|
||||
<AccountUncategorizedTransactionsBoot>
|
||||
<UncategorizedTransactionsFilterProvider>
|
||||
<Box>
|
||||
<CashflowTransactionsTableCard>
|
||||
<AccountTransactionsUncategorizedTable />
|
||||
</CashflowTransactionsTableCard>
|
||||
<AccountTransactionsUncategorizeFilter />
|
||||
|
||||
<ExcludedBankTransactionsTableBoot>
|
||||
<CashflowTransactionsTableCard>
|
||||
<ExcludedTransactionsTable />
|
||||
</CashflowTransactionsTableCard>
|
||||
</ExcludedBankTransactionsTableBoot>
|
||||
|
||||
{/* <RecognizedTransactionsTableBoot>
|
||||
<CashflowTransactionsTableCard>
|
||||
<RecognizedTransactionsTable />
|
||||
</CashflowTransactionsTableCard>
|
||||
</RecognizedTransactionsTableBoot> */}
|
||||
|
||||
{/* <AccountUncategorizedTransactionsBoot>
|
||||
<CashflowTransactionsTableCard>
|
||||
<AccountTransactionsUncategorizedTable />
|
||||
</CashflowTransactionsTableCard>
|
||||
</AccountUncategorizedTransactionsBoot> */}
|
||||
</Box>
|
||||
</AccountUncategorizedTransactionsBoot>
|
||||
</UncategorizedTransactionsFilterProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import {
|
||||
DataTable,
|
||||
TableFastCell,
|
||||
TableSkeletonRows,
|
||||
TableSkeletonHeader,
|
||||
TableVirtualizedListRows,
|
||||
AppToaster,
|
||||
} from '@/components';
|
||||
import { TABLES } from '@/constants/tables';
|
||||
|
||||
import withDrawerActions from '@/containers/Drawer/withDrawerActions';
|
||||
|
||||
import { useMemorizedColumnsWidths } from '@/hooks';
|
||||
import { useExcludedTransactionsColumns } from './_utils';
|
||||
import { useExcludedTransactionsBoot } from './ExcludedTransactionsTableBoot';
|
||||
|
||||
import { compose } from '@/utils';
|
||||
import { ActionsMenu } from './_components';
|
||||
import { useUnexcludeUncategorizedTransaction } from '@/hooks/query/bank-rules';
|
||||
import { Intent } from '@blueprintjs/core';
|
||||
|
||||
interface ExcludedTransactionsTableProps {}
|
||||
|
||||
/**
|
||||
* Renders the recognized account transactions datatable.
|
||||
*/
|
||||
function ExcludedTransactionsTableRoot({}: ExcludedTransactionsTableProps) {
|
||||
const { excludedBankTransactions } = useExcludedTransactionsBoot();
|
||||
const { mutateAsync: unexcludeBankTransaction } =
|
||||
useUnexcludeUncategorizedTransaction();
|
||||
|
||||
// Retrieve table columns.
|
||||
const columns = useExcludedTransactionsColumns();
|
||||
|
||||
// Local storage memorizing columns widths.
|
||||
const [initialColumnsWidths, , handleColumnResizing] =
|
||||
useMemorizedColumnsWidths(TABLES.UNCATEGORIZED_ACCOUNT_TRANSACTIONS);
|
||||
|
||||
// Handle cell click.
|
||||
const handleCellClick = (cell, event) => {};
|
||||
|
||||
// Handle restore button click.
|
||||
const handleRestoreClick = (transaction) => {
|
||||
unexcludeBankTransaction(transaction.id)
|
||||
.then(() => {
|
||||
AppToaster.show({
|
||||
message: 'The excluded bank transaction has been restored.',
|
||||
intent: Intent.SUCCESS,
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
AppToaster.show({
|
||||
message: 'Something went wrong.',
|
||||
intent: Intent.DANGER,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<CashflowTransactionsTable
|
||||
noInitialFetch={true}
|
||||
columns={columns}
|
||||
data={excludedBankTransactions}
|
||||
sticky={true}
|
||||
loading={false}
|
||||
headerLoading={false}
|
||||
expandColumnSpace={1}
|
||||
expandToggleColumn={2}
|
||||
selectionColumnWidth={45}
|
||||
TableCellRenderer={TableFastCell}
|
||||
TableLoadingRenderer={TableSkeletonRows}
|
||||
TableRowsRenderer={TableVirtualizedListRows}
|
||||
TableHeaderSkeletonRenderer={TableSkeletonHeader}
|
||||
ContextMenu={ActionsMenu}
|
||||
onCellClick={handleCellClick}
|
||||
// #TableVirtualizedListRows props.
|
||||
vListrowHeight={'small' == 'small' ? 32 : 40}
|
||||
vListrowHeight={40}
|
||||
vListOverscanRowCount={0}
|
||||
initialColumnsWidths={initialColumnsWidths}
|
||||
onColumnResizing={handleColumnResizing}
|
||||
// noResults={<T id={'cash_flow.account_transactions.no_results'} />}
|
||||
className="table-constrant"
|
||||
payload={{
|
||||
onRestore: handleRestoreClick,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export const ExcludedTransactionsTable = compose(withDrawerActions)(
|
||||
ExcludedTransactionsTableRoot,
|
||||
);
|
||||
|
||||
const DashboardConstrantTable = styled(DataTable)`
|
||||
.table {
|
||||
.thead {
|
||||
.th {
|
||||
background: #fff;
|
||||
letter-spacing: 1px;
|
||||
text-transform: uppercase;
|
||||
font-weight: 500;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
.tbody {
|
||||
.tr:last-child .td {
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const CashflowTransactionsTable = styled(DashboardConstrantTable)`
|
||||
.table .tbody {
|
||||
.tbody-inner .tr.no-results {
|
||||
.td {
|
||||
padding: 2rem 0;
|
||||
font-size: 14px;
|
||||
color: #888;
|
||||
font-weight: 400;
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.tbody-inner {
|
||||
.tr .td {
|
||||
border-bottom: 1px solid #e6e6e6;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1,90 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { flatten, map } from 'lodash';
|
||||
import { IntersectionObserver } from '@/components';
|
||||
import { useAccountTransactionsContext } from '../AccountTransactionsProvider';
|
||||
import { useExcludedBankTransactionsInfinity } from '@/hooks/query/bank-rules';
|
||||
|
||||
interface ExcludedBankTransactionsContextValue {
|
||||
isExcludedTransactionsLoading: boolean;
|
||||
isExcludedTransactionsFetching: boolean;
|
||||
excludedBankTransactions: Array<any>;
|
||||
}
|
||||
|
||||
const ExcludedTransactionsContext =
|
||||
React.createContext<ExcludedBankTransactionsContextValue>(
|
||||
{} as ExcludedBankTransactionsContextValue,
|
||||
);
|
||||
|
||||
function flattenInfinityPagesData(data) {
|
||||
return flatten(map(data.pages, (page) => page.data));
|
||||
}
|
||||
|
||||
interface ExcludedBankTransactionsTableBootProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Account uncategorized transctions provider.
|
||||
*/
|
||||
function ExcludedBankTransactionsTableBoot({
|
||||
children,
|
||||
}: ExcludedBankTransactionsTableBootProps) {
|
||||
const { accountId } = useAccountTransactionsContext();
|
||||
|
||||
// Fetches the uncategorized transactions.
|
||||
const {
|
||||
data: recognizedTransactionsPage,
|
||||
isFetching: isExcludedTransactionsFetching,
|
||||
isLoading: isExcludedTransactionsLoading,
|
||||
isSuccess: isRecognizedTransactionsSuccess,
|
||||
isFetchingNextPage: isUncategorizedTransactionFetchNextPage,
|
||||
fetchNextPage: fetchNextrecognizedTransactionsPage,
|
||||
hasNextPage: hasUncategorizedTransactionsNextPage,
|
||||
} = useExcludedBankTransactionsInfinity({
|
||||
page_size: 50,
|
||||
account_id: accountId,
|
||||
});
|
||||
// Memorized the cashflow account transactions.
|
||||
const excludedBankTransactions = React.useMemo(
|
||||
() =>
|
||||
isRecognizedTransactionsSuccess
|
||||
? flattenInfinityPagesData(recognizedTransactionsPage)
|
||||
: [],
|
||||
[recognizedTransactionsPage, isRecognizedTransactionsSuccess],
|
||||
);
|
||||
// Handle the observer ineraction.
|
||||
const handleObserverInteract = React.useCallback(() => {
|
||||
if (
|
||||
!isExcludedTransactionsFetching &&
|
||||
hasUncategorizedTransactionsNextPage
|
||||
) {
|
||||
fetchNextrecognizedTransactionsPage();
|
||||
}
|
||||
}, [
|
||||
isExcludedTransactionsFetching,
|
||||
hasUncategorizedTransactionsNextPage,
|
||||
fetchNextrecognizedTransactionsPage,
|
||||
]);
|
||||
// Provider payload.
|
||||
const provider = {
|
||||
excludedBankTransactions,
|
||||
isExcludedTransactionsFetching,
|
||||
isExcludedTransactionsLoading,
|
||||
};
|
||||
|
||||
return (
|
||||
<ExcludedTransactionsContext.Provider value={provider}>
|
||||
{children}
|
||||
<IntersectionObserver
|
||||
onIntersect={handleObserverInteract}
|
||||
enabled={!isUncategorizedTransactionFetchNextPage}
|
||||
/>
|
||||
</ExcludedTransactionsContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
const useExcludedTransactionsBoot = () =>
|
||||
React.useContext(ExcludedTransactionsContext);
|
||||
|
||||
export { ExcludedBankTransactionsTableBoot, useExcludedTransactionsBoot };
|
||||
@@ -0,0 +1,11 @@
|
||||
// @ts-nocheck
|
||||
import { Menu, MenuItem, MenuDivider } from '@blueprintjs/core';
|
||||
import { safeCallback } from '@/utils';
|
||||
|
||||
export function ActionsMenu({ payload: { onRestore }, row: { original } }) {
|
||||
return (
|
||||
<Menu>
|
||||
<MenuItem text={'Restore'} onClick={safeCallback(onRestore, original)} />
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { getColumnWidth } from '@/utils';
|
||||
import { useExcludedTransactionsBoot } from './ExcludedTransactionsTableBoot';
|
||||
|
||||
const getReportColWidth = (data, accessor, headerText) => {
|
||||
return getColumnWidth(
|
||||
data,
|
||||
accessor,
|
||||
{ magicSpacing: 10, minWidth: 100 },
|
||||
headerText,
|
||||
);
|
||||
};
|
||||
|
||||
const descriptionAccessor = (transaction) => {
|
||||
return <span style={{ color: '#5F6B7C' }}>{transaction.description}</span>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve excluded transactions columns table.
|
||||
*/
|
||||
export function useExcludedTransactionsColumns() {
|
||||
const { excludedBankTransactions: data } = useExcludedTransactionsBoot();
|
||||
|
||||
const withdrawalWidth = getReportColWidth(
|
||||
data,
|
||||
'formatted_withdrawal_amount',
|
||||
'Withdrawal',
|
||||
);
|
||||
const depositWidth = getReportColWidth(
|
||||
data,
|
||||
'formatted_deposit_amount',
|
||||
'Deposit',
|
||||
);
|
||||
|
||||
return React.useMemo(
|
||||
() => [
|
||||
{
|
||||
Header: 'Date',
|
||||
accessor: 'formatted_date',
|
||||
width: 110,
|
||||
},
|
||||
{
|
||||
Header: 'Description',
|
||||
accessor: descriptionAccessor,
|
||||
},
|
||||
{
|
||||
Header: 'Payee',
|
||||
accessor: 'payee',
|
||||
},
|
||||
{
|
||||
Header: 'Deposit',
|
||||
accessor: 'formatted_deposit_amount',
|
||||
align: 'right',
|
||||
width: depositWidth,
|
||||
},
|
||||
{
|
||||
Header: 'Withdrawal',
|
||||
accessor: 'formatted_withdrawal_amount',
|
||||
align: 'right',
|
||||
width: withdrawalWidth,
|
||||
},
|
||||
],
|
||||
[],
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import {
|
||||
DataTable,
|
||||
TableFastCell,
|
||||
TableSkeletonRows,
|
||||
TableSkeletonHeader,
|
||||
TableVirtualizedListRows,
|
||||
FormattedMessage as T,
|
||||
AppToaster,
|
||||
} from '@/components';
|
||||
import { TABLES } from '@/constants/tables';
|
||||
|
||||
import withSettings from '@/containers/Settings/withSettings';
|
||||
import withAlertsActions from '@/containers/Alert/withAlertActions';
|
||||
import withDrawerActions from '@/containers/Drawer/withDrawerActions';
|
||||
|
||||
import { useMemorizedColumnsWidths } from '@/hooks';
|
||||
import { useUncategorizedTransactionsColumns } from './_utils';
|
||||
import { useRecognizedTransactionsBoot } from './RecognizedTransactionsTableBoot';
|
||||
|
||||
import { ActionsMenu } from './_components';
|
||||
import { compose } from '@/utils';
|
||||
import { useExcludeUncategorizedTransaction } from '@/hooks/query/bank-rules';
|
||||
import { Intent } from '@blueprintjs/core';
|
||||
import {
|
||||
WithBankingActionsProps,
|
||||
withBankingActions,
|
||||
} from '../../withBankingActions';
|
||||
|
||||
interface RecognizedTransactionsTableProps extends WithBankingActionsProps {}
|
||||
|
||||
/**
|
||||
* Renders the recognized account transactions datatable.
|
||||
*/
|
||||
function RecognizedTransactionsTableRoot({
|
||||
// #withSettings
|
||||
cashflowTansactionsTableSize,
|
||||
|
||||
// #withAlertsActions
|
||||
openAlert,
|
||||
|
||||
// #withDrawerActions
|
||||
openDrawer,
|
||||
|
||||
// #withBanking
|
||||
setUncategorizedTransactionIdForMatching,
|
||||
}: RecognizedTransactionsTableProps) {
|
||||
const { mutateAsync: excludeBankTransaction } =
|
||||
useExcludeUncategorizedTransaction();
|
||||
|
||||
const { recognizedTransactions } = useRecognizedTransactionsBoot();
|
||||
|
||||
// Retrieve table columns.
|
||||
const columns = useUncategorizedTransactionsColumns();
|
||||
|
||||
// Local storage memorizing columns widths.
|
||||
const [initialColumnsWidths, , handleColumnResizing] =
|
||||
useMemorizedColumnsWidths(TABLES.UNCATEGORIZED_ACCOUNT_TRANSACTIONS);
|
||||
|
||||
// Handle cell click.
|
||||
const handleCellClick = (cell, event) => {
|
||||
setUncategorizedTransactionIdForMatching(
|
||||
cell.row.original.uncategorized_transaction_id,
|
||||
);
|
||||
};
|
||||
// Handle exclude button click.
|
||||
const handleExcludeClick = (transaction) => {
|
||||
excludeBankTransaction(transaction.uncategorized_transaction_id)
|
||||
.then(() => {
|
||||
AppToaster.show({
|
||||
intent: Intent.SUCCESS,
|
||||
message: 'The bank transaction has been excluded.',
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
AppToaster.show({
|
||||
intent: Intent.DANGER,
|
||||
message: 'Something went wrong.',
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
//
|
||||
const handleCategorizeClick = (transaction) => {
|
||||
setUncategorizedTransactionIdForMatching(
|
||||
transaction.uncategorized_transaction_id,
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<CashflowTransactionsTable
|
||||
noInitialFetch={true}
|
||||
columns={columns}
|
||||
data={recognizedTransactions}
|
||||
sticky={true}
|
||||
loading={false}
|
||||
headerLoading={false}
|
||||
expandColumnSpace={1}
|
||||
expandToggleColumn={2}
|
||||
selectionColumnWidth={45}
|
||||
TableCellRenderer={TableFastCell}
|
||||
TableLoadingRenderer={TableSkeletonRows}
|
||||
TableRowsRenderer={TableVirtualizedListRows}
|
||||
TableHeaderSkeletonRenderer={TableSkeletonHeader}
|
||||
ContextMenu={ActionsMenu}
|
||||
onCellClick={handleCellClick}
|
||||
// #TableVirtualizedListRows props.
|
||||
vListrowHeight={'small' == 'small' ? 32 : 40}
|
||||
vListrowHeight={40}
|
||||
vListOverscanRowCount={0}
|
||||
initialColumnsWidths={initialColumnsWidths}
|
||||
onColumnResizing={handleColumnResizing}
|
||||
noResults={<T id={'cash_flow.account_transactions.no_results'} />}
|
||||
className="table-constrant"
|
||||
payload={{
|
||||
onExclude: handleExcludeClick,
|
||||
onCategorize: handleCategorizeClick,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export const RecognizedTransactionsTable = compose(
|
||||
withAlertsActions,
|
||||
withDrawerActions,
|
||||
withBankingActions,
|
||||
)(RecognizedTransactionsTableRoot);
|
||||
|
||||
const DashboardConstrantTable = styled(DataTable)`
|
||||
.table {
|
||||
.thead {
|
||||
.th {
|
||||
background: #fff;
|
||||
letter-spacing: 1px;
|
||||
text-transform: uppercase;
|
||||
font-weight: 500;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
.tbody {
|
||||
.tr:last-child .td {
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const CashflowTransactionsTable = styled(DashboardConstrantTable)`
|
||||
.table .tbody {
|
||||
.tbody-inner .tr.no-results {
|
||||
.td {
|
||||
padding: 2rem 0;
|
||||
font-size: 14px;
|
||||
color: #888;
|
||||
font-weight: 400;
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.tbody-inner {
|
||||
.tr .td {
|
||||
border-bottom: 1px solid #e6e6e6;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1,89 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { flatten, map } from 'lodash';
|
||||
import { IntersectionObserver } from '@/components';
|
||||
import { useAccountTransactionsContext } from '../AccountTransactionsProvider';
|
||||
import { useRecognizedBankTransactionsInfinity } from '@/hooks/query/bank-rules';
|
||||
|
||||
interface RecognizedTransactionsContextValue {
|
||||
isRecongizedTransactionsLoading: boolean;
|
||||
isRecognizedTransactionsFetching: boolean;
|
||||
recognizedTransactions: Array<any>;
|
||||
}
|
||||
|
||||
const RecognizedTransactionsContext =
|
||||
React.createContext<RecognizedTransactionsContextValue>(
|
||||
{} as RecognizedTransactionsContextValue,
|
||||
);
|
||||
|
||||
function flattenInfinityPagesData(data) {
|
||||
return flatten(map(data.pages, (page) => page.data));
|
||||
}
|
||||
|
||||
interface RecognizedTransactionsTableBootProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Account uncategorized transctions provider.
|
||||
*/
|
||||
function RecognizedTransactionsTableBoot({
|
||||
children,
|
||||
}: RecognizedTransactionsTableBootProps) {
|
||||
const { accountId } = useAccountTransactionsContext();
|
||||
|
||||
// Fetches the uncategorized transactions.
|
||||
const {
|
||||
data: recognizedTransactionsPage,
|
||||
isFetching: isRecognizedTransactionsFetching,
|
||||
isLoading: isRecongizedTransactionsLoading,
|
||||
isSuccess: isRecognizedTransactionsSuccess,
|
||||
isFetchingNextPage: isUncategorizedTransactionFetchNextPage,
|
||||
fetchNextPage: fetchNextrecognizedTransactionsPage,
|
||||
hasNextPage: hasUncategorizedTransactionsNextPage,
|
||||
} = useRecognizedBankTransactionsInfinity({
|
||||
page_size: 50,
|
||||
});
|
||||
// Memorized the cashflow account transactions.
|
||||
const recognizedTransactions = React.useMemo(
|
||||
() =>
|
||||
isRecognizedTransactionsSuccess
|
||||
? flattenInfinityPagesData(recognizedTransactionsPage)
|
||||
: [],
|
||||
[recognizedTransactionsPage, isRecognizedTransactionsSuccess],
|
||||
);
|
||||
// Handle the observer ineraction.
|
||||
const handleObserverInteract = React.useCallback(() => {
|
||||
if (
|
||||
!isRecognizedTransactionsFetching &&
|
||||
hasUncategorizedTransactionsNextPage
|
||||
) {
|
||||
fetchNextrecognizedTransactionsPage();
|
||||
}
|
||||
}, [
|
||||
isRecognizedTransactionsFetching,
|
||||
hasUncategorizedTransactionsNextPage,
|
||||
fetchNextrecognizedTransactionsPage,
|
||||
]);
|
||||
// Provider payload.
|
||||
const provider = {
|
||||
recognizedTransactions,
|
||||
isRecognizedTransactionsFetching,
|
||||
isRecongizedTransactionsLoading,
|
||||
};
|
||||
|
||||
return (
|
||||
<RecognizedTransactionsContext.Provider value={provider}>
|
||||
{children}
|
||||
<IntersectionObserver
|
||||
onIntersect={handleObserverInteract}
|
||||
enabled={!isUncategorizedTransactionFetchNextPage}
|
||||
/>
|
||||
</RecognizedTransactionsContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
const useRecognizedTransactionsBoot = () =>
|
||||
React.useContext(RecognizedTransactionsContext);
|
||||
|
||||
export { RecognizedTransactionsTableBoot, useRecognizedTransactionsBoot };
|
||||
@@ -0,0 +1,19 @@
|
||||
// @ts-nocheck
|
||||
import { Menu, MenuItem, MenuDivider } from '@blueprintjs/core';
|
||||
import { safeCallback } from '@/utils';
|
||||
|
||||
export function ActionsMenu({
|
||||
payload: { onCategorize, onExclude },
|
||||
row: { original },
|
||||
}) {
|
||||
return (
|
||||
<Menu>
|
||||
<MenuItem
|
||||
text={'Categorize'}
|
||||
onClick={safeCallback(onCategorize, original)}
|
||||
/>
|
||||
<MenuDivider />
|
||||
<MenuItem text={'Exclude'} onClick={safeCallback(onExclude, original)} />
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
// @ts-nocheck
|
||||
import { Group, Icon } from '@/components';
|
||||
import { getColumnWidth } from '@/utils';
|
||||
import React from 'react';
|
||||
import { useRecognizedTransactionsBoot } from './RecognizedTransactionsTableBoot';
|
||||
|
||||
const getReportColWidth = (data, accessor, headerText) => {
|
||||
return getColumnWidth(
|
||||
data,
|
||||
accessor,
|
||||
{ magicSpacing: 10, minWidth: 100 },
|
||||
headerText,
|
||||
);
|
||||
};
|
||||
|
||||
const recognizeAccessor = (transaction) => {
|
||||
return (
|
||||
<>
|
||||
<span>{transaction.assigned_category_formatted}</span>
|
||||
<Icon
|
||||
icon={'arrowRight'}
|
||||
color={'#8F99A8'}
|
||||
iconSize={12}
|
||||
style={{ marginLeft: 8, marginRight: 8 }}
|
||||
/>
|
||||
<span>{transaction.assigned_account_name}</span>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const descriptionAccessor = (transaction) => {
|
||||
return <span style={{ color: '#5F6B7C' }}>{transaction.description}</span>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve uncategorized transactions columns table.
|
||||
*/
|
||||
export function useUncategorizedTransactionsColumns() {
|
||||
const { recognizedTransactions: data } = useRecognizedTransactionsBoot();
|
||||
|
||||
const withdrawalWidth = getReportColWidth(
|
||||
data,
|
||||
'formatted_withdrawal_amount',
|
||||
'Withdrawal',
|
||||
);
|
||||
const depositWidth = getReportColWidth(
|
||||
data,
|
||||
'formatted_deposit_amount',
|
||||
'Deposit',
|
||||
);
|
||||
|
||||
return React.useMemo(
|
||||
() => [
|
||||
{
|
||||
Header: 'Date',
|
||||
accessor: 'formatted_date',
|
||||
width: 110,
|
||||
},
|
||||
{
|
||||
Header: 'Description',
|
||||
accessor: descriptionAccessor,
|
||||
},
|
||||
{
|
||||
Header: 'Payee',
|
||||
accessor: 'payee',
|
||||
},
|
||||
{
|
||||
Header: 'Recognize',
|
||||
accessor: recognizeAccessor,
|
||||
textOverview: true,
|
||||
},
|
||||
{
|
||||
Header: 'Rule',
|
||||
accessor: 'bank_rule_name',
|
||||
},
|
||||
{
|
||||
Header: 'Deposit',
|
||||
accessor: 'formatted_deposit_amount',
|
||||
align: 'right',
|
||||
width: depositWidth,
|
||||
},
|
||||
{
|
||||
Header: 'Withdrawal',
|
||||
accessor: 'formatted_withdrawal_amount',
|
||||
align: 'right',
|
||||
width: withdrawalWidth,
|
||||
},
|
||||
],
|
||||
[],
|
||||
);
|
||||
}
|
||||
@@ -44,7 +44,7 @@ export function MatchTransaction({
|
||||
onClick={handleClick}
|
||||
>
|
||||
<Stack spacing={3}>
|
||||
<span>{label}</span>
|
||||
<span className={styles.label}>{label}</span>
|
||||
<Text style={{ fontSize: 12, color: '#5C7080' }}>Date: {date}</Text>
|
||||
</Stack>
|
||||
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
// @ts-nocheck
|
||||
import { useMutation, useQuery, useQueryClient } from 'react-query';
|
||||
import {
|
||||
useInfiniteQuery,
|
||||
useMutation,
|
||||
useQuery,
|
||||
useQueryClient,
|
||||
} from 'react-query';
|
||||
import useApiRequest from '../useRequest';
|
||||
import { transformToCamelCase } from '@/utils';
|
||||
|
||||
@@ -118,7 +123,7 @@ export function useUnexcludeUncategorizedTransaction(props) {
|
||||
|
||||
return useMutation(
|
||||
(uncategorizedTransactionId: number) =>
|
||||
apiRequest.post(
|
||||
apiRequest.put(
|
||||
`/cashflow/transactions/${uncategorizedTransactionId}/unexclude`,
|
||||
),
|
||||
{
|
||||
@@ -145,3 +150,70 @@ export function useMatchTransaction(props?: any) {
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns
|
||||
*/
|
||||
export function useRecognizedBankTransactionsInfinity(
|
||||
query,
|
||||
infinityProps,
|
||||
axios,
|
||||
) {
|
||||
const apiRequest = useApiRequest();
|
||||
|
||||
return useInfiniteQuery(
|
||||
['RECOGNIZED_BANK_TRANSACTIONS_INFINITY', query],
|
||||
async ({ pageParam = 1 }) => {
|
||||
const response = await apiRequest.http({
|
||||
...axios,
|
||||
method: 'get',
|
||||
url: `/api/banking/recognized`,
|
||||
params: { page: pageParam, ...query },
|
||||
});
|
||||
return response.data;
|
||||
},
|
||||
{
|
||||
getPreviousPageParam: (firstPage) => firstPage.pagination.page - 1,
|
||||
getNextPageParam: (lastPage) => {
|
||||
const { pagination } = lastPage;
|
||||
|
||||
return pagination.total > pagination.page_size * pagination.page
|
||||
? lastPage.pagination.page + 1
|
||||
: undefined;
|
||||
},
|
||||
...infinityProps,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export function useExcludedBankTransactionsInfinity(
|
||||
query,
|
||||
infinityProps,
|
||||
axios,
|
||||
) {
|
||||
const apiRequest = useApiRequest();
|
||||
|
||||
return useInfiniteQuery(
|
||||
['EXCLUDED_BANK_TRANSACTIONS_INFINITY', query],
|
||||
async ({ pageParam = 1 }) => {
|
||||
const response = await apiRequest.http({
|
||||
...axios,
|
||||
method: 'get',
|
||||
url: `/api/cashflow/excluded`,
|
||||
params: { page: pageParam, ...query },
|
||||
});
|
||||
return response.data;
|
||||
},
|
||||
{
|
||||
getPreviousPageParam: (firstPage) => firstPage.pagination.page - 1,
|
||||
getNextPageParam: (lastPage) => {
|
||||
const { pagination } = lastPage;
|
||||
|
||||
return pagination.total > pagination.page_size * pagination.page
|
||||
? lastPage.pagination.page + 1
|
||||
: undefined;
|
||||
},
|
||||
...infinityProps,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -611,4 +611,10 @@ export default {
|
||||
],
|
||||
viewBox: '0 0 16 16',
|
||||
},
|
||||
arrowRight: {
|
||||
path: [
|
||||
'M14.7,7.29l-5-5C9.52,2.1,9.27,1.99,8.99,1.99c-0.55,0-1,0.45-1,1c0,0.28,0.11,0.53,0.29,0.71l3.29,3.29H1.99c-0.55,0-1,0.45-1,1s0.45,1,1,1h9.59l-3.29,3.29c-0.18,0.18-0.29,0.43-0.29,0.71c0,0.55,0.45,1,1,1c0.28,0,0.53-0.11,0.71-0.29l5-5c0.18-0.18,0.29-0.43,0.29-0.71S14.88,7.47,14.7,7.29z',
|
||||
],
|
||||
viewBox: '0 0 16 16',
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user