mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-18 13:50:31 +00:00
fix: recognize transactions on editing bank rule
This commit is contained in:
@@ -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 {
|
||||
@@ -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'
|
||||
|
||||
@@ -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.where('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
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
export interface RevertRecognizedTransactionsCriteria {
|
||||
batch?: string;
|
||||
accountId?: number;
|
||||
}
|
||||
|
||||
|
||||
export interface RecognizeTransactionsCriteria {
|
||||
batch?: string;
|
||||
accountId?: number;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Container, { Service } from 'typedi';
|
||||
import { RecognizeTranasctionsService } from './RecognizeTranasctionsService';
|
||||
import { RecognizeTranasctionsService } from '../RecognizeTranasctionsService';
|
||||
|
||||
@Service()
|
||||
export class RegonizeTransactionsJob {
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user