Skip to content

Commit

Permalink
Merge pull request #569 from bigcapitalhq/fix-edit-bank-rule-recognized
Browse files Browse the repository at this point in the history
fix: Recognize transactions on editing bank rule
  • Loading branch information
abouolia authored Aug 7, 2024
2 parents 3fcc70c + 0025dcf commit f67c63a
Show file tree
Hide file tree
Showing 14 changed files with 227 additions and 87 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,6 @@ export class BankingRulesController extends BaseController {
body('assign_account_id').isInt({ min: 0 }),
body('assign_payee').isString().optional({ nullable: true }),
body('assign_memo').isString().optional({ nullable: true }),

body('recognition').isBoolean().toBoolean().optional({ nullable: true }),
];
}

Expand Down
6 changes: 5 additions & 1 deletion packages/server/src/loaders/jobs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ import { PaymentReceiveMailNotificationJob } from '@/services/Sales/PaymentRecei
import { PlaidFetchTransactionsJob } from '@/services/Banking/Plaid/PlaidFetchTransactionsJob';
import { ImportDeleteExpiredFilesJobs } from '@/services/Import/jobs/ImportDeleteExpiredFilesJob';
import { SendVerifyMailJob } from '@/services/Authentication/jobs/SendVerifyMailJob';
import { RegonizeTransactionsJob } from '@/services/Banking/RegonizeTranasctions/RecognizeTransactionsJob';
import { ReregonizeTransactionsJob } from '@/services/Banking/RegonizeTranasctions/jobs/RerecognizeTransactionsJob';
import { RegonizeTransactionsJob } from '@/services/Banking/RegonizeTranasctions/jobs/RecognizeTransactionsJob';
import { RevertRegonizeTransactionsJob } from '@/services/Banking/RegonizeTranasctions/jobs/RevertRecognizedTransactionsJob';

export default ({ agenda }: { agenda: Agenda }) => {
new ResetPasswordMailJob(agenda);
Expand All @@ -31,6 +33,8 @@ export default ({ agenda }: { agenda: Agenda }) => {
new ImportDeleteExpiredFilesJobs(agenda);
new SendVerifyMailJob(agenda);
new RegonizeTransactionsJob(agenda);
new ReregonizeTransactionsJob(agenda);
new RevertRegonizeTransactionsJob(agenda);

agenda.start().then(() => {
agenda.every('1 hours', 'delete-expired-imported-files', {});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ export class RecognizeSyncedBankTranasctions extends EventSubscriber {
runAfterTransaction(trx, async () => {
await this.recognizeTranasctionsService.recognizeTransactions(
tenantId,
batch
null,
{ batch }
);
});
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { Knex } from 'knex';
import { Inject, Service } from 'typedi';
import { castArray, isEmpty } from 'lodash';
import UncategorizedCashflowTransaction from '@/models/UncategorizedCashflowTransaction';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { transformToMapBy } from '@/utils';
import { PromisePool } from '@supercharge/promise-pool';
import { BankRule } from '@/models/BankRule';
import { bankRulesMatchTransaction } from './_utils';
import { RecognizeTransactionsCriteria } from './_types';

@Service()
export class RecognizeTranasctionsService {
Expand Down Expand Up @@ -48,24 +50,42 @@ export class RecognizeTranasctionsService {
/**
* Regonized the uncategorized transactions.
* @param {number} tenantId -
* @param {number|Array<number>} ruleId - The target rule id/ids.
* @param {RecognizeTransactionsCriteria}
* @param {Knex.Transaction} trx -
*/
public async recognizeTransactions(
tenantId: number,
batch: string = '',
ruleId?: number | Array<number>,
transactionsCriteria?: RecognizeTransactionsCriteria,
trx?: Knex.Transaction
) {
const { UncategorizedCashflowTransaction, BankRule } =
this.tenancy.models(tenantId);

const uncategorizedTranasctions =
await UncategorizedCashflowTransaction.query().onBuild((query) => {
query.where('recognized_transaction_id', null);
query.where('categorized', false);
await UncategorizedCashflowTransaction.query(trx).onBuild((query) => {
query.modify('notRecognized');
query.modify('notCategorized');

if (batch) query.where('batch', batch);
// Filter the transactions based on the given criteria.
if (transactionsCriteria?.batch) {
query.where('batch', transactionsCriteria.batch);
}
if (transactionsCriteria?.accountId) {
query.where('accountId', transactionsCriteria.accountId);
}
});
const bankRules = await BankRule.query().withGraphFetched('conditions');

const bankRules = await BankRule.query(trx).onBuild((q) => {
const rulesIds = castArray(ruleId);

if (!isEmpty(rulesIds)) {
q.whereIn('id', rulesIds);
}
q.withGraphFetched('conditions');
});

const bankRulesByAccountId = transformToMapBy(
bankRules,
'applyIfAccountId'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { Inject, Service } from 'typedi';
import { castArray } from 'lodash';
import { Knex } from 'knex';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import UnitOfWork from '@/services/UnitOfWork';
import { RevertRecognizedTransactionsCriteria } from './_types';

@Service()
export class RevertRecognizedTransactions {
@Inject()
private tenancy: HasTenancyService;

@Inject()
private uow: UnitOfWork;

/**
* Revert and unlinks the recognized transactions based on the given bank rule
* and transactions criteria..
* @param {number} tenantId - Tenant id.
* @param {number|Array<number>} bankRuleId - Bank rule id.
* @param {RevertRecognizedTransactionsCriteria} transactionsCriteria -
* @param {Knex.Transaction} trx - Knex transaction.
* @returns {Promise<void>}
*/
public async revertRecognizedTransactions(
tenantId: number,
ruleId?: number | Array<number>,
transactionsCriteria?: RevertRecognizedTransactionsCriteria,
trx?: Knex.Transaction
): Promise<void> {
const { UncategorizedCashflowTransaction, RecognizedBankTransaction } =
this.tenancy.models(tenantId);

const rulesIds = castArray(ruleId);

return this.uow.withTransaction(
tenantId,
async (trx: Knex.Transaction) => {
// Retrieves all the recognized transactions of the banbk rule.
const uncategorizedTransactions =
await UncategorizedCashflowTransaction.query(trx).onBuild((q) => {
q.withGraphJoined('recognizedTransaction');
q.whereNotNull('recognizedTransaction.id');

if (rulesIds.length > 0) {
q.whereIn('recognizedTransaction.bankRuleId', rulesIds);
}
if (transactionsCriteria?.accountId) {
q.where('accountId', transactionsCriteria.accountId);
}
if (transactionsCriteria?.batch) {
q.where('batch', transactionsCriteria.batch);
}
});
const uncategorizedTransactionIds = uncategorizedTransactions.map(
(r) => r.id
);
// Unlink the recongized transactions out of uncategorized transactions.
await UncategorizedCashflowTransaction.query(trx)
.whereIn('id', uncategorizedTransactionIds)
.patch({
recognizedTransactionId: null,
});
// Delete the recognized bank transactions that assocaited to bank rule.
await RecognizedBankTransaction.query(trx)
.whereIn('uncategorizedTransactionId', uncategorizedTransactionIds)
.delete();
},
trx
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export interface RevertRecognizedTransactionsCriteria {
batch?: string;
accountId?: number;
}


export interface RecognizeTransactionsCriteria {
batch?: string;
accountId?: number;
}

Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,10 @@ export class TriggerRecognizedTransactions {
*/
private async recognizedTransactionsOnRuleCreated({
tenantId,
createRuleDTO,
bankRule,
}: IBankRuleEventCreatedPayload) {
const payload = { tenantId };
const payload = { tenantId, ruleId: bankRule.id };

// Cannot run recognition if the option is not enabled.
if (createRuleDTO.recognition) {
return;
}
await this.agenda.now('recognize-uncategorized-transactions-job', payload);
}

Expand All @@ -59,14 +55,14 @@ export class TriggerRecognizedTransactions {
private async recognizedTransactionsOnRuleEdited({
tenantId,
editRuleDTO,
ruleId,
}: IBankRuleEventEditedPayload) {
const payload = { tenantId };
const payload = { tenantId, ruleId };

// Cannot run recognition if the option is not enabled.
if (!editRuleDTO.recognition) {
return;
}
await this.agenda.now('recognize-uncategorized-transactions-job', payload);
await this.agenda.now(
'rerecognize-uncategorized-transactions-job',
payload
);
}

/**
Expand All @@ -75,9 +71,13 @@ export class TriggerRecognizedTransactions {
*/
private async recognizedTransactionsOnRuleDeleted({
tenantId,
ruleId,
}: IBankRuleEventDeletedPayload) {
const payload = { tenantId };
await this.agenda.now('recognize-uncategorized-transactions-job', payload);
const payload = { tenantId, ruleId };
await this.agenda.now(
'revert-recognized-uncategorized-transactions-job',
payload
);
}

/**
Expand All @@ -91,7 +91,7 @@ export class TriggerRecognizedTransactions {
}: IImportFileCommitedEventPayload) {
const importFile = await Import.query().findOne({ importId });
const batch = importFile.paramsParsed.batch;
const payload = { tenantId, batch };
const payload = { tenantId, transactionsCriteria: { batch } };

await this.agenda.now('recognize-uncategorized-transactions-job', payload);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Container, { Service } from 'typedi';
import { RecognizeTranasctionsService } from './RecognizeTranasctionsService';
import { RecognizeTranasctionsService } from '../RecognizeTranasctionsService';

@Service()
export class RegonizeTransactionsJob {
Expand All @@ -18,11 +18,15 @@ export class RegonizeTransactionsJob {
* Triggers sending invoice mail.
*/
private handler = async (job, done: Function) => {
const { tenantId, batch } = job.attrs.data;
const { tenantId, ruleId, transactionsCriteria } = job.attrs.data;
const regonizeTransactions = Container.get(RecognizeTranasctionsService);

try {
await regonizeTransactions.recognizeTransactions(tenantId, batch);
await regonizeTransactions.recognizeTransactions(
tenantId,
ruleId,
transactionsCriteria
);
done();
} catch (error) {
console.log(error);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import Container, { Service } from 'typedi';
import { RecognizeTranasctionsService } from '../RecognizeTranasctionsService';
import { RevertRecognizedTransactions } from '../RevertRecognizedTransactions';

@Service()
export class ReregonizeTransactionsJob {
/**
* Constructor method.
*/
constructor(agenda) {
agenda.define(
'rerecognize-uncategorized-transactions-job',
{ priority: 'high', concurrency: 2 },
this.handler
);
}

/**
* Triggers sending invoice mail.
*/
private handler = async (job, done: Function) => {
const { tenantId, ruleId, transactionsCriteria } = job.attrs.data;
const regonizeTransactions = Container.get(RecognizeTranasctionsService);
const revertRegonizedTransactions = Container.get(
RevertRecognizedTransactions
);

try {
await revertRegonizedTransactions.revertRecognizedTransactions(
tenantId,
ruleId,
transactionsCriteria
);
await regonizeTransactions.recognizeTransactions(
tenantId,
ruleId,
transactionsCriteria
);
done();
} catch (error) {
console.log(error);
done(error);
}
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import Container, { Service } from 'typedi';
import { RevertRecognizedTransactions } from '../RevertRecognizedTransactions';

@Service()
export class RevertRegonizeTransactionsJob {
/**
* Constructor method.
*/
constructor(agenda) {
agenda.define(
'revert-recognized-uncategorized-transactions-job',
{ priority: 'high', concurrency: 2 },
this.handler
);
}

/**
* Triggers sending invoice mail.
*/
private handler = async (job, done: Function) => {
const { tenantId, ruleId, transactionsCriteria } = job.attrs.data;
const revertRegonizedTransactions = Container.get(
RevertRecognizedTransactions
);

try {
await revertRegonizedTransactions.revertRecognizedTransactions(
tenantId,
ruleId,
transactionsCriteria
);
done();
} catch (error) {
console.log(error);
done(error);
}
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export class CreateBankRuleService {
await this.eventPublisher.emitAsync(events.bankRules.onCreated, {
tenantId,
createRuleDTO,
bankRule,
trx,
} as IBankRuleEventCreatedPayload);

Expand Down
Loading

0 comments on commit f67c63a

Please sign in to comment.