Skip to content

Commit

Permalink
fix: improvements to bank matching transactions
Browse files Browse the repository at this point in the history
  • Loading branch information
abouolia committed Jul 6, 2024
1 parent cd9039f commit b7487f1
Show file tree
Hide file tree
Showing 18 changed files with 188 additions and 233 deletions.
4 changes: 3 additions & 1 deletion packages/server/src/loaders/eventEmitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ import { ValidateMatchingOnPaymentMadeDelete } from '@/services/Banking/Matching
import { ValidateMatchingOnCashflowDelete } from '@/services/Banking/Matching/events/ValidateMatchingOnCashflowDelete';
import { RecognizeSyncedBankTranasctions } from '@/services/Banking/Plaid/subscribers/RecognizeSyncedBankTransactions';
import { UnlinkBankRuleOnDeleteBankRule } from '@/services/Banking/Rules/events/UnlinkBankRuleOnDeleteBankRule';
import { DecrementUncategorizedTransactionOnMatching } from '@/services/Banking/Matching/events/DecrementUncategorizedTransactionsOnMatch';

export default () => {
return new EventPublisher();
Expand Down Expand Up @@ -258,6 +259,7 @@ export const susbcribers = () => {
// Bank Rules
TriggerRecognizedTransactions,
UnlinkBankRuleOnDeleteBankRule,
DecrementUncategorizedTransactionOnMatching,

// Validate matching
ValidateMatchingOnCashflowDelete,
Expand All @@ -266,7 +268,7 @@ export const susbcribers = () => {
ValidateMatchingOnPaymentReceivedDelete,
ValidateMatchingOnPaymentMadeDelete,

// Plaid
// Plaid
RecognizeSyncedBankTranasctions,
];
};
30 changes: 28 additions & 2 deletions packages/server/src/models/UncategorizedCashflowTransaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,34 @@ export default class UncategorizedCashflowTransaction extends mixin(
* Filters the excluded transactions.
*/
excluded(query) {
query.whereNotNull('excluded_at')
}
query.whereNotNull('excluded_at');
},

/**
* Filter out the recognized transactions.
* @param query
*/
recognized(query) {
query.whereNotNull('recognizedTransactionId');
},

/**
* Filter out the not recognized transactions.
* @param query
*/
notRecognized(query) {
query.whereNull('recognizedTransactionId');
},

categorized(query) {
query.whereNotNull('categorizeRefType');
query.whereNotNull('categorizeRefId');
},

notCategorized(query) {
query.whereNull('categorizeRefType');
query.whereNull('categorizeRefId');
},
};
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { Server } from 'socket.io';
import { Inject, Service } from 'typedi';
import { initialize } from 'objection';
import HasTenancyService from '@/services/Tenancy/TenancyService';

@Service()
export class GetBankAccountSummary {
Expand All @@ -14,22 +14,41 @@ export class GetBankAccountSummary {
* @returns
*/
public async getBankAccountSummary(tenantId: number, bankAccountId: number) {
const knex = this.tenancy.knex(tenantId);
const {
Account,
UncategorizedCashflowTransaction,
RecognizedBankTransaction,
} = this.tenancy.models(tenantId);

await initialize(knex, [
UncategorizedCashflowTransaction,
RecognizedBankTransaction,
]);
const bankAccount = await Account.query()
.findById(bankAccountId)
.throwIfNotFound();

// Retrieves the uncategorized transactions count of the given bank account.
const uncategorizedTranasctionsCount =
await UncategorizedCashflowTransaction.query()
.where('accountId', bankAccountId)
.count('id as total')
.first();
await UncategorizedCashflowTransaction.query().onBuild((q) => {
// Include just the given account.
q.where('accountId', bankAccountId);

// Only the not excluded.
q.modify('notExcluded');

// Only the not categorized.
q.modify('notCategorized');

// Only the not matched bank transactions.
q.withGraphJoined('matchedBankTransactions');
q.whereNull('matchedBankTransactions.id');

// Count the results.
q.count('uncategorized_cashflow_transactions.id as total');
q.first();
});

// Retrieves the recognized transactions count of the given bank account.
const recognizedTransactionsCount = await RecognizedBankTransaction.query()
Expand All @@ -43,8 +62,8 @@ export class GetBankAccountSummary {
.first();

const totalUncategorizedTransactions =
uncategorizedTranasctionsCount?.total;
const totalRecognizedTransactions = recognizedTransactionsCount?.total;
uncategorizedTranasctionsCount?.total || 0;
const totalRecognizedTransactions = recognizedTransactionsCount?.total || 0;

return {
name: bankAccount.name,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export class UnmatchMatchedBankTransaction {
return this.uow.withTransaction(tenantId, async (trx) => {
await this.eventPublisher.emitAsync(events.bankMatch.onUnmatching, {
tenantId,
uncategorizedTransactionId,
trx,
} as IBankTransactionUnmatchingEventPayload);

Expand All @@ -40,6 +41,7 @@ export class UnmatchMatchedBankTransaction {

await this.eventPublisher.emitAsync(events.bankMatch.onUnmatched, {
tenantId,
uncategorizedTransactionId,
trx,
} as IBankTransactionUnmatchingEventPayload);
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Knex } from 'knex';
import { Inject, Service } from 'typedi';
import { ServiceError } from '@/exceptions';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { Inject, Service } from 'typedi';
import { ERRORS } from './types';

@Service()
Expand All @@ -18,12 +19,13 @@ export class ValidateTransactionMatched {
public async validateTransactionNoMatchLinking(
tenantId: number,
referenceType: string,
referenceId: number
referenceId: number,
trx?: Knex.Transaction
) {
const { MatchedBankTransaction } = this.tenancy.models(tenantId);

const foundMatchedTransaction =
await MatchedBankTransaction.query().findOne({
await MatchedBankTransaction.query(trx).findOne({
referenceType,
referenceId,
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { Inject, Service } from 'typedi';
import events from '@/subscribers/events';
import {
IBankTransactionMatchedEventPayload,
IBankTransactionUnmatchedEventPayload,
} from '../types';
import HasTenancyService from '@/services/Tenancy/TenancyService';

@Service()
export class DecrementUncategorizedTransactionOnMatching {
@Inject()
private tenancy: HasTenancyService;
/**
* Constructor method.
*/
public attach(bus) {
bus.subscribe(
events.bankMatch.onMatched,
this.decrementUnCategorizedTransactionsOnMatching.bind(this)
);
bus.subscribe(
events.bankMatch.onUnmatched,
this.incrementUnCategorizedTransactionsOnUnmatching.bind(this)
);
}

/**
* Validates the cashflow transaction whether matched with bank transaction on deleting.
* @param {IManualJournalDeletingPayload}
*/
public async decrementUnCategorizedTransactionsOnMatching({
tenantId,
uncategorizedTransactionId,
matchTransactionsDTO,
trx,
}: IBankTransactionMatchedEventPayload) {
const { UncategorizedCashflowTransaction, Account } =
this.tenancy.models(tenantId);

const transaction = await UncategorizedCashflowTransaction.query().findById(
uncategorizedTransactionId
);
//
await Account.query(trx)
.findById(transaction.accountId)
.decrement('uncategorizedTransactions', 1);
}

/**
* Validates the cashflow transaction whether matched with bank transaction on deleting.
* @param {IManualJournalDeletingPayload}
*/
public async incrementUnCategorizedTransactionsOnUnmatching({
tenantId,
uncategorizedTransactionId,
trx,
}: IBankTransactionUnmatchedEventPayload) {
const { UncategorizedCashflowTransaction, Account } =
this.tenancy.models(tenantId);

const transaction = await UncategorizedCashflowTransaction.query().findById(
uncategorizedTransactionId
);
//
await Account.query(trx)
.findById(transaction.accountId)
.decrement('uncategorizedTransactions', 1);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Inject, Service } from 'typedi';
import { IManualJournalDeletingPayload } from '@/interfaces';
import { ICommandCashflowDeletingPayload, IManualJournalDeletingPayload } from '@/interfaces';
import events from '@/subscribers/events';
import { ValidateTransactionMatched } from '../ValidateTransactionsMatched';

Expand All @@ -24,13 +24,14 @@ export class ValidateMatchingOnCashflowDelete {
*/
public async validateMatchingOnCashflowDeleting({
tenantId,
oldManualJournal,
oldCashflowTransaction,
trx,
}: IManualJournalDeletingPayload) {
}: ICommandCashflowDeletingPayload) {
await this.validateNoMatchingLinkedService.validateTransactionNoMatchLinking(
tenantId,
'ManualJournal',
oldManualJournal.id
'CashflowTransaction',
oldCashflowTransaction.id,
trx
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ export class ValidateMatchingOnExpenseDelete {
await this.validateNoMatchingLinkedService.validateTransactionNoMatchLinking(
tenantId,
'Expense',
oldExpense.id
oldExpense.id,
trx
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ export class ValidateMatchingOnManualJournalDelete {
await this.validateNoMatchingLinkedService.validateTransactionNoMatchLinking(
tenantId,
'ManualJournal',
oldManualJournal.id
oldManualJournal.id,
trx
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ export class ValidateMatchingOnPaymentMadeDelete {
await this.validateNoMatchingLinkedService.validateTransactionNoMatchLinking(
tenantId,
'PaymentMade',
oldBillPayment.id
oldBillPayment.id,
trx
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ export class ValidateMatchingOnPaymentReceivedDelete {
await this.validateNoMatchingLinkedService.validateTransactionNoMatchLinking(
tenantId,
'PaymentReceive',
oldPaymentReceive.id
oldPaymentReceive.id,
trx
);
}
}
4 changes: 4 additions & 0 deletions packages/server/src/services/Banking/Matching/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,14 @@ export interface IBankTransactionMatchedEventPayload {

export interface IBankTransactionUnmatchingEventPayload {
tenantId: number;
uncategorizedTransactionId: number;
trx?: Knex.Transaction;
}

export interface IBankTransactionUnmatchedEventPayload {
tenantId: number;
uncategorizedTransactionId: number;
trx?: Knex.Transaction;
}

export interface IMatchTransactionDTO {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,14 @@ function AccountDeleteTransactionAlert({
'Cannot delete transaction converted from uncategorized transaction but you uncategorize it.',
intent: Intent.DANGER,
});
} else if (
errors.find((e) => e.type === 'CANNOT_DELETE_TRANSACTION_MATCHED')
) {
AppToaster.show({
message:
'Cannot delete a transaction matched to the bank transaction',
intent: Intent.DANGER,
});
}
},
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
color: rgb(21, 82, 200),
}
}

&:hover:not(.active){
border-color: #c0c0c0;
}
Expand All @@ -25,7 +24,7 @@
margin: 0;
}
.checkbox:global(.bp4-control.bp4-checkbox) :global .bp4-control-indicator{
border-color: #CBCBCB;
box-shadow: 0 0 0 1px #CBCBCB;
}
.checkbox:global(.bp4-control.bp4-checkbox) :global .bp4-control-indicator{
margin-right: 4px;
Expand All @@ -34,9 +33,17 @@
width: 16px;
}

.checkbox:global(.bp4-control.bp4-checkbox) :global input:checked ~ .bp4-control-indicator{
box-shadow: 0 0 0 1px #0069ff;
}

.label {
color: #10161A;
font-size: 15px;
color: #252A33;
font-size: 15px;
}
.label :global strong {
font-weight: 500;
font-variant-numeric:tabular-nums;
}

.date {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export interface MatchTransactionCheckboxProps {
active?: boolean;
initialActive?: boolean;
onChange?: (state: boolean) => void;
label: string;
label: string | React.ReactNode;
date: string;
}

Expand Down Expand Up @@ -43,7 +43,7 @@ export function MatchTransactionCheckbox({
position="apart"
onClick={handleClick}
>
<Stack spacing={3}>
<Stack spacing={2}>
<span className={styles.label}>{label}</span>
<Text className={styles.date}>Date: {date}</Text>
</Stack>
Expand Down
Loading

0 comments on commit b7487f1

Please sign in to comment.