mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-18 05:40:31 +00:00
fix: filter the uncategorized transactions out of matched transactions
This commit is contained in:
@@ -5,7 +5,7 @@ import { MatchBankTransactionsApplication } from '@/services/Banking/Matching/Ma
|
||||
import { body, param } from 'express-validator';
|
||||
import {
|
||||
GetMatchedTransactionsFilter,
|
||||
IMatchTransactionDTO,
|
||||
IMatchTransactionsDTO,
|
||||
} from '@/services/Banking/Matching/types';
|
||||
|
||||
@Service()
|
||||
@@ -44,10 +44,10 @@ export class BankTransactionsMatchingController extends BaseController {
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
* @returns
|
||||
* @returns {Promise<Response|null>}
|
||||
*/
|
||||
private async matchBankTransaction(
|
||||
req: Request,
|
||||
req: Request<{ transactionId: number }>,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
@@ -55,7 +55,7 @@ export class BankTransactionsMatchingController extends BaseController {
|
||||
const { transactionId } = req.params;
|
||||
const matchTransactionDTO = this.matchedBodyData(
|
||||
req
|
||||
) as IMatchTransactionDTO;
|
||||
) as IMatchTransactionsDTO;
|
||||
|
||||
try {
|
||||
await this.bankTransactionsMatchingApp.matchTransaction(
|
||||
@@ -64,6 +64,7 @@ export class BankTransactionsMatchingController extends BaseController {
|
||||
matchTransactionDTO
|
||||
);
|
||||
return res.status(200).send({
|
||||
id: transactionId,
|
||||
message: 'The bank transaction has been matched.',
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -72,60 +73,31 @@ export class BankTransactionsMatchingController extends BaseController {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Unmatches the matched bank transaction.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
* @returns
|
||||
* @returns {Promise<Response|null>}
|
||||
*/
|
||||
private async unmatchMatchedBankTransaction(
|
||||
req: Request,
|
||||
req: Request<{ transactionId: number }>,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
const { tenantId } = req;
|
||||
const transactionId = req.params?.transactionId;
|
||||
const { transactionId } = req.params;
|
||||
|
||||
try {
|
||||
await this.bankTransactionsMatchingApp.unmatchMatchedTransaction(
|
||||
tenantId,
|
||||
transactionId
|
||||
);
|
||||
|
||||
return res.status(200).send({
|
||||
id: transactionId,
|
||||
message: 'The bank matched transaction has been unmatched.',
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the matched transactions.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
private async getMatchedTransactions(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
const { tenantId } = req;
|
||||
const filter = this.matchedQueryData(req) as GetMatchedTransactionsFilter;
|
||||
|
||||
console.log('test');
|
||||
|
||||
try {
|
||||
const matchedTransactions =
|
||||
await this.bankTransactionsMatchingApp.getMatchedTransactions(
|
||||
tenantId,
|
||||
filter
|
||||
);
|
||||
|
||||
return res.status(200).send({ data: matchedTransactions });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ exports.up = function (knex) {
|
||||
table.increments('id');
|
||||
table.integer('uncategorized_transaction_id').unsigned();
|
||||
table.string('reference_type');
|
||||
table.integer('reference_id');
|
||||
table.integer('reference_id').unsigned();
|
||||
table.decimal('amount');
|
||||
table.timestamps();
|
||||
});
|
||||
|
||||
@@ -54,14 +54,17 @@ export class BankRule extends TenantModel {
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Bank rule may associated to the assign account.
|
||||
*/
|
||||
assignAccount: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Account.default,
|
||||
join: {
|
||||
from: 'bank_rules.assignAccountId',
|
||||
to: 'accounts.id'
|
||||
}
|
||||
}
|
||||
to: 'accounts.id',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Inject, Service } from 'typedi';
|
||||
import { GetMatchedTransactions } from './GetMatchedTransactions';
|
||||
import { MatchBankTransactions } from './MatchTransactions';
|
||||
import { UnmatchMatchedBankTransaction } from './UnmatchMatchedTransaction';
|
||||
import { GetMatchedTransactionsFilter, IMatchTransactionDTO } from './types';
|
||||
import { GetMatchedTransactionsFilter, IMatchTransactionsDTO } from './types';
|
||||
|
||||
@Service()
|
||||
export class MatchBankTransactionsApplication {
|
||||
@@ -43,7 +43,7 @@ export class MatchBankTransactionsApplication {
|
||||
public matchTransaction(
|
||||
tenantId: number,
|
||||
uncategorizedTransactionId: number,
|
||||
matchTransactionsDTO: IMatchTransactionDTO
|
||||
matchTransactionsDTO: IMatchTransactionsDTO
|
||||
): Promise<void> {
|
||||
return this.matchTransactionService.matchTransaction(
|
||||
tenantId,
|
||||
|
||||
@@ -105,12 +105,13 @@ export class MatchBankTransactions {
|
||||
* Matches the given uncategorized transaction to the given references.
|
||||
* @param {number} tenantId
|
||||
* @param {number} uncategorizedTransactionId
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public async matchTransaction(
|
||||
tenantId: number,
|
||||
uncategorizedTransactionId: number,
|
||||
matchTransactionsDTO: IMatchTransactionsDTO
|
||||
) {
|
||||
): Promise<void> {
|
||||
const { matchedTransactions } = matchTransactionsDTO;
|
||||
|
||||
// Validates the given matching transactions DTO.
|
||||
|
||||
@@ -9,10 +9,11 @@ export class ValidateTransactionMatched {
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
/**
|
||||
*
|
||||
* Validate the given transaction whether is matched with bank transactions.
|
||||
* @param {number} tenantId
|
||||
* @param {string} referenceType
|
||||
* @param {number} referenceId
|
||||
* @param {string} referenceType - Transaction reference type.
|
||||
* @param {number} referenceId - Transaction reference id.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public async validateTransactionNoMatchLinking(
|
||||
tenantId: number,
|
||||
|
||||
@@ -14,15 +14,15 @@ export class ValidateMatchingOnCashflowDelete {
|
||||
public attach(bus) {
|
||||
bus.subscribe(
|
||||
events.cashflow.onTransactionDeleting,
|
||||
this.validateMatchingOnCashflowDelete.bind(this)
|
||||
this.validateMatchingOnCashflowDeleting.bind(this)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Validates the cashflow transaction whether matched with bank transaction on deleting.
|
||||
* @param {IManualJournalDeletingPayload}
|
||||
*/
|
||||
public async validateMatchingOnCashflowDelete({
|
||||
public async validateMatchingOnCashflowDeleting({
|
||||
tenantId,
|
||||
oldManualJournal,
|
||||
trx,
|
||||
|
||||
@@ -14,15 +14,15 @@ export class ValidateMatchingOnExpenseDelete {
|
||||
public attach(bus) {
|
||||
bus.subscribe(
|
||||
events.expenses.onDeleting,
|
||||
this.validateMatchingOnExpenseDelete.bind(this)
|
||||
this.validateMatchingOnExpenseDeleting.bind(this)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Validates the expense transaction whether matched with bank transaction on deleting.
|
||||
* @param {IExpenseEventDeletePayload}
|
||||
*/
|
||||
public async validateMatchingOnExpenseDelete({
|
||||
public async validateMatchingOnExpenseDeleting({
|
||||
tenantId,
|
||||
oldExpense,
|
||||
trx,
|
||||
|
||||
@@ -14,15 +14,15 @@ export class ValidateMatchingOnManualJournalDelete {
|
||||
public attach(bus) {
|
||||
bus.subscribe(
|
||||
events.manualJournals.onDeleting,
|
||||
this.validateMatchingOnManualJournalDelete.bind(this)
|
||||
this.validateMatchingOnManualJournalDeleting.bind(this)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Validates the manual journal transaction whether matched with bank transaction on deleting.
|
||||
* @param {IManualJournalDeletingPayload}
|
||||
*/
|
||||
public async validateMatchingOnManualJournalDelete({
|
||||
public async validateMatchingOnManualJournalDeleting({
|
||||
tenantId,
|
||||
oldManualJournal,
|
||||
trx,
|
||||
|
||||
@@ -17,15 +17,15 @@ export class ValidateMatchingOnPaymentMadeDelete {
|
||||
public attach(bus) {
|
||||
bus.subscribe(
|
||||
events.billPayment.onDeleting,
|
||||
this.validateMatchingOnPaymentMadeDelete.bind(this)
|
||||
this.validateMatchingOnPaymentMadeDeleting.bind(this)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Validates the payment made transaction whether matched with bank transaction on deleting.
|
||||
* @param {IPaymentReceiveDeletedPayload}
|
||||
*/
|
||||
public async validateMatchingOnPaymentMadeDelete({
|
||||
public async validateMatchingOnPaymentMadeDeleting({
|
||||
tenantId,
|
||||
oldBillPayment,
|
||||
trx,
|
||||
|
||||
@@ -14,15 +14,15 @@ export class ValidateMatchingOnPaymentReceivedDelete {
|
||||
public attach(bus) {
|
||||
bus.subscribe(
|
||||
events.paymentReceive.onDeleting,
|
||||
this.validateMatchingOnPaymentReceivedDelete.bind(this)
|
||||
this.validateMatchingOnPaymentReceivedDeleting.bind(this)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Validates the payment received transaction whether matched with bank transaction on deleting.
|
||||
* @param {IPaymentReceiveDeletedPayload}
|
||||
*/
|
||||
public async validateMatchingOnPaymentReceivedDelete({
|
||||
public async validateMatchingOnPaymentReceivedDeleting({
|
||||
tenantId,
|
||||
oldPaymentReceive,
|
||||
trx,
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Knex } from 'knex';
|
||||
export enum BankRuleConditionField {
|
||||
Amount = 'Amount',
|
||||
Description = 'Description',
|
||||
Payee = 'Payee'
|
||||
}
|
||||
|
||||
export enum BankRuleConditionComparator {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { initialize } from 'objection';
|
||||
import HasTenancyService from '../Tenancy/TenancyService';
|
||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||
import { UncategorizedTransactionTransformer } from './UncategorizedTransactionTransformer';
|
||||
@@ -22,7 +23,13 @@ export class GetUncategorizedTransactions {
|
||||
accountId: number,
|
||||
query: IGetUncategorizedTransactionsQuery
|
||||
) {
|
||||
const { UncategorizedCashflowTransaction } = this.tenancy.models(tenantId);
|
||||
const {
|
||||
UncategorizedCashflowTransaction,
|
||||
RecognizedBankTransaction,
|
||||
MatchedBankTransaction,
|
||||
Account,
|
||||
} = this.tenancy.models(tenantId);
|
||||
const knex = this.tenancy.knex(tenantId);
|
||||
|
||||
// Parsed query with default values.
|
||||
const _query = {
|
||||
@@ -30,6 +37,15 @@ export class GetUncategorizedTransactions {
|
||||
pageSize: 20,
|
||||
...query,
|
||||
};
|
||||
|
||||
// Initialize the ORM models metadata.
|
||||
await initialize(knex, [
|
||||
UncategorizedCashflowTransaction,
|
||||
MatchedBankTransaction,
|
||||
RecognizedBankTransaction,
|
||||
Account,
|
||||
]);
|
||||
|
||||
const { results, pagination } =
|
||||
await UncategorizedCashflowTransaction.query()
|
||||
.onBuild((q) => {
|
||||
@@ -40,6 +56,9 @@ export class GetUncategorizedTransactions {
|
||||
q.withGraphFetched('account');
|
||||
q.withGraphFetched('recognizedTransaction.assignAccount');
|
||||
|
||||
q.withGraphJoined('matchedBankTransactions');
|
||||
|
||||
q.whereNull('matchedBankTransactions.id');
|
||||
q.orderBy('date', 'DESC');
|
||||
})
|
||||
.pagination(_query.page - 1, _query.pageSize);
|
||||
|
||||
@@ -34,7 +34,6 @@ import { DialogsName } from '@/constants/dialogs';
|
||||
|
||||
function RuleFormContentFormRoot({
|
||||
// #withDialogActions
|
||||
openDialog,
|
||||
closeDialog,
|
||||
}) {
|
||||
const { accounts, bankRule, isEditMode, bankRuleId } =
|
||||
@@ -180,6 +179,10 @@ export const RuleFormContentForm = R.compose(withDialogActions)(
|
||||
RuleFormContentFormRoot,
|
||||
);
|
||||
|
||||
/**
|
||||
* Rule form conditions stack.
|
||||
* @returns {React.ReactNode}
|
||||
*/
|
||||
function RuleFormConditions() {
|
||||
const { values, setFieldValue } = useFormikContext<RuleFormValues>();
|
||||
|
||||
@@ -245,6 +248,10 @@ function RuleFormConditions() {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rule form actions buttons.
|
||||
* @returns {React.ReactNode}
|
||||
*/
|
||||
function RuleFormActionsRoot({
|
||||
// #withDialogActions
|
||||
closeDialog,
|
||||
|
||||
@@ -135,7 +135,7 @@ function AccountTransactionsActionsBar({
|
||||
<Popover
|
||||
minimal={true}
|
||||
interactionKind={PopoverInteractionKind.CLICK}
|
||||
position={Position.BOTTOM_LEFT}
|
||||
position={Position.BOTTOM_RIGHT}
|
||||
modifiers={{
|
||||
offset: { offset: '0, 4' },
|
||||
}}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { isEmpty } from 'lodash';
|
||||
import * as R from 'ramda';
|
||||
import { AnchorButton, Button, Intent, Tag, Text } from '@blueprintjs/core';
|
||||
import { FastField, FastFieldProps, Formik } from 'formik';
|
||||
import { FastField, FastFieldProps, Formik, useFormikContext } from 'formik';
|
||||
import { AppToaster, Box, FormatNumber, Group, Stack } from '@/components';
|
||||
import {
|
||||
MatchingTransactionBoot,
|
||||
@@ -27,12 +27,18 @@ const initialValues = {
|
||||
matched: {},
|
||||
};
|
||||
|
||||
export function MatchingBankTransaction() {
|
||||
function MatchingBankTransactionRoot({
|
||||
// #withBankingActions
|
||||
closeMatchingTransactionAside,
|
||||
}) {
|
||||
const { uncategorizedTransactionId } = useCategorizeTransactionTabsBoot();
|
||||
const { mutateAsync: matchTransaction } = useMatchUncategorizedTransaction();
|
||||
|
||||
// Handles the form submitting.
|
||||
const handleSubmit = (values: MatchingTransactionFormValues) => {
|
||||
const handleSubmit = (
|
||||
values: MatchingTransactionFormValues,
|
||||
{ setSubmitting }: FormikHelpers<MatchingTransactionFormValues>,
|
||||
) => {
|
||||
const _values = transformToReq(values);
|
||||
|
||||
if (_values.matchedTransactions?.length === 0) {
|
||||
@@ -42,18 +48,22 @@ export function MatchingBankTransaction() {
|
||||
});
|
||||
return;
|
||||
}
|
||||
setSubmitting(true);
|
||||
matchTransaction({ id: uncategorizedTransactionId, value: _values })
|
||||
.then(() => {
|
||||
AppToaster.show({
|
||||
intent: Intent.SUCCESS,
|
||||
message: 'The bank transaction has been matched successfully.',
|
||||
});
|
||||
setSubmitting(false);
|
||||
closeMatchingTransactionAside();
|
||||
})
|
||||
.catch((err) => {
|
||||
AppToaster.show({
|
||||
intent: Intent.DANGER,
|
||||
message: 'Something went wrong.',
|
||||
});
|
||||
setSubmitting(false);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -71,6 +81,10 @@ export function MatchingBankTransaction() {
|
||||
);
|
||||
}
|
||||
|
||||
export const MatchingBankTransaction = R.compose(withBankingActions)(
|
||||
MatchingBankTransactionRoot,
|
||||
);
|
||||
|
||||
function MatchingBankTransactionContent() {
|
||||
return (
|
||||
<Box className={styles.root}>
|
||||
@@ -193,12 +207,16 @@ interface MatchTransctionFooterProps extends WithBankingActionsProps {}
|
||||
*/
|
||||
const MatchTransactionFooter = R.compose(withBankingActions)(
|
||||
({ closeMatchingTransactionAside }: MatchTransctionFooterProps) => {
|
||||
const { submitForm, isSubmitting } = useFormikContext();
|
||||
const totalPending = useGetPendingAmountMatched();
|
||||
const showReconcileLink = useIsShowReconcileTransactionLink();
|
||||
|
||||
const handleCancelBtnClick = () => {
|
||||
closeMatchingTransactionAside();
|
||||
};
|
||||
const handleSubmitBtnClick = () => {
|
||||
submitForm();
|
||||
};
|
||||
|
||||
return (
|
||||
<Box className={styles.footer}>
|
||||
@@ -223,7 +241,8 @@ const MatchTransactionFooter = R.compose(withBankingActions)(
|
||||
<Button
|
||||
intent={Intent.PRIMARY}
|
||||
style={{ minWidth: 85 }}
|
||||
type="submit"
|
||||
onClick={handleSubmitBtnClick}
|
||||
loading={isSubmitting}
|
||||
>
|
||||
Match
|
||||
</Button>
|
||||
|
||||
@@ -116,6 +116,9 @@ export function useDeleteBankRule(
|
||||
queryClient.invalidateQueries(
|
||||
QUERY_KEY.RECOGNIZED_BANK_TRANSACTIONS_INFINITY,
|
||||
);
|
||||
queryClient.invalidateQueries([
|
||||
t.CASHFLOW_ACCOUNT_UNCATEGORIZED_TRANSACTIONS_INFINITY,
|
||||
]);
|
||||
},
|
||||
...options,
|
||||
},
|
||||
|
||||
@@ -1228,6 +1228,8 @@ export const getDashboardRoutes = () => [
|
||||
() => import('@/containers/Banking/Rules/RulesList/RulesLandingPage'),
|
||||
),
|
||||
pageTitle: 'Bank Rules',
|
||||
breadcrumb: 'Bank Rules',
|
||||
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
|
||||
},
|
||||
// Homepage
|
||||
{
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
// @ts-nocheck
|
||||
import t from '@/store/types';
|
||||
|
||||
/**
|
||||
* Sets global table state of the table.
|
||||
* @param {object} queries
|
||||
*/
|
||||
export const setUncategorizedTransactionIdForMatching = (
|
||||
uncategorizedTransactionId: number,
|
||||
) => {
|
||||
return {
|
||||
type: 'setUncategorizedTransactionIdForMatching',
|
||||
payload: uncategorizedTransactionId,
|
||||
};
|
||||
};
|
||||
|
||||
export const closeMatchingTransactionAside = () => {
|
||||
return {
|
||||
type: 'closeMatchingTransactionAside',
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user