Skip to content

Commit

Permalink
Merge pull request #522 from bigcapitalhq/reconcile-match-transactionss
Browse files Browse the repository at this point in the history
Reconcile match transactionss
  • Loading branch information
abouolia authored Jul 7, 2024
2 parents 168883a + b80bc95 commit aa89653
Show file tree
Hide file tree
Showing 71 changed files with 1,853 additions and 439 deletions.
2 changes: 2 additions & 0 deletions packages/server/src/interfaces/Account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,9 @@ export interface IAccountTransaction {
referenceId: number;

referenceNumber?: string;

transactionNumber?: string;
transactionType?: string;

note?: string;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,9 @@ export interface ICashflowAccountTransaction {

date: Date;
formattedDate: string;

status: string;
formattedStatus: string;

uncategorizedTransactionId: number;
}
2 changes: 2 additions & 0 deletions packages/server/src/interfaces/Ledger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ export interface ILedgerEntry {
date: Date | string;

transactionType: string;
transactionSubType: string;

transactionId: number;

transactionNumber?: string;
Expand Down
6 changes: 5 additions & 1 deletion packages/server/src/loaders/eventEmitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ 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';
import { DecrementUncategorizedTransactionOnExclude } from '@/services/Banking/Exclude/events/DecrementUncategorizedTransactionOnExclude';

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

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

// Plaid
// Plaid
RecognizeSyncedBankTranasctions,
];
};
30 changes: 29 additions & 1 deletion packages/server/src/models/CashflowTransaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import {
getCashflowAccountTransactionsTypes,
getCashflowTransactionType,
} from '@/services/Cashflow/utils';
import AccountTransaction from './AccountTransaction';
import { CASHFLOW_DIRECTION } from '@/services/Cashflow/constants';
import { getTransactionTypeLabel } from '@/utils/transactions-types';

export default class CashflowTransaction extends TenantModel {
transactionType: string;
amount: number;
Expand Down Expand Up @@ -95,6 +95,34 @@ export default class CashflowTransaction extends TenantModel {
return !!this.uncategorizedTransaction;
}

/**
* Model modifiers.
*/
static get modifiers() {
return {
/**
* Filter the published transactions.
*/
published(query) {
query.whereNot('published_at', null);
},

/**
* Filter the not categorized transactions.
*/
notCategorized(query) {
query.whereNull('cashflowTransactions.uncategorizedTransactionId');
},

/**
* Filter the categorized transactions.
*/
categorized(query) {
query.whereNotNull('cashflowTransactions.uncategorizedTransactionId');
},
};
}

/**
* Relationship mapping.
*/
Expand Down
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
2 changes: 2 additions & 0 deletions packages/server/src/services/Accounting/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ export const transformLedgerEntryToTransaction = (
referenceId: entry.transactionId,

transactionNumber: entry.transactionNumber,
transactionType: entry.transactionSubType,

referenceNumber: entry.referenceNumber,

note: entry.note,
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,43 @@ export class GetBankAccountSummary {
* @returns
*/
public async getBankAccountSummary(tenantId: number, bankAccountId: number) {
const knex = this.tenancy.knex(tenantId);
const {
Account,
UncategorizedCashflowTransaction,
RecognizedBankTransaction,
MatchedBankTransaction,
} = this.tenancy.models(tenantId);

await initialize(knex, [
UncategorizedCashflowTransaction,
RecognizedBankTransaction,
MatchedBankTransaction,
]);
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 +64,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 @@ -2,6 +2,12 @@ import HasTenancyService from '@/services/Tenancy/TenancyService';
import UnitOfWork from '@/services/UnitOfWork';
import { Inject, Service } from 'typedi';
import { validateTransactionNotCategorized } from './utils';
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
import events from '@/subscribers/events';
import {
IBankTransactionUnexcludedEventPayload,
IBankTransactionUnexcludingEventPayload,
} from './_types';

@Service()
export class ExcludeBankTransaction {
Expand All @@ -11,6 +17,9 @@ export class ExcludeBankTransaction {
@Inject()
private uow: UnitOfWork;

@Inject()
private eventPublisher: EventPublisher;

/**
* Marks the given bank transaction as excluded.
* @param {number} tenantId
Expand All @@ -31,11 +40,23 @@ export class ExcludeBankTransaction {
validateTransactionNotCategorized(oldUncategorizedTransaction);

return this.uow.withTransaction(tenantId, async (trx) => {
await this.eventPublisher.emitAsync(events.bankTransactions.onExcluding, {
tenantId,
uncategorizedTransactionId,
trx,
} as IBankTransactionUnexcludingEventPayload);

await UncategorizedCashflowTransaction.query(trx)
.findById(uncategorizedTransactionId)
.patch({
excludedAt: new Date(),
});

await this.eventPublisher.emitAsync(events.bankTransactions.onExcluded, {
tenantId,
uncategorizedTransactionId,
trx,
} as IBankTransactionUnexcludedEventPayload);
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ import HasTenancyService from '@/services/Tenancy/TenancyService';
import UnitOfWork from '@/services/UnitOfWork';
import { Inject, Service } from 'typedi';
import { validateTransactionNotCategorized } from './utils';
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
import events from '@/subscribers/events';
import {
IBankTransactionExcludedEventPayload,
IBankTransactionExcludingEventPayload,
} from './_types';

@Service()
export class UnexcludeBankTransaction {
Expand All @@ -11,6 +17,9 @@ export class UnexcludeBankTransaction {
@Inject()
private uow: UnitOfWork;

@Inject()
private eventPublisher: EventPublisher;

/**
* Marks the given bank transaction as excluded.
* @param {number} tenantId
Expand All @@ -20,7 +29,7 @@ export class UnexcludeBankTransaction {
public async unexcludeBankTransaction(
tenantId: number,
uncategorizedTransactionId: number
) {
): Promise<void> {
const { UncategorizedCashflowTransaction } = this.tenancy.models(tenantId);

const oldUncategorizedTransaction =
Expand All @@ -31,11 +40,27 @@ export class UnexcludeBankTransaction {
validateTransactionNotCategorized(oldUncategorizedTransaction);

return this.uow.withTransaction(tenantId, async (trx) => {
await this.eventPublisher.emitAsync(
events.bankTransactions.onUnexcluding,
{
tenantId,
uncategorizedTransactionId,
} as IBankTransactionExcludingEventPayload
);

await UncategorizedCashflowTransaction.query(trx)
.findById(uncategorizedTransactionId)
.patch({
excludedAt: null,
});

await this.eventPublisher.emitAsync(
events.bankTransactions.onUnexcluded,
{
tenantId,
uncategorizedTransactionId,
} as IBankTransactionExcludedEventPayload
);
});
}
}
26 changes: 25 additions & 1 deletion packages/server/src/services/Banking/Exclude/_types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,30 @@
import { Knex } from "knex";

export interface ExcludedBankTransactionsQuery {
page?: number;
pageSize?: number;
accountId?: number;
}
}

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

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

export interface IBankTransactionExcludingEventPayload {
tenantId: number;
uncategorizedTransactionId: number;
trx?: Knex.Transaction
}
export interface IBankTransactionExcludedEventPayload {
tenantId: number;
uncategorizedTransactionId: number;
trx?: Knex.Transaction
}
Loading

0 comments on commit aa89653

Please sign in to comment.