refactor: tenant proxy providers

This commit is contained in:
Ahmed Bouhuolia
2025-02-15 23:52:12 +02:00
parent 36851d3209
commit 5c0bb52b59
302 changed files with 2396 additions and 1677 deletions

View File

@@ -22,7 +22,7 @@ import { DynamicListModule } from '../DynamicListing/DynamicList.module';
const models = [RegisterTenancyModel(BankAccount)];
@Module({
imports: [TenancyDatabaseModule, DynamicListModule],
imports: [TenancyDatabaseModule, DynamicListModule, ...models],
controllers: [AccountsController],
providers: [
AccountsApplication,
@@ -38,7 +38,6 @@ const models = [RegisterTenancyModel(BankAccount)];
GetAccountTypesService,
GetAccountTransactionsService,
GetAccountsService,
...models,
],
exports: [AccountRepository, CreateAccountService, ...models],
})

View File

@@ -6,16 +6,17 @@ import { AccountRepository } from './repositories/Account.repository';
import { UnitOfWork } from '../Tenancy/TenancyDB/UnitOfWork.service';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { events } from '@/common/events/events';
import { TenantModelProxy } from '../System/models/TenantBaseModel';
@Injectable()
export class ActivateAccount {
constructor(
private readonly eventEmitter: EventEmitter2,
private readonly uow: UnitOfWork,
private readonly accountRepository: AccountRepository,
@Inject(Account.name)
private readonly accountModel: typeof Account,
private readonly accountRepository: AccountRepository,
private readonly accountModel: TenantModelProxy<typeof Account>,
) {}
/**
@@ -25,7 +26,7 @@ export class ActivateAccount {
*/
public activateAccount = async (accountId: number, activate?: boolean) => {
// Retrieve the given account or throw not found error.
const oldAccount = await this.accountModel
const oldAccount = await this.accountModel()
.query()
.findById(accountId)
.throwIfNotFound();

View File

@@ -9,12 +9,13 @@ import { AccountRepository } from './repositories/Account.repository';
import { AccountTypesUtils } from './utils/AccountType.utils';
import { CreateAccountDTO } from './CreateAccount.dto';
import { EditAccountDTO } from './EditAccount.dto';
import { TenantModelProxy } from '../System/models/TenantBaseModel';
@Injectable({ scope: Scope.REQUEST })
export class CommandAccountValidators {
constructor(
@Inject(Account.name)
private readonly accountModel: typeof Account,
private readonly accountModel: TenantModelProxy<typeof Account>,
private readonly accountRepository: AccountRepository,
) {}
@@ -66,7 +67,7 @@ export class CommandAccountValidators {
accountId: number,
notAccountId?: number,
) {
const parentAccount = await this.accountModel
const parentAccount = await this.accountModel()
.query()
.findById(accountId)
.onBuild((query) => {
@@ -89,7 +90,7 @@ export class CommandAccountValidators {
accountCode: string,
notAccountId?: number,
) {
const account = await this.accountModel
const account = await this.accountModel()
.query()
.where('code', accountCode)
.onBuild((query) => {

View File

@@ -14,12 +14,13 @@ import { TenancyContext } from '../Tenancy/TenancyContext.service';
import { events } from '@/common/events/events';
import { CreateAccountDTO } from './CreateAccount.dto';
import { PartialModelObject } from 'objection';
import { TenantModelProxy } from '../System/models/TenantBaseModel';
@Injectable()
export class CreateAccountService {
constructor(
@Inject(Account.name)
private readonly accountModel: typeof Account,
private readonly accountModel: TenantModelProxy<typeof Account>,
private readonly eventEmitter: EventEmitter2,
private readonly uow: UnitOfWork,
private readonly validator: CommandAccountValidators,
@@ -121,9 +122,11 @@ export class CreateAccountService {
} as IAccountEventCreatingPayload);
// Inserts account to the storage.
const account = await this.accountModel.query().insert({
...accountInputModel,
});
const account = await this.accountModel()
.query()
.insert({
...accountInputModel,
});
// Triggers `onAccountCreated` event.
await this.eventEmitter.emitAsync(events.accounts.onCreated, {
account,

View File

@@ -7,11 +7,14 @@ import { EventEmitter2 } from '@nestjs/event-emitter';
import { UnitOfWork } from '../Tenancy/TenancyDB/UnitOfWork.service';
import { events } from '@/common/events/events';
import { IAccountEventDeletedPayload } from './Accounts.types';
import { TenantModelProxy } from '../System/models/TenantBaseModel';
@Injectable()
export class DeleteAccount {
constructor(
@Inject(Account.name) private accountModel: typeof Account,
@Inject(Account.name)
private accountModel: TenantModelProxy<typeof Account>,
private eventEmitter: EventEmitter2,
private uow: UnitOfWork,
private validator: CommandAccountValidators,
@@ -38,7 +41,7 @@ export class DeleteAccount {
? parentAccountId
: [parentAccountId];
await this.accountModel
await this.accountModel()
.query(trx)
.whereIn('parent_account_id', accountsIds)
.patch({ parentAccountId: null });
@@ -50,7 +53,7 @@ export class DeleteAccount {
*/
public deleteAccount = async (accountId: number): Promise<void> => {
// Retrieve account or not found service error.
const oldAccount = await this.accountModel.query().findById(accountId);
const oldAccount = await this.accountModel().query().findById(accountId);
// Authorize before delete account.
await this.authorize(accountId, oldAccount);
@@ -67,7 +70,7 @@ export class DeleteAccount {
await this.unassociateChildrenAccountsFromParent(accountId, trx);
// Deletes account by the given id.
await this.accountModel.query(trx).deleteById(accountId);
await this.accountModel().query(trx).deleteById(accountId);
// Triggers `onAccountDeleted` event.
await this.eventEmitter.emitAsync(events.accounts.onDeleted, {

View File

@@ -6,6 +6,7 @@ import { EventEmitter2 } from '@nestjs/event-emitter';
import { UnitOfWork } from '../Tenancy/TenancyDB/UnitOfWork.service';
import { events } from '@/common/events/events';
import { EditAccountDTO } from './EditAccount.dto';
import { TenantModelProxy } from '../System/models/TenantBaseModel';
@Injectable()
export class EditAccount {
@@ -15,7 +16,7 @@ export class EditAccount {
private readonly validator: CommandAccountValidators,
@Inject(Account.name)
private readonly accountModel: typeof Account,
private readonly accountModel: TenantModelProxy<typeof Account>,
) {}
/**
@@ -66,7 +67,7 @@ export class EditAccount {
accountDTO: EditAccountDTO,
): Promise<Account> {
// Retrieve the old account or throw not found service error.
const oldAccount = await this.accountModel
const oldAccount = await this.accountModel()
.query()
.findById(accountId)
.throwIfNotFound();
@@ -82,7 +83,7 @@ export class EditAccount {
accountDTO,
});
// Update the account on the storage.
const account = await this.accountModel
const account = await this.accountModel()
.query(trx)
.findById(accountId)
.updateAndFetch({ ...accountDTO });

View File

@@ -5,12 +5,13 @@ import { AccountRepository } from './repositories/Account.repository';
import { TransformerInjectable } from '../Transformer/TransformerInjectable.service';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { events } from '@/common/events/events';
import { TenantModelProxy } from '../System/models/TenantBaseModel';
@Injectable()
export class GetAccount {
constructor(
@Inject(Account.name)
private readonly accountModel: typeof Account,
private readonly accountModel: TenantModelProxy<typeof Account>,
private readonly accountRepository: AccountRepository,
private readonly transformer: TransformerInjectable,
private readonly eventEmitter: EventEmitter2,
@@ -22,7 +23,7 @@ export class GetAccount {
*/
public getAccount = async (accountId: number) => {
// Find the given account or throw not found error.
const account = await this.accountModel
const account = await this.accountModel()
.query()
.findById(accountId)
.withGraphFetched('plaidItem')

View File

@@ -7,6 +7,7 @@ import { AccountTransaction } from './models/AccountTransaction.model';
import { Account } from './models/Account.model';
import { Inject, Injectable } from '@nestjs/common';
import { TransformerInjectable } from '../Transformer/TransformerInjectable.service';
import { TenantModelProxy } from '../System/models/TenantBaseModel';
@Injectable()
export class GetAccountTransactionsService {
@@ -14,10 +15,12 @@ export class GetAccountTransactionsService {
private readonly transformer: TransformerInjectable,
@Inject(AccountTransaction.name)
private readonly accountTransaction: typeof AccountTransaction,
private readonly accountTransaction: TenantModelProxy<
typeof AccountTransaction
>,
@Inject(Account.name)
private readonly account: typeof Account,
private readonly account: TenantModelProxy<typeof Account>,
) {}
/**
@@ -29,9 +32,9 @@ export class GetAccountTransactionsService {
): Promise<IGetAccountTransactionPOJO[]> => {
// Retrieve the given account or throw not found error.
if (filter.accountId) {
await this.account.query().findById(filter.accountId).throwIfNotFound();
await this.account().query().findById(filter.accountId).throwIfNotFound();
}
const transactions = await this.accountTransaction
const transactions = await this.accountTransaction()
.query()
.onBuild((query) => {
query.orderBy('date', 'DESC');

View File

@@ -7,6 +7,7 @@ import { TransformerInjectable } from '../Transformer/TransformerInjectable.serv
import { Account } from './models/Account.model';
import { AccountRepository } from './repositories/Account.repository';
import { IFilterMeta } from '@/interfaces/Model';
import { TenantModelProxy } from '../System/models/TenantBaseModel';
@Injectable()
export class GetAccountsService {
@@ -16,7 +17,7 @@ export class GetAccountsService {
private readonly accountRepository: AccountRepository,
@Inject(Account.name)
private readonly accountModel: typeof Account,
private readonly accountModel: TenantModelProxy<typeof Account>,
) {}
/**
@@ -32,14 +33,16 @@ export class GetAccountsService {
// Dynamic list service.
const dynamicList = await this.dynamicListService.dynamicList(
this.accountModel,
this.accountModel(),
filter,
);
// Retrieve accounts model based on the given query.
const accounts = await this.accountModel.query().onBuild((builder) => {
dynamicList.buildQuery()(builder);
builder.modify('inactiveMode', filter.inactiveMode);
});
const accounts = await this.accountModel()
.query()
.onBuild((builder) => {
dynamicList.buildQuery()(builder);
builder.modify('inactiveMode', filter.inactiveMode);
});
const accountsGraph = await this.accountRepository.getDependencyGraph();
// Retrieves the transformed accounts collection.

View File

@@ -117,6 +117,8 @@ import { StripePaymentModule } from '../StripePayment/StripePayment.module';
cls.set('organizationId', req.headers['organization-id']);
cls.set('userId', 1);
},
generateId: true,
saveReq: true,
},
}),
TenancyDatabaseModule,
@@ -152,13 +154,13 @@ import { StripePaymentModule } from '../StripePayment/StripePayment.module';
BankingTransactionsModule,
BankingTransactionsExcludeModule,
BankingTransactionsRegonizeModule,
BankingMatchingModule,
TransactionsLockingModule,
SettingsModule,
// BankingMatchingModule,
// TransactionsLockingModule,
// SettingsModule,
InventoryAdjustmentsModule,
PostHogModule,
EventTrackerModule,
FinancialStatementsModule,
// FinancialStatementsModule,
StripePaymentModule,
],
controllers: [AppController],

View File

@@ -20,9 +20,8 @@ const models = [
@Module({
controllers: [BankRulesController],
imports: [forwardRef(() => BankingTransactionsRegonizeModule)],
imports: [forwardRef(() => BankingTransactionsRegonizeModule), ...models],
providers: [
...models,
CreateBankRuleService,
EditBankRuleService,
DeleteBankRuleService,
@@ -30,7 +29,7 @@ const models = [
GetBankRuleService,
GetBankRulesService,
BankRulesApplication,
UnlinkBankRuleOnDeleteBankRuleSubscriber
UnlinkBankRuleOnDeleteBankRuleSubscriber,
],
exports: [...models, DeleteBankRuleService, DeleteBankRulesService],
})

View File

@@ -9,13 +9,16 @@ import { BankRuleCondition } from '../models/BankRuleCondition';
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { events } from '@/common/events/events';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class DeleteBankRuleService {
constructor(
@Inject(BankRule.name) private bankRuleModel: typeof BankRule,
@Inject(BankRule.name)
private bankRuleModel: TenantModelProxy<typeof BankRule>,
@Inject(BankRuleCondition.name)
private bankRuleConditionModel: typeof BankRuleCondition,
private bankRuleConditionModel: TenantModelProxy<typeof BankRuleCondition>,
private readonly uow: UnitOfWork,
private readonly eventPublisher: EventEmitter2,
@@ -30,7 +33,7 @@ export class DeleteBankRuleService {
ruleId: number,
trx?: Knex.Transaction,
): Promise<void> {
const oldBankRule = await this.bankRuleModel
const oldBankRule = await this.bankRuleModel()
.query()
.findById(ruleId)
.throwIfNotFound();
@@ -43,11 +46,12 @@ export class DeleteBankRuleService {
trx,
} as IBankRuleEventDeletingPayload);
await this.bankRuleConditionModel
await this.bankRuleConditionModel()
.query(trx)
.where('ruleId', ruleId)
.delete();
await this.bankRuleModel.query(trx).findById(ruleId).delete();
await this.bankRuleModel().query(trx).findById(ruleId).delete();
// Triggers `onBankRuleDeleted` event.
await this.eventPublisher.emitAsync(events.bankRules.onDeleted, {

View File

@@ -8,6 +8,7 @@ import { BankRule } from '../models/BankRule';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { events } from '@/common/events/events';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class EditBankRuleService {
@@ -15,7 +16,8 @@ export class EditBankRuleService {
private readonly uow: UnitOfWork,
private readonly eventPublisher: EventEmitter2,
@Inject(BankRule.name) private bankRuleModel: typeof BankRule,
@Inject(BankRule.name)
private bankRuleModel: TenantModelProxy<typeof BankRule>,
) {}
/**
@@ -34,11 +36,9 @@ export class EditBankRuleService {
* @param {number} ruleId -
* @param {IEditBankRuleDTO} editBankDTO
*/
public async editBankRule(
ruleId: number,
editRuleDTO: IEditBankRuleDTO
) {
const oldBankRule = await this.bankRuleModel.query()
public async editBankRule(ruleId: number, editRuleDTO: IEditBankRuleDTO) {
const oldBankRule = await this.bankRuleModel()
.query()
.findById(ruleId)
.withGraphFetched('conditions')
.throwIfNotFound();
@@ -55,10 +55,12 @@ export class EditBankRuleService {
} as IBankRuleEventEditingPayload);
// Updates the given bank rule.
const bankRule = await this.bankRuleModel.query(trx).upsertGraphAndFetch({
...tranformDTO,
id: ruleId,
});
const bankRule = await this.bankRuleModel()
.query(trx)
.upsertGraphAndFetch({
...tranformDTO,
id: ruleId,
});
// Triggers `onBankRuleEdited` event.
await this.eventPublisher.emitAsync(events.bankRules.onEdited, {
oldBankRule,

View File

@@ -3,11 +3,13 @@ import { GetBankRuleTransformer } from './GetBankRuleTransformer';
import { TransformerInjectable } from '../../Transformer/TransformerInjectable.service';
import { BankRule } from '../models/BankRule';
import { GetBankRulesTransformer } from './GetBankRulesTransformer';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class GetBankRuleService {
constructor(
@Inject(BankRule.name) private bankRuleModel: typeof BankRule,
@Inject(BankRule.name)
private bankRuleModel: TenantModelProxy<typeof BankRule>,
private transformer: TransformerInjectable,
) {}
@@ -17,15 +19,12 @@ export class GetBankRuleService {
* @returns {Promise<any>}
*/
async getBankRule(ruleId: number): Promise<any> {
const bankRule = await this.bankRuleModel
const bankRule = await this.bankRuleModel()
.query()
.findById(ruleId)
.withGraphFetched('conditions')
.withGraphFetched('assignAccount');
return this.transformer.transform(
bankRule,
new GetBankRulesTransformer()
);
return this.transformer.transform(bankRule, new GetBankRulesTransformer());
}
}

View File

@@ -2,6 +2,7 @@ import { Inject, Injectable } from '@nestjs/common';
import { GetBankRulesTransformer } from './GetBankRulesTransformer';
import { BankRule } from '../models/BankRule';
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class GetBankRulesService {
@@ -9,7 +10,7 @@ export class GetBankRulesService {
private transformer: TransformerInjectable,
@Inject(BankRule.name)
private bankRuleModel: typeof BankRule,
private bankRuleModel: TenantModelProxy<typeof BankRule>,
) {}
/**
@@ -17,14 +18,11 @@ export class GetBankRulesService {
* @returns {Promise<any>}
*/
public async getBankRules(): Promise<any> {
const bankRule = await this.bankRuleModel
const bankRule = await this.bankRuleModel()
.query()
.withGraphFetched('conditions')
.withGraphFetched('assignAccount');
return this.transformer.transform(
bankRule,
new GetBankRulesTransformer()
);
return this.transformer.transform(bankRule, new GetBankRulesTransformer());
}
}

View File

@@ -14,6 +14,7 @@ import { ServiceError } from '@/modules/Items/ServiceError';
import { events } from '@/common/events/events';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { PLAID_CLIENT } from '@/modules/Plaid/Plaid.module';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class DisconnectBankAccountService {
@@ -21,9 +22,12 @@ export class DisconnectBankAccountService {
private eventPublisher: EventEmitter2,
private uow: UnitOfWork,
@Inject(Account.name) private accountModel: typeof Account,
@Inject(PlaidItem.name) private plaidItemModel: typeof PlaidItem,
@Inject(PLAID_CLIENT) private plaidClient: PlaidApi,
@Inject(Account.name)
private accountModel: TenantModelProxy<typeof Account>,
@Inject(PlaidItem.name)
private plaidItemModel: TenantModelProxy<typeof PlaidItem>,
) {}
/**
@@ -33,7 +37,7 @@ export class DisconnectBankAccountService {
*/
public async disconnectBankAccount(bankAccountId: number) {
// Retrieve the bank account or throw not found error.
const account = await this.accountModel
const account = await this.accountModel()
.query()
.findById(bankAccountId)
.whereIn('account_type', [ACCOUNT_TYPE.CASH, ACCOUNT_TYPE.BANK])
@@ -52,10 +56,13 @@ export class DisconnectBankAccountService {
} as IBankAccountDisconnectingEventPayload);
// Remove the Plaid item from the system.
await this.plaidItemModel.query(trx).findById(account.plaidItemId).delete();
await this.plaidItemModel()
.query(trx)
.findById(account.plaidItemId)
.delete();
// Remove the plaid item association to the bank account.
await this.accountModel.query(trx).findById(bankAccountId).patch({
await this.accountModel().query(trx).findById(bankAccountId).patch({
plaidAccountId: null,
plaidItemId: null,
isFeedsActive: false,

View File

@@ -5,23 +5,26 @@ import { ServiceError } from '@/modules/Items/ServiceError';
import { Account } from '@/modules/Accounts/models/Account.model';
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { PlaidItem } from '@/modules/BankingPlaid/models/PlaidItem';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class PauseBankAccountFeeds {
constructor(
@Inject(Account.name) private accountModel: typeof Account,
@Inject(PlaidItem.name) private plaidItemModel: typeof PlaidItem,
@Inject(Account.name)
private accountModel: TenantModelProxy<typeof Account>,
@Inject(PlaidItem.name)
private plaidItemModel: TenantModelProxy<typeof PlaidItem>,
private uow: UnitOfWork,
) {}
/**
* Pauses the bankfeed syncing of the given bank account.
* Pauses the bank feed syncing of the given bank account.
* @param {number} bankAccountId
* @returns {Promise<void>}
*/
public async pauseBankAccountFeeds(bankAccountId: number) {
const oldAccount = await this.accountModel
const oldAccount = await this.accountModel()
.query()
.findById(bankAccountId)
.withGraphFetched('plaidItem')
@@ -36,7 +39,7 @@ export class PauseBankAccountFeeds {
throw new ServiceError(ERRORS.BANK_ACCOUNT_FEEDS_ALREADY_PAUSED);
}
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
await this.plaidItemModel
await this.plaidItemModel()
.query(trx)
.findById(oldAccount.plaidItem.id)
.patch({

View File

@@ -4,12 +4,14 @@ import { Account } from '@/modules/Accounts/models/Account.model';
import { ServiceError } from '@/modules/Items/ServiceError';
import { PLAID_CLIENT } from '@/modules/Plaid/Plaid.module';
import { ERRORS } from '../types/BankAccounts.types';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class RefreshBankAccountService {
constructor(
@Inject(PLAID_CLIENT) private plaidClient: PlaidApi,
@Inject(Account.name) private readonly accountModel: typeof Account,
@Inject(Account.name)
private readonly accountModel: TenantModelProxy<typeof Account>,
) {}
/**
@@ -18,7 +20,7 @@ export class RefreshBankAccountService {
* @returns {Promise<void>}
*/
public async refreshBankAccount(bankAccountId: number) {
const bankAccount = await this.accountModel
const bankAccount = await this.accountModel()
.query()
.findById(bankAccountId)
.withGraphFetched('plaidItem')

View File

@@ -5,12 +5,16 @@ import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { PlaidItem } from '@/modules/BankingPlaid/models/PlaidItem';
import { Account } from '@/modules/Accounts/models/Account.model';
import { ServiceError } from '@/modules/Items/ServiceError';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class ResumeBankAccountFeedsService {
constructor(
@Inject(Account.name) private accountModel: typeof Account,
@Inject(PlaidItem.name) private plaidItemModel: typeof PlaidItem,
@Inject(Account.name)
private accountModel: TenantModelProxy<typeof Account>,
@Inject(PlaidItem.name)
private plaidItemModel: TenantModelProxy<typeof PlaidItem>,
private uow: UnitOfWork,
) {}
@@ -21,7 +25,7 @@ export class ResumeBankAccountFeedsService {
* @returns {Promise<void>}
*/
public async resumeBankAccountFeeds(bankAccountId: number) {
const oldAccount = await this.accountModel
const oldAccount = await this.accountModel()
.query()
.findById(bankAccountId)
.withGraphFetched('plaidItem');
@@ -35,7 +39,7 @@ export class ResumeBankAccountFeedsService {
throw new ServiceError(ERRORS.BANK_ACCOUNT_FEEDS_ALREADY_RESUMED);
}
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
await this.plaidItemModel
await this.plaidItemModel()
.query(trx)
.findById(oldAccount.plaidItem.id)
.patch({

View File

@@ -2,15 +2,18 @@ import { Inject, Injectable } from '@nestjs/common';
import { Account } from '@/modules/Accounts/models/Account.model';
import { UncategorizedBankTransaction } from '@/modules/BankingTransactions/models/UncategorizedBankTransaction';
import { BaseModel } from '@/models/Model';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class GetBankAccountSummary {
constructor(
@Inject(Account.name)
private readonly accountModel: typeof Account,
private readonly accountModel: TenantModelProxy<typeof Account>,
@Inject(UncategorizedBankTransaction.name)
private readonly uncategorizedBankTransactionModel: typeof UncategorizedBankTransaction,
private readonly uncategorizedBankTransactionModel: TenantModelProxy<
typeof UncategorizedBankTransaction
>,
) {}
/**
@@ -19,7 +22,7 @@ export class GetBankAccountSummary {
* @returns {Promise<IBankAccountSummary>}
*/
public async getBankAccountSummary(bankAccountId: number) {
const bankAccount = await this.accountModel
const bankAccount = await this.accountModel()
.query()
.findById(bankAccountId)
.throwIfNotFound();
@@ -41,60 +44,68 @@ export class GetBankAccountSummary {
// Retrieves the uncategorized transactions count of the given bank account.
const uncategorizedTranasctionsCount =
await this.uncategorizedBankTransactionModel.query().onBuild((q) => {
commonQuery(q);
await this.uncategorizedBankTransactionModel()
.query()
.onBuild((q) => {
commonQuery(q);
// Only the not matched bank transactions.
q.withGraphJoined('matchedBankTransactions');
q.whereNull('matchedBankTransactions.id');
// Only the not matched bank transactions.
q.withGraphJoined('matchedBankTransactions');
q.whereNull('matchedBankTransactions.id');
// Exclude the pending transactions.
q.modify('notPending');
// Exclude the pending transactions.
q.modify('notPending');
// Count the results.
q.count('uncategorized_cashflow_transactions.id as total');
q.first();
});
// Count the results.
q.count('uncategorized_cashflow_transactions.id as total');
q.first();
});
// Retrives the recognized transactions count.
const recognizedTransactionsCount =
await this.uncategorizedBankTransactionModel.query().onBuild((q) => {
commonQuery(q);
await this.uncategorizedBankTransactionModel()
.query()
.onBuild((q) => {
commonQuery(q);
q.withGraphJoined('recognizedTransaction');
q.whereNotNull('recognizedTransaction.id');
q.withGraphJoined('recognizedTransaction');
q.whereNotNull('recognizedTransaction.id');
// Exclude the pending transactions.
q.modify('notPending');
// Exclude the pending transactions.
q.modify('notPending');
// Count the results.
q.count('uncategorized_cashflow_transactions.id as total');
q.first();
});
// Count the results.
q.count('uncategorized_cashflow_transactions.id as total');
q.first();
});
// Retrieves excluded transactions count.
const excludedTransactionsCount =
await this.uncategorizedBankTransactionModel.query().onBuild((q) => {
q.where('accountId', bankAccountId);
q.modify('excluded');
await this.uncategorizedBankTransactionModel()
.query()
.onBuild((q) => {
q.where('accountId', bankAccountId);
q.modify('excluded');
// Exclude the pending transactions.
q.modify('notPending');
// Exclude the pending transactions.
q.modify('notPending');
// Count the results.
q.count('uncategorized_cashflow_transactions.id as total');
q.first();
});
// Count the results.
q.count('uncategorized_cashflow_transactions.id as total');
q.first();
});
// Retrieves the pending transactions count.
const pendingTransactionsCount =
await this.uncategorizedBankTransactionModel.query().onBuild((q) => {
q.where('accountId', bankAccountId);
q.modify('pending');
await this.uncategorizedBankTransactionModel()
.query()
.onBuild((q) => {
q.where('accountId', bankAccountId);
q.modify('pending');
// Count the results.
q.count('uncategorized_cashflow_transactions.id as total');
q.first();
});
// Count the results.
q.count('uncategorized_cashflow_transactions.id as total');
q.first();
});
const totalUncategorizedTransactions =
// @ts-ignore

View File

@@ -6,6 +6,7 @@ import { RevertRecognizedTransactionsService } from '@/modules/BankingTranasctio
import { UncategorizedBankTransaction } from '@/modules/BankingTransactions/models/UncategorizedBankTransaction';
import { DeleteBankRulesService } from '@/modules/BankRules/commands/DeleteBankRules.service';
import { BankRule } from '@/modules/BankRules/models/BankRule';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class DeleteUncategorizedTransactionsOnAccountDeleting {
@@ -13,9 +14,13 @@ export class DeleteUncategorizedTransactionsOnAccountDeleting {
private readonly deleteBankRules: DeleteBankRulesService,
private readonly revertRecognizedTransactins: RevertRecognizedTransactionsService,
@Inject(BankRule.name) private bankRuleModel: typeof BankRule,
@Inject(BankRule.name)
private bankRuleModel: TenantModelProxy<typeof BankRule>,
@Inject(UncategorizedBankTransaction.name)
private uncategorizedCashflowTransactionModel: typeof UncategorizedBankTransaction,
private uncategorizedCashflowTransactionModel: TenantModelProxy<
typeof UncategorizedBankTransaction
>,
) {}
/**
@@ -28,10 +33,9 @@ export class DeleteUncategorizedTransactionsOnAccountDeleting {
oldAccount,
trx,
}: IAccountEventDeletePayload) {
const foundAssociatedRules = await this.bankRuleModel.query(trx).where(
'applyIfAccountId',
oldAccount.id,
);
const foundAssociatedRules = await this.bankRuleModel()
.query(trx)
.where('applyIfAccountId', oldAccount.id);
const foundAssociatedRulesIds = foundAssociatedRules.map((rule) => rule.id);
// Revert the recognized transactions of the given bank rules.
@@ -41,15 +45,12 @@ export class DeleteUncategorizedTransactionsOnAccountDeleting {
trx,
);
// Delete the associated uncategorized transactions.
await this.uncategorizedCashflowTransactionModel
await this.uncategorizedCashflowTransactionModel()
.query(trx)
.where('accountId', oldAccount.id)
.delete();
// Delete the given bank rules.
await this.deleteBankRules.deleteBankRules(
foundAssociatedRulesIds,
trx,
);
await this.deleteBankRules.deleteBankRules(foundAssociatedRulesIds, trx);
}
}

View File

@@ -6,14 +6,20 @@ import { PlaidItem } from '@/modules/BankingPlaid/models/PlaidItem';
import { Account } from '@/modules/Accounts/models/Account.model';
import { PlaidApi } from 'plaid';
import { PLAID_CLIENT } from '@/modules/Plaid/Plaid.module';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class DisconnectPlaidItemOnAccountDeleted {
constructor(
@Inject(PLAID_CLIENT) private plaidClient: PlaidApi,
@Inject(PlaidItem.name) private plaidItemModel: typeof PlaidItem,
@Inject(Account.name) private accountModel: typeof Account,
@Inject(PlaidItem.name)
private plaidItemModel: TenantModelProxy<typeof PlaidItem>,
@Inject(Account.name)
private accountModel: TenantModelProxy<typeof Account>,
) {}
/**
* Deletes Plaid item from the system and Plaid once the account deleted.
* @param {IAccountEventDeletedPayload} payload
@@ -21,7 +27,6 @@ export class DisconnectPlaidItemOnAccountDeleted {
*/
@OnEvent(events.accounts.onDeleted)
public async handleDisconnectPlaidItemOnAccountDelete({
tenantId,
oldAccount,
trx,
}: IAccountEventDeletedPayload) {
@@ -29,11 +34,11 @@ export class DisconnectPlaidItemOnAccountDeleted {
if (!oldAccount.plaidItemId) return;
// Retrieves the Plaid item that associated to the deleted account.
const oldPlaidItem = await this.plaidItemModel
const oldPlaidItem = await this.plaidItemModel()
.query(trx)
.findOne('plaidItemId', oldAccount.plaidItemId);
// Unlink the Plaid item from all account before deleting it.
await this.accountModel
await this.accountModel()
.query(trx)
.where('plaidItemId', oldAccount.plaidItemId)
.patch({
@@ -41,7 +46,7 @@ export class DisconnectPlaidItemOnAccountDeleted {
plaidItemId: null,
});
// Remove the Plaid item from the system.
await this.plaidItemModel
await this.plaidItemModel()
.query(trx)
.findOne('plaidItemId', oldAccount.plaidItemId)
.delete();

View File

@@ -16,6 +16,7 @@ import { CreateBankTransactionService } from '../../BankingTransactions/commands
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { UncategorizedBankTransaction } from '@/modules/BankingTransactions/models/UncategorizedBankTransaction';
import { events } from '@/common/events/events';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class CategorizeCashflowTransaction {
@@ -26,7 +27,9 @@ export class CategorizeCashflowTransaction {
private readonly createBankTransaction: CreateBankTransactionService,
@Inject(UncategorizedBankTransaction.name)
private readonly uncategorizedBankTransactionModel: typeof UncategorizedBankTransaction,
private readonly uncategorizedBankTransactionModel: TenantModelProxy<
typeof UncategorizedBankTransaction
>,
) {}
/**
@@ -41,7 +44,8 @@ export class CategorizeCashflowTransaction {
// Retrieves the uncategorized transaction or throw an error.
const oldUncategorizedTransactions =
await this.uncategorizedBankTransactionModel.query()
await this.uncategorizedBankTransactionModel()
.query()
.whereIn('id', uncategorizedTransactionIds)
.throwIfNotFound();
@@ -81,7 +85,8 @@ export class CategorizeCashflowTransaction {
);
// Updates the uncategorized transaction as categorized.
await this.uncategorizedBankTransactionModel.query(trx)
await this.uncategorizedBankTransactionModel()
.query(trx)
.whereIn('id', uncategorizedTransactionIds)
.patch({
categorized: true,
@@ -90,10 +95,9 @@ export class CategorizeCashflowTransaction {
});
// Fetch the new updated uncategorized transactions.
const uncategorizedTransactions =
await this.uncategorizedBankTransactionModel.query(trx).whereIn(
'id',
uncategorizedTransactionIds,
);
await this.uncategorizedBankTransactionModel()
.query(trx)
.whereIn('id', uncategorizedTransactionIds);
// Triggers `onCashflowTransactionCategorized` event.
await this.eventPublisher.emitAsync(
events.cashflow.onTransactionCategorized,

View File

@@ -10,6 +10,7 @@ import {
ICashflowTransactionCategorizedPayload,
ICategorizeCashflowTransactioDTO,
} from '../types/BankingCategorize.types';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class CategorizeTransactionAsExpense {
@@ -19,7 +20,9 @@ export class CategorizeTransactionAsExpense {
private readonly createExpenseService: CreateExpense,
@Inject(BankTransaction.name)
private readonly bankTransactionModel: typeof BankTransaction,
private readonly bankTransactionModel: TenantModelProxy<
typeof BankTransaction
>,
) {}
/**
@@ -31,7 +34,7 @@ export class CategorizeTransactionAsExpense {
cashflowTransactionId: number,
transactionDTO: ICategorizeCashflowTransactioDTO,
) {
const transaction = await this.bankTransactionModel
const transaction = await this.bankTransactionModel()
.query()
.findById(cashflowTransactionId)
.throwIfNotFound();
@@ -53,7 +56,7 @@ export class CategorizeTransactionAsExpense {
});
// Updates the item on the storage and fetches the updated once.
const cashflowTransaction = await this.bankTransactionModel
const cashflowTransaction = await this.bankTransactionModel()
.query(trx)
.patchAndFetchById(cashflowTransactionId, {
categorizeRefType: 'Expense',

View File

@@ -9,6 +9,7 @@ import { EventEmitter2 } from '@nestjs/event-emitter';
import { UncategorizedBankTransaction } from '../../BankingTransactions/models/UncategorizedBankTransaction';
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { events } from '@/common/events/events';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class CreateUncategorizedTransactionService {
@@ -17,7 +18,9 @@ export class CreateUncategorizedTransactionService {
private readonly eventPublisher: EventEmitter2,
@Inject(UncategorizedBankTransaction.name)
private readonly uncategorizedBankTransaction: typeof UncategorizedBankTransaction,
private readonly uncategorizedBankTransaction: TenantModelProxy<
typeof UncategorizedBankTransaction
>,
) {}
/**
@@ -28,34 +31,32 @@ export class CreateUncategorizedTransactionService {
*/
public create(
createUncategorizedTransactionDTO: CreateUncategorizedTransactionDTO,
trx?: Knex.Transaction
trx?: Knex.Transaction,
) {
return this.uow.withTransaction(
async (trx: Knex.Transaction) => {
await this.eventPublisher.emitAsync(
events.cashflow.onTransactionUncategorizedCreating,
{
createUncategorizedTransactionDTO,
trx,
} as IUncategorizedTransactionCreatingEventPayload
);
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
await this.eventPublisher.emitAsync(
events.cashflow.onTransactionUncategorizedCreating,
{
createUncategorizedTransactionDTO,
trx,
} as IUncategorizedTransactionCreatingEventPayload,
);
const uncategorizedTransaction =
await this.uncategorizedBankTransaction.query(trx).insertAndFetch({
...createUncategorizedTransactionDTO,
});
const uncategorizedTransaction = await this.uncategorizedBankTransaction
.query(trx)
.insertAndFetch({
...createUncategorizedTransactionDTO,
});
await this.eventPublisher.emitAsync(
events.cashflow.onTransactionUncategorizedCreated,
{
uncategorizedTransaction,
createUncategorizedTransactionDTO,
trx,
} as IUncategorizedTransactionCreatedEventPayload
);
return uncategorizedTransaction;
},
trx
);
await this.eventPublisher.emitAsync(
events.cashflow.onTransactionUncategorizedCreated,
{
uncategorizedTransaction,
createUncategorizedTransactionDTO,
trx,
} as IUncategorizedTransactionCreatedEventPayload,
);
return uncategorizedTransaction;
}, trx);
}
}

View File

@@ -9,6 +9,7 @@ import { EventEmitter2 } from '@nestjs/event-emitter';
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { events } from '@/common/events/events';
import { UncategorizedBankTransaction } from '../../BankingTransactions/models/UncategorizedBankTransaction';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class UncategorizeCashflowTransactionService {
@@ -17,7 +18,9 @@ export class UncategorizeCashflowTransactionService {
private readonly uow: UnitOfWork,
@Inject(UncategorizedBankTransaction.name)
private readonly uncategorizedBankTransactionModel: typeof UncategorizedBankTransaction,
private readonly uncategorizedBankTransactionModel: TenantModelProxy<
typeof UncategorizedBankTransaction
>,
) {}
/**
@@ -29,7 +32,7 @@ export class UncategorizeCashflowTransactionService {
uncategorizedTransactionId: number,
): Promise<Array<number>> {
const oldMainUncategorizedTransaction =
await this.uncategorizedBankTransactionModel
await this.uncategorizedBankTransactionModel()
.query()
.findById(uncategorizedTransactionId)
.throwIfNotFound();
@@ -37,7 +40,7 @@ export class UncategorizeCashflowTransactionService {
validateTransactionShouldBeCategorized(oldMainUncategorizedTransaction);
const associatedUncategorizedTransactions =
await this.uncategorizedBankTransactionModel
await this.uncategorizedBankTransactionModel()
.query()
.where(
'categorizeRefId',
@@ -69,7 +72,7 @@ export class UncategorizeCashflowTransactionService {
} as ICashflowTransactionUncategorizingPayload,
);
// Removes the ref relation with the related transaction.
await this.uncategorizedBankTransactionModel
await this.uncategorizedBankTransactionModel()
.query(trx)
.whereIn('id', oldUncategoirzedTransactionsIds)
.patch({
@@ -78,7 +81,7 @@ export class UncategorizeCashflowTransactionService {
categorizeRefType: null,
});
const uncategorizedTransactions =
await this.uncategorizedBankTransactionModel
await this.uncategorizedBankTransactionModel()
.query(trx)
.whereIn('id', oldUncategoirzedTransactionsIds);
// Triggers `onTransactionUncategorized` event.

View File

@@ -8,6 +8,7 @@ import { ImportableContext } from '../../Import/interfaces';
import { BankTransactionsSampleData } from '../../BankingTransactions/constants';
import { Account } from '@/modules/Accounts/models/Account.model';
import { CreateUncategorizedTransactionDTO } from '../types/BankingCategorize.types';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class UncategorizedTransactionsImportable extends Importable {
@@ -15,14 +16,14 @@ export class UncategorizedTransactionsImportable extends Importable {
private readonly createUncategorizedTransaction: CreateUncategorizedTransactionService,
@Inject(Account.name)
private readonly accountModel: typeof Account,
private readonly accountModel: TenantModelProxy<typeof Account>,
) {
super();
}
/**
* Passing the sheet DTO to create uncategorized transaction.
* @param {CreateUncategorizedTransactionDTO,} createDTO
* @param {CreateUncategorizedTransactionDTO,} createDTO
* @param {Knex.Transaction} trx
*/
public async importable(
@@ -77,7 +78,7 @@ export class UncategorizedTransactionsImportable extends Importable {
*/
public async validateParams(params: Record<string, any>): Promise<void> {
if (params.accountId) {
await this.accountModel
await this.accountModel()
.query()
.findById(params.accountId)
.throwIfNotFound({});

View File

@@ -28,12 +28,12 @@ const models = [RegisterTenancyModel(MatchedBankTransaction)];
@Module({
controllers: [BankingMatchingController],
imports: [
...models,
BillPaymentsModule,
BankingTransactionsModule,
PaymentsReceivedModule,
],
providers: [
...models,
ValidateTransactionMatched,
MatchBankTransactions,
MatchTransactionsTypes,

View File

@@ -20,6 +20,7 @@ import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { ServiceError } from '@/modules/Items/ServiceError';
import { UncategorizedBankTransaction } from '@/modules/BankingTransactions/models/UncategorizedBankTransaction';
import { events } from '@/common/events/events';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class MatchBankTransactions {
@@ -29,7 +30,9 @@ export class MatchBankTransactions {
private readonly matchedBankTransactions: MatchTransactionsTypes,
@Inject(UncategorizedBankTransaction.name)
private readonly uncategorizedBankTransactionModel: typeof UncategorizedBankTransaction,
private readonly uncategorizedBankTransactionModel: TenantModelProxy<
typeof UncategorizedBankTransaction
>,
) {}
/**
@@ -46,7 +49,7 @@ export class MatchBankTransactions {
// Validates the uncategorized transaction existance.
const uncategorizedTransactions =
await this.uncategorizedBankTransactionModel
await this.uncategorizedBankTransactionModel()
.query()
.whereIn('id', uncategorizedTransactionIds)
.withGraphFetched('matchedBankTransactions')

View File

@@ -4,6 +4,7 @@ import { EventEmitter2 } from '@nestjs/event-emitter';
import { UnitOfWork } from '../../Tenancy/TenancyDB/UnitOfWork.service';
import { events } from '@/common/events/events';
import { MatchedBankTransaction } from '../models/MatchedBankTransaction';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class UnmatchMatchedBankTransaction {
@@ -12,7 +13,9 @@ export class UnmatchMatchedBankTransaction {
private readonly eventPublisher: EventEmitter2,
@Inject(MatchedBankTransaction.name)
private readonly matchedBankTransactionModel: typeof MatchedBankTransaction,
private readonly matchedBankTransactionModel: TenantModelProxy<
typeof MatchedBankTransaction
>,
) {}
/**
@@ -29,7 +32,7 @@ export class UnmatchMatchedBankTransaction {
trx,
} as IBankTransactionUnmatchingEventPayload);
await this.matchedBankTransactionModel
await this.matchedBankTransactionModel()
.query(trx)
.where('uncategorizedTransactionId', uncategorizedTransactionId)
.delete();

View File

@@ -3,12 +3,15 @@ import { ERRORS } from '../types';
import { Inject, Injectable } from '@nestjs/common';
import { ServiceError } from '@/modules/Items/ServiceError';
import { MatchedBankTransaction } from '../models/MatchedBankTransaction';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class ValidateTransactionMatched {
constructor(
@Inject(MatchedBankTransaction.name)
private readonly matchedBankTransactionModel: typeof MatchedBankTransaction,
private readonly matchedBankTransactionModel: TenantModelProxy<
typeof MatchedBankTransaction
>,
) {}
/**
@@ -20,13 +23,15 @@ export class ValidateTransactionMatched {
public async validateTransactionNoMatchLinking(
referenceType: string,
referenceId: number,
trx?: Knex.Transaction
trx?: Knex.Transaction,
) {
const foundMatchedTransaction =
await this.matchedBankTransactionModel.query(trx).findOne({
const foundMatchedTransaction = await this.matchedBankTransactionModel()
.query(trx)
.findOne({
referenceType,
referenceId,
});
if (foundMatchedTransaction) {
throw new ServiceError(ERRORS.CANNOT_DELETE_TRANSACTION_MATCHED);
}

View File

@@ -8,15 +8,18 @@ import { Account } from '@/modules/Accounts/models/Account.model';
import { Inject, Injectable } from '@nestjs/common';
import { UncategorizedBankTransaction } from '@/modules/BankingTransactions/models/UncategorizedBankTransaction';
import { events } from '@/common/events/events';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class DecrementUncategorizedTransactionOnMatchingSubscriber {
constructor(
@Inject(Account.name)
private readonly accountModel: typeof Account,
private readonly accountModel: TenantModelProxy<typeof Account>,
@Inject(UncategorizedBankTransaction.name)
private readonly uncategorizedBankTransactionModel: typeof UncategorizedBankTransaction,
private readonly uncategorizedBankTransactionModel: TenantModelProxy<
typeof UncategorizedBankTransaction
>,
) {}
/**
@@ -29,14 +32,14 @@ export class DecrementUncategorizedTransactionOnMatchingSubscriber {
trx,
}: IBankTransactionMatchedEventPayload) {
const uncategorizedTransactions =
await this.uncategorizedBankTransactionModel.query().whereIn(
'id',
uncategorizedTransactionIds
);
await this.uncategorizedBankTransactionModel()
.query()
.whereIn('id', uncategorizedTransactionIds);
await PromisePool.withConcurrency(1)
.for(uncategorizedTransactions)
.process(async (transaction) => {
await this.accountModel
await this.accountModel()
.query(trx)
.findById(transaction.accountId)
.decrement('uncategorizedTransactions', 1);
@@ -52,11 +55,11 @@ export class DecrementUncategorizedTransactionOnMatchingSubscriber {
uncategorizedTransactionId,
trx,
}: IBankTransactionUnmatchedEventPayload) {
const transaction =
await this.uncategorizedBankTransactionModel.query().findById(
uncategorizedTransactionId
);
await this.accountModel
const transaction = await this.uncategorizedBankTransactionModel()
.query()
.findById(uncategorizedTransactionId);
await this.accountModel()
.query(trx)
.findById(transaction.accountId)
.increment('uncategorizedTransactions', 1);

View File

@@ -3,7 +3,10 @@ import * as moment from 'moment';
import { first, sumBy } from 'lodash';
import { PromisePool } from '@supercharge/promise-pool';
import { Inject, Injectable } from '@nestjs/common';
import { GetMatchedTransactionsFilter, MatchedTransactionsPOJO } from '../types';
import {
GetMatchedTransactionsFilter,
MatchedTransactionsPOJO,
} from '../types';
import { GetMatchedTransactionsByExpenses } from './GetMatchedTransactionsByExpenses';
import { GetMatchedTransactionsByBills } from './GetMatchedTransactionsByBills.service';
import { GetMatchedTransactionsByManualJournals } from './GetMatchedTransactionsByManualJournals.service';
@@ -11,6 +14,7 @@ import { GetMatchedTransactionsByCashflow } from './GetMatchedTransactionsByCash
import { GetMatchedTransactionsByInvoices } from './GetMatchedTransactionsByInvoices.service';
import { UncategorizedBankTransaction } from '@/modules/BankingTransactions/models/UncategorizedBankTransaction';
import { sortClosestMatchTransactions } from '../_utils';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class GetMatchedTransactions {
@@ -22,7 +26,9 @@ export class GetMatchedTransactions {
private readonly getMatchedCashflowService: GetMatchedTransactionsByCashflow,
@Inject(UncategorizedBankTransaction.name)
private readonly uncategorizedBankTransactionModel: typeof UncategorizedBankTransaction,
private readonly uncategorizedBankTransactionModel: TenantModelProxy<
typeof UncategorizedBankTransaction
>,
) {}
/**
@@ -46,10 +52,11 @@ export class GetMatchedTransactions {
*/
public async getMatchedTransactions(
uncategorizedTransactionIds: Array<number>,
filter: GetMatchedTransactionsFilter
filter: GetMatchedTransactionsFilter,
): Promise<MatchedTransactionsPOJO> {
const uncategorizedTransactions =
await this.uncategorizedBankTransactionModel.query()
await this.uncategorizedBankTransactionModel()
.query()
.whereIn('id', uncategorizedTransactionIds)
.throwIfNotFound();
@@ -66,7 +73,7 @@ export class GetMatchedTransactions {
});
const { perfectMatches, possibleMatches } = this.groupMatchedResults(
uncategorizedTransactions,
matchedTransactions
matchedTransactions,
);
return {
perfectMatches,
@@ -84,7 +91,7 @@ export class GetMatchedTransactions {
*/
private groupMatchedResults(
uncategorizedTransactions: Array<any>,
matchedTransactions
matchedTransactions,
): MatchedTransactionsPOJO {
const results = R.compose(R.flatten)(matchedTransactions?.results);
@@ -97,7 +104,7 @@ export class GetMatchedTransactions {
const perfectMatches = R.filter(
(match) =>
match.amount === amount && moment(match.date).isSame(date, 'day'),
closestResullts
closestResullts,
);
const possibleMatches = R.difference(closestResullts, perfectMatches);
const totalPending = sumBy(uncategorizedTransactions, 'amount');

View File

@@ -13,6 +13,7 @@ import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectab
import { IBillPaymentDTO } from '@/modules/BillPayments/types/BillPayments.types';
import { Bill } from '@/modules/Bills/models/Bill';
import { UncategorizedBankTransaction } from '@/modules/BankingTransactions/models/UncategorizedBankTransaction';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class GetMatchedTransactionsByBills extends GetMatchedTransactionsByType {
@@ -21,10 +22,12 @@ export class GetMatchedTransactionsByBills extends GetMatchedTransactionsByType
private readonly transformer: TransformerInjectable,
@Inject(Bill.name)
private readonly billModel: typeof Bill,
private readonly billModel: TenantModelProxy<typeof Bill>,
@Inject(UncategorizedBankTransaction.name)
private readonly uncategorizedBankTransactionModel: typeof UncategorizedBankTransaction,
private readonly uncategorizedBankTransactionModel: TenantModelProxy<
typeof UncategorizedBankTransaction
>,
) {
super();
}
@@ -34,23 +37,23 @@ export class GetMatchedTransactionsByBills extends GetMatchedTransactionsByType
* @param {number} tenantId -
* @param {GetMatchedTransactionsFilter} filter -
*/
public async getMatchedTransactions(
filter: GetMatchedTransactionsFilter,
) {
public async getMatchedTransactions(filter: GetMatchedTransactionsFilter) {
// Retrieves the bill matches.
const bills = await Bill.query().onBuild((q) => {
q.withGraphJoined('matchedBankTransaction');
q.whereNull('matchedBankTransaction.id');
q.modify('published');
const bills = await this.billModel()
.query()
.onBuild((q) => {
q.withGraphJoined('matchedBankTransaction');
q.whereNull('matchedBankTransaction.id');
q.modify('published');
if (filter.fromDate) {
q.where('billDate', '>=', filter.fromDate);
}
if (filter.toDate) {
q.where('billDate', '<=', filter.toDate);
}
q.orderBy('billDate', 'DESC');
});
if (filter.fromDate) {
q.where('billDate', '>=', filter.fromDate);
}
if (filter.toDate) {
q.where('billDate', '<=', filter.toDate);
}
q.orderBy('billDate', 'DESC');
});
return this.transformer.transform(
bills,
@@ -67,7 +70,7 @@ export class GetMatchedTransactionsByBills extends GetMatchedTransactionsByType
public async getMatchedTransaction(
transactionId: number,
): Promise<MatchedTransactionPOJO> {
const bill = await this.billModel
const bill = await this.billModel()
.query()
.findById(transactionId)
.throwIfNotFound();
@@ -97,12 +100,12 @@ export class GetMatchedTransactionsByBills extends GetMatchedTransactionsByType
);
const uncategorizedTransactionId = first(uncategorizedTransactionIds);
const uncategorizedTransaction =
await this.uncategorizedBankTransactionModel
await this.uncategorizedBankTransactionModel()
.query(trx)
.findById(uncategorizedTransactionId)
.throwIfNotFound();
const bill = await this.billModel
const bill = await this.billModel()
.query(trx)
.findById(matchTransactionDTO.referenceId)
.throwIfNotFound();

View File

@@ -4,6 +4,7 @@ import { GetMatchedTransactionsFilter } from '../types';
import { BankTransaction } from '@/modules/BankingTransactions/models/BankTransaction';
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
import { Inject, Injectable } from '@nestjs/common';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class GetMatchedTransactionsByCashflow extends GetMatchedTransactionsByType {
@@ -11,7 +12,9 @@ export class GetMatchedTransactionsByCashflow extends GetMatchedTransactionsByTy
private readonly transformer: TransformerInjectable,
@Inject(BankTransaction.name)
private readonly bankTransactionModel: typeof BankTransaction,
private readonly bankTransactionModel: TenantModelProxy<
typeof BankTransaction
>,
) {
super();
}
@@ -25,7 +28,7 @@ export class GetMatchedTransactionsByCashflow extends GetMatchedTransactionsByTy
async getMatchedTransactions(
filter: Omit<GetMatchedTransactionsFilter, 'transactionType'>,
) {
const transactions = await this.bankTransactionModel
const transactions = await this.bankTransactionModel()
.query()
.onBuild((q) => {
// Not matched to bank transaction.
@@ -60,7 +63,7 @@ export class GetMatchedTransactionsByCashflow extends GetMatchedTransactionsByTy
* @returns
*/
async getMatchedTransaction(transactionId: number) {
const transactions = await this.bankTransactionModel
const transactions = await this.bankTransactionModel()
.query()
.findById(transactionId)
.withGraphJoined('matchedBankTransaction')

View File

@@ -4,6 +4,7 @@ import { GetMatchedTransactionsByType } from './GetMatchedTransactionsByType';
import { GetMatchedTransactionExpensesTransformer } from './GetMatchedTransactionExpensesTransformer';
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
import { Expense } from '@/modules/Expenses/models/Expense.model';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class GetMatchedTransactionsByExpenses extends GetMatchedTransactionsByType {
@@ -11,7 +12,7 @@ export class GetMatchedTransactionsByExpenses extends GetMatchedTransactionsByTy
protected readonly transformer: TransformerInjectable,
@Inject(Expense.name)
protected readonly expenseModel: typeof Expense,
protected readonly expenseModel: TenantModelProxy<typeof Expense>,
) {
super();
}
@@ -24,28 +25,30 @@ export class GetMatchedTransactionsByExpenses extends GetMatchedTransactionsByTy
*/
async getMatchedTransactions(filter: GetMatchedTransactionsFilter) {
// Retrieve the expense matches.
const expenses = await this.expenseModel.query().onBuild((query) => {
// Filter out the not matched to bank transactions.
query.withGraphJoined('matchedBankTransaction');
query.whereNull('matchedBankTransaction.id');
const expenses = await this.expenseModel()
.query()
.onBuild((query) => {
// Filter out the not matched to bank transactions.
query.withGraphJoined('matchedBankTransaction');
query.whereNull('matchedBankTransaction.id');
// Filter the published onyl
query.modify('filterByPublished');
// Filter the published onyl
query.modify('filterByPublished');
if (filter.fromDate) {
query.where('paymentDate', '>=', filter.fromDate);
}
if (filter.toDate) {
query.where('paymentDate', '<=', filter.toDate);
}
if (filter.minAmount) {
query.where('totalAmount', '>=', filter.minAmount);
}
if (filter.maxAmount) {
query.where('totalAmount', '<=', filter.maxAmount);
}
query.orderBy('paymentDate', 'DESC');
});
if (filter.fromDate) {
query.where('paymentDate', '>=', filter.fromDate);
}
if (filter.toDate) {
query.where('paymentDate', '<=', filter.toDate);
}
if (filter.minAmount) {
query.where('totalAmount', '>=', filter.minAmount);
}
if (filter.maxAmount) {
query.where('totalAmount', '<=', filter.maxAmount);
}
query.orderBy('paymentDate', 'DESC');
});
return this.transformer.transform(
expenses,
new GetMatchedTransactionExpensesTransformer(),
@@ -61,7 +64,7 @@ export class GetMatchedTransactionsByExpenses extends GetMatchedTransactionsByTy
public async getMatchedTransaction(
transactionId: number,
): Promise<MatchedTransactionPOJO> {
const expense = await this.expenseModel
const expense = await this.expenseModel()
.query()
.findById(transactionId)
.throwIfNotFound();

View File

@@ -14,6 +14,7 @@ import { SaleInvoice } from '@/modules/SaleInvoices/models/SaleInvoice';
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
import { UncategorizedBankTransaction } from '@/modules/BankingTransactions/models/UncategorizedBankTransaction';
import { IPaymentReceivedCreateDTO } from '@/modules/PaymentReceived/types/PaymentReceived.types';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class GetMatchedTransactionsByInvoices extends GetMatchedTransactionsByType {
@@ -22,10 +23,12 @@ export class GetMatchedTransactionsByInvoices extends GetMatchedTransactionsByTy
private readonly createPaymentReceivedService: CreatePaymentReceivedService,
@Inject(SaleInvoice.name)
private readonly saleInvoiceModel: typeof SaleInvoice,
private readonly saleInvoiceModel: TenantModelProxy<typeof SaleInvoice>,
@Inject(UncategorizedBankTransaction.name)
private readonly uncategorizedBankTransactionModel: typeof UncategorizedBankTransaction,
private readonly uncategorizedBankTransactionModel: TenantModelProxy<
typeof UncategorizedBankTransaction
>,
) {
super();
}
@@ -36,27 +39,29 @@ export class GetMatchedTransactionsByInvoices extends GetMatchedTransactionsByTy
* @returns {Promise<MatchedTransactionsPOJO>}
*/
public async getMatchedTransactions(
filter: GetMatchedTransactionsFilter
filter: GetMatchedTransactionsFilter,
): Promise<MatchedTransactionsPOJO> {
// Retrieve the invoices that not matched, unpaid.
const invoices = await this.saleInvoiceModel.query().onBuild((q) => {
q.withGraphJoined('matchedBankTransaction');
q.whereNull('matchedBankTransaction.id');
q.modify('unpaid');
q.modify('published');
const invoices = await this.saleInvoiceModel()
.query()
.onBuild((q) => {
q.withGraphJoined('matchedBankTransaction');
q.whereNull('matchedBankTransaction.id');
q.modify('unpaid');
q.modify('published');
if (filter.fromDate) {
q.where('invoiceDate', '>=', filter.fromDate);
}
if (filter.toDate) {
q.where('invoiceDate', '<=', filter.toDate);
}
q.orderBy('invoiceDate', 'DESC');
});
if (filter.fromDate) {
q.where('invoiceDate', '>=', filter.fromDate);
}
if (filter.toDate) {
q.where('invoiceDate', '<=', filter.toDate);
}
q.orderBy('invoiceDate', 'DESC');
});
return this.transformer.transform(
invoices,
new GetMatchedTransactionInvoicesTransformer()
new GetMatchedTransactionInvoicesTransformer(),
);
}
@@ -67,13 +72,15 @@ export class GetMatchedTransactionsByInvoices extends GetMatchedTransactionsByTy
* @returns {Promise<MatchedTransactionPOJO>}
*/
public async getMatchedTransaction(
transactionId: number
transactionId: number,
): Promise<MatchedTransactionPOJO> {
const invoice = await this.saleInvoiceModel.query().findById(transactionId);
const invoice = await this.saleInvoiceModel()
.query()
.findById(transactionId);
return this.transformer.transform(
invoice,
new GetMatchedTransactionInvoicesTransformer()
new GetMatchedTransactionInvoicesTransformer(),
);
}
@@ -87,16 +94,17 @@ export class GetMatchedTransactionsByInvoices extends GetMatchedTransactionsByTy
public async createMatchedTransaction(
uncategorizedTransactionIds: Array<number>,
matchTransactionDTO: IMatchTransactionDTO,
trx?: Knex.Transaction
trx?: Knex.Transaction,
) {
await super.createMatchedTransaction(
uncategorizedTransactionIds,
matchTransactionDTO,
trx
trx,
);
const uncategorizedTransactionId = first(uncategorizedTransactionIds);
const uncategorizedTransaction =
await this.uncategorizedBankTransactionModel.query(trx)
await this.uncategorizedBankTransactionModel()
.query(trx)
.findById(uncategorizedTransactionId)
.throwIfNotFound();
@@ -119,14 +127,19 @@ export class GetMatchedTransactionsByInvoices extends GetMatchedTransactionsByTy
branchId: invoice.branchId,
};
// Create a payment received associated to the matched invoice.
const paymentReceived = await this.createPaymentReceivedService.createPaymentReceived(
createPaymentReceivedDTO,
trx
);
const paymentReceived =
await this.createPaymentReceivedService.createPaymentReceived(
createPaymentReceivedDTO,
trx,
);
// Link the create payment received with matched invoice transaction.
await super.createMatchedTransaction(uncategorizedTransactionIds, {
referenceType: 'PaymentReceive',
referenceId: paymentReceived.id,
}, trx)
await super.createMatchedTransaction(
uncategorizedTransactionIds,
{
referenceType: 'PaymentReceive',
referenceId: paymentReceived.id,
},
trx,
);
}
}

View File

@@ -4,6 +4,7 @@ import { GetMatchedTransactionsByType } from './GetMatchedTransactionsByType';
import { GetMatchedTransactionsFilter } from '../types';
import { ManualJournal } from '@/modules/ManualJournals/models/ManualJournal';
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class GetMatchedTransactionsByManualJournals extends GetMatchedTransactionsByType {
@@ -11,14 +12,13 @@ export class GetMatchedTransactionsByManualJournals extends GetMatchedTransactio
private readonly transformer: TransformerInjectable,
@Inject(ManualJournal.name)
private readonly manualJournalModel: typeof ManualJournal,
private readonly manualJournalModel: TenantModelProxy<typeof ManualJournal>,
) {
super();
}
/**
* Retrieve the matched transactions of manual journals.
* @param {number} tenantId
* @param {GetMatchedTransactionsFilter} filter
* @returns
*/
@@ -28,27 +28,29 @@ export class GetMatchedTransactionsByManualJournals extends GetMatchedTransactio
// @todo: get the account id from the filter
const accountId = 1000;
const manualJournals = await this.manualJournalModel.query().onBuild((query) => {
query.withGraphJoined('matchedBankTransaction');
query.whereNull('matchedBankTransaction.id');
const manualJournals = await this.manualJournalModel()
.query()
.onBuild((query) => {
query.withGraphJoined('matchedBankTransaction');
query.whereNull('matchedBankTransaction.id');
query.withGraphJoined('entries');
query.where('entries.accountId', accountId);
query.modify('filterByPublished');
query.withGraphJoined('entries');
query.where('entries.accountId', accountId);
query.modify('filterByPublished');
if (filter.fromDate) {
query.where('date', '>=', filter.fromDate);
}
if (filter.toDate) {
query.where('date', '<=', filter.toDate);
}
if (filter.minAmount) {
query.where('amount', '>=', filter.minAmount);
}
if (filter.maxAmount) {
query.where('amount', '<=', filter.maxAmount);
}
});
if (filter.fromDate) {
query.where('date', '>=', filter.fromDate);
}
if (filter.toDate) {
query.where('date', '<=', filter.toDate);
}
if (filter.minAmount) {
query.where('amount', '>=', filter.minAmount);
}
if (filter.maxAmount) {
query.where('amount', '<=', filter.maxAmount);
}
});
return this.transformer.transform(
manualJournals,
new GetMatchedTransactionManualJournalsTransformer(),
@@ -62,7 +64,8 @@ export class GetMatchedTransactionsByManualJournals extends GetMatchedTransactio
* @returns
*/
public async getMatchedTransaction(transactionId: number) {
const manualJournal = await this.manualJournalModel.query()
const manualJournal = await this.manualJournalModel()
.query()
.findById(transactionId)
.whereNotExists(ManualJournal.relatedQuery('matchedBankTransaction'))
.throwIfNotFound();

View File

@@ -8,10 +8,13 @@ import {
import PromisePool from '@supercharge/promise-pool';
import { MatchedBankTransaction } from '../models/MatchedBankTransaction';
import { Inject } from '@nestjs/common';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
export abstract class GetMatchedTransactionsByType {
@Inject(MatchedBankTransaction.name)
private readonly matchedBankTransactionModel: typeof MatchedBankTransaction;
private readonly matchedBankTransactionModel: TenantModelProxy<
typeof MatchedBankTransaction
>;
/**
* Retrieves the matched transactions.
@@ -20,10 +23,10 @@ export abstract class GetMatchedTransactionsByType {
* @returns {Promise<MatchedTransactionsPOJO>}
*/
public async getMatchedTransactions(
filter: GetMatchedTransactionsFilter
filter: GetMatchedTransactionsFilter,
): Promise<MatchedTransactionsPOJO> {
throw new Error(
'The `getMatchedTransactions` method is not defined for the transaction type.'
'The `getMatchedTransactions` method is not defined for the transaction type.',
);
}
@@ -34,10 +37,10 @@ export abstract class GetMatchedTransactionsByType {
* @returns {Promise<MatchedTransactionPOJO>}
*/
public async getMatchedTransaction(
transactionId: number
transactionId: number,
): Promise<MatchedTransactionPOJO> {
throw new Error(
'The `getMatchedTransaction` method is not defined for the transaction type.'
'The `getMatchedTransaction` method is not defined for the transaction type.',
);
}
@@ -51,12 +54,12 @@ export abstract class GetMatchedTransactionsByType {
public async createMatchedTransaction(
uncategorizedTransactionIds: Array<number>,
matchTransactionDTO: IMatchTransactionDTO,
trx?: Knex.Transaction
trx?: Knex.Transaction,
) {
await PromisePool.withConcurrency(2)
.for(uncategorizedTransactionIds)
.process(async (uncategorizedTransactionId) => {
await this.matchedBankTransactionModel.query(trx).insert({
await this.matchedBankTransactionModel().query(trx).insert({
uncategorizedTransactionId,
referenceType: matchTransactionDTO.referenceType,
referenceId: matchTransactionDTO.referenceId,

View File

@@ -16,10 +16,7 @@ import { TenancyContext } from '../Tenancy/TenancyContext.service';
import { InjectSystemModel } from '../System/SystemModels/SystemModels.module';
import { SystemPlaidItem } from './models/SystemPlaidItem';
const models = [
RegisterTenancyModel(PlaidItem),
InjectSystemModel(SystemPlaidItem),
];
const models = [RegisterTenancyModel(PlaidItem)];
@Module({
imports: [
@@ -27,9 +24,10 @@ const models = [
AccountsModule,
BankingCategorizeModule,
BankingTransactionsModule,
...models,
],
providers: [
...models,
InjectSystemModel(SystemPlaidItem),
PlaidItemService,
PlaidUpdateTransactions,
PlaidSyncDb,

View File

@@ -6,7 +6,11 @@ import { EventEmitter2 } from '@nestjs/event-emitter';
import { events } from '@/common/events/events';
import { SystemPlaidItem } from '../models/SystemPlaidItem';
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
import { IPlaidItemCreatedEventPayload, PlaidItemDTO } from '../types/BankingPlaid.types';
import {
IPlaidItemCreatedEventPayload,
PlaidItemDTO,
} from '../types/BankingPlaid.types';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class PlaidItemService {
@@ -15,10 +19,12 @@ export class PlaidItemService {
private readonly tenancyContext: TenancyContext,
@Inject(SystemPlaidItem.name)
private readonly systemPlaidItemModel: typeof SystemPlaidItem,
private readonly systemPlaidItemModel: TenantModelProxy<
typeof SystemPlaidItem
>,
@Inject(PlaidItem.name)
private readonly plaidItemModel: typeof PlaidItem,
private readonly plaidItemModel: TenantModelProxy<typeof PlaidItem>,
@Inject(PLAID_CLIENT)
private readonly plaidClient: PlaidApi,
@@ -44,14 +50,14 @@ export class PlaidItemService {
const plaidItemId = response.data.item_id;
// Store the Plaid item metadata on tenant scope.
const plaidItem = await this.plaidItemModel.query().insertAndFetch({
const plaidItem = await this.plaidItemModel().query().insertAndFetch({
tenantId,
plaidAccessToken,
plaidItemId,
plaidInstitutionId: institutionId,
});
// Stores the Plaid item id on system scope.
await this.systemPlaidItemModel.query().insert({ tenantId, plaidItemId });
await this.systemPlaidItemModel().query().insert({ tenantId, plaidItemId });
// Triggers `onPlaidItemCreated` event.
await this.eventEmitter.emitAsync(events.plaid.onItemCreated, {

View File

@@ -24,6 +24,7 @@ import { IPlaidTransactionsSyncedEventPayload } from '../types/BankingPlaid.type
import { UncategorizedBankTransaction } from '../../BankingTransactions/models/UncategorizedBankTransaction';
import { Inject, Injectable } from '@nestjs/common';
import { CreateUncategorizedTransactionService } from '@/modules/BankingCategorize/commands/CreateUncategorizedTransaction.service';
import { TenantModelProxy } from '../../System/models/TenantBaseModel';
const CONCURRENCY_ASYNC = 10;
@@ -36,13 +37,15 @@ export class PlaidSyncDb {
private readonly eventPublisher: EventEmitter2,
@Inject(Account.name)
private readonly accountModel: typeof Account,
private readonly accountModel: TenantModelProxy<typeof Account>,
@Inject(PlaidItemModel.name)
private readonly plaidItemModel: typeof PlaidItemModel,
private readonly plaidItemModel: TenantModelProxy<typeof PlaidItemModel>,
@Inject(UncategorizedBankTransaction.name)
private readonly uncategorizedBankTransactionModel: typeof UncategorizedBankTransaction,
private readonly uncategorizedBankTransactionModel: TenantModelProxy<
typeof UncategorizedBankTransaction
>,
) {}
/**
@@ -55,7 +58,7 @@ export class PlaidSyncDb {
createBankAccountDTO: IAccountCreateDTO,
trx?: Knex.Transaction,
) {
const plaidAccount = await this.accountModel
const plaidAccount = await this.accountModel()
.query(trx)
.findOne('plaidAccountId', createBankAccountDTO.plaidAccountId);
// Can't continue if the Plaid account is already created.
@@ -102,7 +105,7 @@ export class PlaidSyncDb {
trx?: Knex.Transaction,
): Promise<void> {
const batch = uniqid();
const cashflowAccount = await this.accountModel
const cashflowAccount = await this.accountModel()
.query(trx)
.findOne({ plaidAccountId })
.throwIfNotFound();
@@ -165,7 +168,7 @@ export class PlaidSyncDb {
trx?: Knex.Transaction,
) {
const uncategorizedTransactions =
await this.uncategorizedBankTransactionModel
await this.uncategorizedBankTransactionModel()
.query(trx)
.whereIn('plaidTransactionId', plaidTransactionsIds);
const uncategorizedTransactionsIds = uncategorizedTransactions.map(
@@ -193,7 +196,7 @@ export class PlaidSyncDb {
lastCursor: string,
trx?: Knex.Transaction,
): Promise<void> {
await this.plaidItemModel
await this.plaidItemModel()
.query(trx)
.findOne({ plaidItemId })
.patch({ lastCursor });
@@ -208,7 +211,7 @@ export class PlaidSyncDb {
plaidAccountIds: string[],
trx?: Knex.Transaction,
): Promise<void> {
await this.accountModel
await this.accountModel()
.query(trx)
.whereIn('plaid_account_id', plaidAccountIds)
.patch({
@@ -227,7 +230,7 @@ export class PlaidSyncDb {
isFeedsActive: boolean = true,
trx?: Knex.Transaction,
): Promise<void> {
await this.accountModel
await this.accountModel()
.query(trx)
.whereIn('plaid_account_id', plaidAccountIds)
.patch({

View File

@@ -11,6 +11,7 @@ import {
RemovedTransaction,
} from 'plaid';
import { PLAID_CLIENT } from '@/modules/Plaid/Plaid.module';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class PlaidUpdateTransactions {
@@ -19,7 +20,7 @@ export class PlaidUpdateTransactions {
private readonly uow: UnitOfWork,
@Inject(PlaidItem.name)
private readonly plaidItemModel: typeof PlaidItem,
private readonly plaidItemModel: TenantModelProxy<typeof PlaidItem>,
@Inject(PLAID_CLIENT)
private readonly plaidClient: PlaidApi,
@@ -105,9 +106,10 @@ export class PlaidUpdateTransactions {
): Promise<PlaidFetchedTransactionsUpdates> {
// the transactions endpoint is paginated, so we may need to hit it multiple times to
// retrieve all available transactions.
const plaidItem = await this.plaidItemModel
const plaidItem = await this.plaidItemModel()
.query()
.findOne('plaidItemId', plaidItemId);
if (!plaidItem) {
throw new Error('The given Plaid item id is not found.');
}

View File

@@ -11,9 +11,12 @@ import { BankRulesModule } from '../BankRules/BankRules.module';
const models = [RegisterTenancyModel(RecognizedBankTransaction)];
@Module({
imports: [BankingTransactionsModule, forwardRef(() => BankRulesModule)],
providers: [
imports: [
BankingTransactionsModule,
forwardRef(() => BankRulesModule),
...models,
],
providers: [
GetAutofillCategorizeTransactionService,
RevertRecognizedTransactionsService,
RecognizeTranasctionsService,

View File

@@ -8,18 +8,23 @@ import { BankRule } from '@/modules/BankRules/models/BankRule';
import { RecognizedBankTransaction } from '../models/RecognizedBankTransaction';
import { UncategorizedBankTransaction } from '@/modules/BankingTransactions/models/UncategorizedBankTransaction';
import { transformToMapBy } from '@/utils/transform-to-map-by';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class RecognizeTranasctionsService {
constructor(
@Inject(UncategorizedBankTransaction.name)
private readonly uncategorizedCashflowTransactionModel: typeof UncategorizedBankTransaction,
private readonly uncategorizedCashflowTransactionModel: TenantModelProxy<
typeof UncategorizedBankTransaction
>,
@Inject(RecognizedBankTransaction.name)
private readonly recognizedBankTransactionModel: typeof RecognizedBankTransaction,
private readonly recognizedBankTransactionModel: TenantModelProxy<
typeof RecognizedBankTransaction
>,
@Inject(BankRule.name)
private readonly bankRuleModel: typeof BankRule,
private readonly bankRuleModel: TenantModelProxy<typeof BankRule>,
) {}
/**
@@ -33,7 +38,7 @@ export class RecognizeTranasctionsService {
transaction: UncategorizedBankTransaction,
trx?: Knex.Transaction,
) {
const recognizedTransaction = await this.recognizedBankTransactionModel
const recognizedTransaction = await this.recognizedBankTransactionModel()
.query(trx)
.insert({
bankRuleId: bankRule.id,
@@ -43,7 +48,8 @@ export class RecognizeTranasctionsService {
assignedPayee: bankRule.assignPayee,
assignedMemo: bankRule.assignMemo,
});
await this.uncategorizedCashflowTransactionModel
await this.uncategorizedCashflowTransactionModel()
.query(trx)
.findById(transaction.id)
.patch({
@@ -52,7 +58,7 @@ export class RecognizeTranasctionsService {
}
/**
* Regonized the uncategorized transactions.
* Recognized the uncategorized transactions.
* @param {number|Array<number>} ruleId - The target rule id/ids.
* @param {RecognizeTransactionsCriteria}
* @param {Knex.Transaction} trx -
@@ -63,7 +69,7 @@ export class RecognizeTranasctionsService {
trx?: Knex.Transaction,
) {
const uncategorizedTranasctions =
await this.uncategorizedCashflowTransactionModel
await this.uncategorizedCashflowTransactionModel()
.query(trx)
.onBuild((query) => {
query.modify('notRecognized');
@@ -78,14 +84,17 @@ export class RecognizeTranasctionsService {
}
});
const bankRules = await this.bankRuleModel.query(trx).onBuild((q) => {
const rulesIds = !isEmpty(ruleId) ? castArray(ruleId) : [];
const bankRules = await this.bankRuleModel()
.query(trx)
.onBuild((q) => {
const rulesIds = !isEmpty(ruleId) ? castArray(ruleId) : [];
if (rulesIds?.length > 0) {
q.whereIn('id', rulesIds);
}
q.withGraphFetched('conditions');
});
if (rulesIds?.length > 0) {
q.whereIn('id', rulesIds);
}
q.withGraphFetched('conditions');
});
const bankRulesByAccountId = transformToMapBy(
bankRules,
'applyIfAccountId',

View File

@@ -3,6 +3,7 @@ import { castArray, first, uniq } from 'lodash';
import { GetAutofillCategorizeTransctionTransformer } from './GetAutofillCategorizeTransactionTransformer';
import { UncategorizedBankTransaction } from '@/modules/BankingTransactions/models/UncategorizedBankTransaction';
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class GetAutofillCategorizeTransactionService {
@@ -10,7 +11,9 @@ export class GetAutofillCategorizeTransactionService {
private readonly transformer: TransformerInjectable,
@Inject(UncategorizedBankTransaction.name)
private readonly uncategorizedBankTransactionModel: typeof UncategorizedBankTransaction,
private readonly uncategorizedBankTransactionModel: TenantModelProxy<
typeof UncategorizedBankTransaction
>,
) {}
/**
@@ -18,13 +21,14 @@ export class GetAutofillCategorizeTransactionService {
* @param {Array<number> | number} uncategorizeTransactionsId - Uncategorized transactions ids.
*/
public async getAutofillCategorizeTransaction(
uncategorizeTransactionsId: Array<number> | number
uncategorizeTransactionsId: Array<number> | number,
) {
const uncategorizeTransactionsIds = uniq(
castArray(uncategorizeTransactionsId)
castArray(uncategorizeTransactionsId),
);
const uncategorizedTransactions =
await this.uncategorizedBankTransactionModel.query()
await this.uncategorizedBankTransactionModel()
.query()
.whereIn('id', uncategorizeTransactionsIds)
.withGraphFetched('recognizedTransaction.assignAccount')
.withGraphFetched('recognizedTransaction.bankRule')
@@ -36,7 +40,7 @@ export class GetAutofillCategorizeTransactionService {
{
uncategorizedTransactions,
firstUncategorizedTransaction: first(uncategorizedTransactions),
}
},
);
}
}

View File

@@ -38,6 +38,7 @@ const models = [
LedgerModule,
BranchesModule,
DynamicListModule,
...models,
],
controllers: [BankingTransactionsController],
providers: [
@@ -56,7 +57,6 @@ const models = [
CommandBankTransactionValidator,
BranchTransactionDTOTransformer,
RemovePendingUncategorizedTransaction,
...models,
],
exports: [...models, RemovePendingUncategorizedTransaction],
})

View File

@@ -17,6 +17,7 @@ import {
ICommandCashflowCreatedPayload,
ICommandCashflowCreatingPayload,
} from '../types/BankingTransactions.types';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class CreateBankTransactionService {
@@ -28,10 +29,10 @@ export class CreateBankTransactionService {
private branchDTOTransform: BranchTransactionDTOTransformer,
@Inject(BankTransaction.name)
private bankTransactionModel: typeof BankTransaction,
private bankTransactionModel: TenantModelProxy<typeof BankTransaction>,
@Inject(Account.name)
private accountModel: typeof Account,
private accountModel: TenantModelProxy<typeof Account>,
) {}
/**
@@ -118,13 +119,13 @@ export class CreateBankTransactionService {
userId?: number,
): Promise<BankTransaction> => {
// Retrieves the cashflow account or throw not found error.
const cashflowAccount = await this.accountModel
const cashflowAccount = await this.accountModel()
.query()
.findById(newTransactionDTO.cashflowAccountId)
.throwIfNotFound();
// Retrieves the credit account or throw not found error.
const creditAccount = await this.accountModel
const creditAccount = await this.accountModel()
.query()
.findById(newTransactionDTO.creditAccountId)
.throwIfNotFound();
@@ -149,7 +150,7 @@ export class CreateBankTransactionService {
} as ICommandCashflowCreatingPayload,
);
// Inserts cashflow owner contribution transaction.
const cashflowTransaction = await this.bankTransactionModel
const cashflowTransaction = await this.bankTransactionModel()
.query(trx)
.upsertGraph(cashflowTransactionObj);

View File

@@ -11,6 +11,7 @@ import {
ICommandCashflowDeletedPayload,
ICommandCashflowDeletingPayload,
} from '../types/BankingTransactions.types';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class DeleteCashflowTransaction {
@@ -19,10 +20,12 @@ export class DeleteCashflowTransaction {
private readonly eventEmitter: EventEmitter2,
@Inject(BankTransaction.name)
private readonly bankTransaction: typeof BankTransaction,
private readonly bankTransaction: TenantModelProxy<typeof BankTransaction>,
@Inject(BankTransactionLine.name)
private readonly bankTransactionLine: typeof BankTransactionLine,
private readonly bankTransactionLine: TenantModelProxy<
typeof BankTransactionLine
>,
) {}
/**
@@ -35,7 +38,7 @@ export class DeleteCashflowTransaction {
trx?: Knex.Transaction,
): Promise<BankTransaction> => {
// Retrieve the cashflow transaction.
const oldCashflowTransaction = await this.bankTransaction
const oldCashflowTransaction = await this.bankTransaction()
.query()
.findById(cashflowTransactionId);
// Throw not found error if the given transaction id not found.
@@ -50,13 +53,13 @@ export class DeleteCashflowTransaction {
} as ICommandCashflowDeletingPayload);
// Delete cashflow transaction associated lines first.
await this.bankTransactionLine
await this.bankTransactionLine()
.query(trx)
.where('cashflow_transaction_id', cashflowTransactionId)
.delete();
// Delete cashflow transaction.
await this.bankTransaction
await this.bankTransaction()
.query(trx)
.findById(cashflowTransactionId)
.delete();

View File

@@ -10,6 +10,7 @@ import { UncategorizedBankTransaction } from '../models/UncategorizedBankTransac
import { ServiceError } from '@/modules/Items/ServiceError';
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { events } from '@/common/events/events';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class RemovePendingUncategorizedTransaction {
@@ -18,7 +19,9 @@ export class RemovePendingUncategorizedTransaction {
private readonly uow: UnitOfWork,
@Inject(UncategorizedBankTransaction.name)
private readonly uncategorizedBankTransaction: typeof UncategorizedBankTransaction,
private readonly uncategorizedBankTransaction: TenantModelProxy<
typeof UncategorizedBankTransaction
>,
) {}
/**
@@ -31,7 +34,7 @@ export class RemovePendingUncategorizedTransaction {
uncategorizedTransactionId: number,
trx?: Knex.Transaction,
): Promise<void> {
const pendingTransaction = await this.uncategorizedBankTransaction
const pendingTransaction = await this.uncategorizedBankTransaction()
.query(trx)
.findById(uncategorizedTransactionId)
.throwIfNotFound();
@@ -49,7 +52,7 @@ export class RemovePendingUncategorizedTransaction {
} as IPendingTransactionRemovingEventPayload,
);
// Removes the pending uncategorized transaction.
await this.uncategorizedBankTransaction
await this.uncategorizedBankTransaction()
.query(trx)
.findById(uncategorizedTransactionId)
.delete();

View File

@@ -2,12 +2,15 @@ import { Inject, Injectable } from '@nestjs/common';
import { ERRORS } from '../constants';
import { ServiceError } from '../../Items/ServiceError';
import { BankTransactionLine } from '../models/BankTransactionLine';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class ValidateDeleteBankAccountTransactions {
constructor(
@Inject(BankTransactionLine.name)
private readonly bankTransactionLineModel: typeof BankTransactionLine,
private readonly bankTransactionLineModel: TenantModelProxy<
typeof BankTransactionLine
>,
) {}
/**
@@ -15,7 +18,7 @@ export class ValidateDeleteBankAccountTransactions {
* @param {number} accountId
*/
public validateAccountHasNoCashflowEntries = async (accountId: number) => {
const associatedLines = await this.bankTransactionLineModel
const associatedLines = await this.bankTransactionLineModel()
.query()
.where('creditAccountId', accountId)
.orWhere('cashflowAccountId', accountId);

View File

@@ -2,6 +2,7 @@ import { Inject, Injectable } from '@nestjs/common';
import { GetRecognizedTransactionTransformer } from './GetRecognizedTransactionTransformer';
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
import { UncategorizedBankTransaction } from '../models/UncategorizedBankTransaction';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class GetRecognizedTransactionService {
@@ -9,7 +10,9 @@ export class GetRecognizedTransactionService {
private readonly transformer: TransformerInjectable,
@Inject(UncategorizedBankTransaction.name)
private readonly uncategorizedBankTransactionModel: typeof UncategorizedBankTransaction
private readonly uncategorizedBankTransactionModel: TenantModelProxy<
typeof UncategorizedBankTransaction
>,
) {}
/**
@@ -17,11 +20,10 @@ export class GetRecognizedTransactionService {
* @param {number} tenantId
* @param {number} uncategorizedTransactionId
*/
public async getRecognizedTransaction(
uncategorizedTransactionId: number
) {
public async getRecognizedTransaction(uncategorizedTransactionId: number) {
const uncategorizedTransaction =
await this.uncategorizedBankTransactionModel.query()
await this.uncategorizedBankTransactionModel()
.query()
.findById(uncategorizedTransactionId)
.withGraphFetched('matchedBankTransactions')
.withGraphFetched('recognizedTransaction.assignAccount')
@@ -31,7 +33,7 @@ export class GetRecognizedTransactionService {
return this.transformer.transform(
uncategorizedTransaction,
new GetRecognizedTransactionTransformer()
new GetRecognizedTransactionTransformer(),
);
}
}

View File

@@ -12,12 +12,15 @@ import {
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { events } from '@/common/events/events';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class ExcludeBankTransactionService {
constructor(
@Inject(UncategorizedBankTransaction.name)
private uncategorizedBankTransactionModel: typeof UncategorizedBankTransaction,
private uncategorizedBankTransactionModel: TenantModelProxy<
typeof UncategorizedBankTransaction
>,
private uow: UnitOfWork,
private eventEmitter: EventEmitter2,
@@ -30,7 +33,7 @@ export class ExcludeBankTransactionService {
*/
public async excludeBankTransaction(uncategorizedTransactionId: number) {
const oldUncategorizedTransaction =
await this.uncategorizedBankTransactionModel
await this.uncategorizedBankTransactionModel()
.query()
.findById(uncategorizedTransactionId)
.throwIfNotFound();
@@ -47,7 +50,7 @@ export class ExcludeBankTransactionService {
trx,
} as IBankTransactionUnexcludingEventPayload);
await this.uncategorizedBankTransactionModel
await this.uncategorizedBankTransactionModel()
.query(trx)
.findById(uncategorizedTransactionId)
.patch({

View File

@@ -12,6 +12,7 @@ import { Inject, Injectable } from '@nestjs/common';
import { UncategorizedBankTransaction } from '@/modules/BankingTransactions/models/UncategorizedBankTransaction';
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { events } from '@/common/events/events';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class UnexcludeBankTransactionService {
@@ -20,7 +21,9 @@ export class UnexcludeBankTransactionService {
private readonly uow: UnitOfWork,
@Inject(UncategorizedBankTransaction.name)
private readonly uncategorizedBankTransactionModel: typeof UncategorizedBankTransaction,
private readonly uncategorizedBankTransactionModel: TenantModelProxy<
typeof UncategorizedBankTransaction
>,
) {}
/**
@@ -33,7 +36,7 @@ export class UnexcludeBankTransactionService {
uncategorizedTransactionId: number,
): Promise<void> {
const oldUncategorizedTransaction =
await this.uncategorizedBankTransactionModel
await this.uncategorizedBankTransactionModel()
.query()
.findById(uncategorizedTransactionId)
.throwIfNotFound();
@@ -49,7 +52,7 @@ export class UnexcludeBankTransactionService {
uncategorizedTransactionId,
} as IBankTransactionExcludingEventPayload);
await this.uncategorizedBankTransactionModel
await this.uncategorizedBankTransactionModel()
.query(trx)
.findById(uncategorizedTransactionId)
.patch({

View File

@@ -3,14 +3,21 @@ import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectab
import { ExcludedBankTransactionsQuery } from '../types/BankTransactionsExclude.types';
import { UncategorizedTransactionTransformer } from '@/modules/BankingCategorize/commands/UncategorizedTransaction.transformer';
import { UncategorizedBankTransaction } from '@/modules/BankingTransactions/models/UncategorizedBankTransaction';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class GetExcludedBankTransactionsService {
/**
* @param {TransformerInjectable} transformer
* @param {TenantModelProxy<typeof UncategorizedBankTransaction>} uncategorizedBankTransaction
*/
constructor(
private readonly transformer: TransformerInjectable,
@Inject(UncategorizedBankTransaction.name)
private readonly uncategorizedBankTransaction: typeof UncategorizedBankTransaction,
private readonly uncategorizedBankTransaction: TenantModelProxy<
typeof UncategorizedBankTransaction
>,
) {}
/**
@@ -27,7 +34,7 @@ export class GetExcludedBankTransactionsService {
pageSize: 20,
...filter,
};
const { results, pagination } = await this.uncategorizedBankTransaction
const { results, pagination } = await this.uncategorizedBankTransaction()
.query()
.onBuild((q) => {
q.modify('excluded');

View File

@@ -7,15 +7,18 @@ import {
import { Account } from '@/modules/Accounts/models/Account.model';
import { UncategorizedBankTransaction } from '@/modules/BankingTransactions/models/UncategorizedBankTransaction';
import { events } from '@/common/events/events';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class DecrementUncategorizedTransactionOnExclude {
constructor(
@Inject(Account.name)
private readonly account: typeof Account,
private readonly account: TenantModelProxy<typeof Account>,
@Inject(UncategorizedBankTransaction.name)
private readonly uncategorizedBankTransaction: typeof UncategorizedBankTransaction,
private readonly uncategorizedBankTransaction: TenantModelProxy<
typeof UncategorizedBankTransaction
>,
) {}
/**
@@ -27,11 +30,12 @@ export class DecrementUncategorizedTransactionOnExclude {
uncategorizedTransactionId,
trx,
}: IBankTransactionExcludedEventPayload) {
const transaction = await this.uncategorizedBankTransaction.query(
trx
).findById(uncategorizedTransactionId);
const transaction = await this.uncategorizedBankTransaction()
.query(trx)
.findById(uncategorizedTransactionId);
await this.account.query(trx)
await this.account()
.query(trx)
.findById(transaction.accountId)
.decrement('uncategorizedTransactions', 1);
}
@@ -45,11 +49,12 @@ export class DecrementUncategorizedTransactionOnExclude {
uncategorizedTransactionId,
trx,
}: IBankTransactionUnexcludedEventPayload) {
const transaction = await this.uncategorizedBankTransaction.query().findById(
uncategorizedTransactionId
);
const transaction = await this.uncategorizedBankTransaction()
.query()
.findById(uncategorizedTransactionId);
//
await this.account.query(trx)
await this.account()
.query(trx)
.findById(transaction.accountId)
.increment('uncategorizedTransactions', 1);
}

View File

@@ -4,12 +4,13 @@ import { Bill } from '../../Bills/models/Bill';
import { IBillPaymentEntryDTO } from '../types/BillPayments.types';
import { entriesAmountDiff } from '@/utils/entries-amount-diff';
import Objection from 'objection';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class BillPaymentBillSync {
constructor(
@Inject(Bill.name)
private readonly bill: typeof Bill
private readonly bill: TenantModelProxy<typeof Bill>,
) {}
/**
@@ -36,7 +37,7 @@ export class BillPaymentBillSync {
if (diffEntry.paymentAmount === 0) {
return;
}
const oper = this.bill.changePaymentAmount(
const oper = this.bill().changePaymentAmount(
diffEntry.billId,
diffEntry.paymentAmount,
trx,

View File

@@ -6,6 +6,7 @@ import { Account } from '@/modules/Accounts/models/Account.model';
import { BillPayment } from '../models/BillPayment';
import { AccountRepository } from '@/modules/Accounts/repositories/Account.repository';
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class BillPaymentGLEntries {
@@ -15,24 +16,23 @@ export class BillPaymentGLEntries {
private readonly tenancyContext: TenancyContext,
@Inject(BillPayment.name)
private readonly billPaymentModel: typeof BillPayment,
private readonly billPaymentModel: TenantModelProxy<typeof BillPayment>,
@Inject(Account.name)
private readonly accountModel: typeof Account,
private readonly accountModel: TenantModelProxy<typeof Account>,
) {}
/**
* Creates a bill payment GL entries.
* @param {number} tenantId
* @param {number} billPaymentId
* @param {Knex.Transaction} trx
* @param {number} billPaymentId - Bill payment id.
* @param {Knex.Transaction} trx - Knex transaction.
*/
public writePaymentGLEntries = async (
billPaymentId: number,
trx?: Knex.Transaction,
): Promise<void> => {
// Retrieves the bill payment details with associated entries.
const payment = await this.billPaymentModel
const payment = await this.billPaymentModel()
.query(trx)
.findById(billPaymentId)
.withGraphFetched('entries.bill');
@@ -47,7 +47,7 @@ export class BillPaymentGLEntries {
trx,
);
// Exchange gain or loss account.
const EXGainLossAccount = await this.accountModel
const EXGainLossAccount = await this.accountModel()
.query(trx)
.modify('findBySlug', 'exchange-grain-loss')
.first();

View File

@@ -11,21 +11,24 @@ import { BillPaymentEntry } from '../models/BillPaymentEntry';
import { ServiceError } from '../../Items/ServiceError';
import { ACCOUNT_TYPE } from '@/constants/accounts';
import { Account } from '../../Accounts/models/Account.model';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class BillPaymentValidators {
constructor(
@Inject(Bill.name)
private readonly billModel: typeof Bill,
private readonly billModel: TenantModelProxy<typeof Bill>,
@Inject(BillPayment.name)
private readonly billPaymentModel: typeof BillPayment,
private readonly billPaymentModel: TenantModelProxy<typeof BillPayment>,
@Inject(BillPaymentEntry.name)
private readonly billPaymentEntryModel: typeof BillPaymentEntry,
private readonly billPaymentEntryModel: TenantModelProxy<
typeof BillPaymentEntry
>,
@Inject(Account.name)
private readonly accountModel: typeof Account,
private readonly accountModel: TenantModelProxy<typeof Account>,
) {}
/**
@@ -35,7 +38,7 @@ export class BillPaymentValidators {
* @param {Function} next
*/
public async getPaymentMadeOrThrowError(paymentMadeId: number) {
const billPayment = await this.billPaymentModel
const billPayment = await this.billPaymentModel()
.query()
.withGraphFetched('entries')
.findById(paymentMadeId);
@@ -53,9 +56,10 @@ export class BillPaymentValidators {
* @return {Promise<IAccountType>}
*/
public async getPaymentAccountOrThrowError(paymentAccountId: number) {
const paymentAccount = await this.accountModel
const paymentAccount = await this.accountModel()
.query()
.findById(paymentAccountId);
if (!paymentAccount) {
throw new ServiceError(ERRORS.PAYMENT_ACCOUNT_NOT_FOUND);
}
@@ -82,7 +86,7 @@ export class BillPaymentValidators {
paymentMadeNumber: string,
notPaymentMadeId?: number,
) {
const foundBillPayment = await this.billPaymentModel
const foundBillPayment = await this.billPaymentModel()
.query()
.onBuild((builder: any) => {
builder.findOne('payment_number', paymentMadeNumber);
@@ -107,7 +111,7 @@ export class BillPaymentValidators {
) {
const entriesBillsIds = billPaymentEntries.map((e: any) => e.billId);
const storedBills = await this.billModel
const storedBills = await this.billModel()
.query()
.whereIn('id', entriesBillsIds)
.where('vendor_id', vendorId);
@@ -144,7 +148,7 @@ export class BillPaymentValidators {
(entry: IBillPaymentEntryDTO) => entry.billId,
);
const storedBills = await this.billModel.query().whereIn('id', billsIds);
const storedBills = await this.billModel().query().whereIn('id', billsIds);
const storedBillsMap = new Map(
storedBills.map((bill) => {
const oldEntries = oldPaymentEntries.filter(
@@ -191,7 +195,7 @@ export class BillPaymentValidators {
.filter((entry: any) => entry.id)
.map((entry: any) => entry.id);
const storedEntries = await this.billPaymentEntryModel
const storedEntries = await this.billPaymentEntryModel()
.query()
.where('bill_payment_id', billPaymentId);
@@ -243,7 +247,7 @@ export class BillPaymentValidators {
* @param {number} vendorId
*/
public async validateVendorHasNoPayments(vendorId: number) {
const payments = await this.billPaymentModel
const payments = await this.billPaymentModel()
.query()
.where('vendor_id', vendorId);

View File

@@ -5,19 +5,20 @@ import { Bill } from '../../Bills/models/Bill';
import { BillPayment } from '../models/BillPayment';
import { IBillReceivePageEntry } from '../types/BillPayments.types';
import { ServiceError } from '../../Items/ServiceError';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export default class BillPaymentsPages {
/**
* @param {typeof Bill} billModel - Bill model.
* @param {typeof BillPayment} billPaymentModel - Bill payment model.
* @param {TenantModelProxy<typeof Bill>} billModel - Bill model.
* @param {TenantModelProxy<typeof BillPayment>} billPaymentModel - Bill payment model.
*/
constructor(
@Inject(Bill.name)
private readonly billModel: typeof Bill,
private readonly billModel: TenantModelProxy<typeof Bill>,
@Inject(BillPayment.name)
private readonly billPaymentModel: typeof BillPayment,
private readonly billPaymentModel: TenantModelProxy<typeof BillPayment>,
) {}
/**
@@ -29,7 +30,7 @@ export default class BillPaymentsPages {
billPayment: Omit<BillPayment, 'entries'>;
entries: IBillReceivePageEntry[];
}> {
const billPayment = await this.billPaymentModel
const billPayment = await this.billPaymentModel()
.query()
.findById(billPaymentId)
.withGraphFetched('entries.bill')
@@ -74,7 +75,7 @@ export default class BillPaymentsPages {
vendorId: number,
): Promise<IBillReceivePageEntry[]> {
// Retrieve all payable bills that associated to the payment made transaction.
const payableBills = await this.billModel
const payableBills = await this.billModel()
.query()
.modify('opened')
.modify('dueBills')

View File

@@ -13,6 +13,7 @@ import { events } from '@/common/events/events';
import { TenancyContext } from '../../Tenancy/TenancyContext.service';
import { BillPayment } from '../models/BillPayment';
import { Vendor } from '../../Vendors/models/Vendor';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class CreateBillPaymentService {
@@ -22,8 +23,8 @@ export class CreateBillPaymentService {
* @param {BillPaymentValidators} validators - Bill payment validators service.
* @param {CommandBillPaymentDTOTransformer} commandTransformerDTO - Command bill payment DTO transformer service.
* @param {TenancyContext} tenancyContext - Tenancy context service.
* @param {typeof Vendor} vendorModel - Vendor model.
* @param {typeof BillPayment} billPaymentModel - Bill payment model.
* @param {TenantModelProxy<typeof Vendor>} vendorModel - Vendor model.
* @param {TenantModelProxy<typeof BillPayment>} billPaymentModel - Bill payment model.
*/
constructor(
private uow: UnitOfWork,
@@ -33,10 +34,10 @@ export class CreateBillPaymentService {
private tenancyContext: TenancyContext,
@Inject(Vendor.name)
private readonly vendorModel: typeof Vendor,
private readonly vendorModel: TenantModelProxy<typeof Vendor>,
@Inject(BillPayment.name)
private readonly billPaymentModel: typeof BillPayment,
private readonly billPaymentModel: TenantModelProxy<typeof BillPayment>,
) {}
/**
@@ -61,7 +62,7 @@ export class CreateBillPaymentService {
const tenantMeta = await this.tenancyContext.getTenant(true);
// Retrieves the payment vendor or throw not found error.
const vendor = await this.vendorModel
const vendor = await this.vendorModel()
.query()
.findById(billPaymentDTO.vendorId)
.throwIfNotFound();
@@ -102,7 +103,7 @@ export class CreateBillPaymentService {
} as IBillPaymentCreatingPayload);
// Writes the bill payment graph to the storage.
const billPayment = await this.billPaymentModel
const billPayment = await this.billPaymentModel()
.query(trx)
.insertGraphAndFetch({
...billPaymentObj,

View File

@@ -9,6 +9,7 @@ import { BillPaymentEntry } from '../models/BillPaymentEntry';
import { UnitOfWork } from '../../Tenancy/TenancyDB/UnitOfWork.service';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { events } from '@/common/events/events';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class DeleteBillPayment {
@@ -23,10 +24,12 @@ export class DeleteBillPayment {
private readonly uow: UnitOfWork,
@Inject(BillPayment.name)
private readonly billPaymentModel: typeof BillPayment,
private readonly billPaymentModel: TenantModelProxy<typeof BillPayment>,
@Inject(BillPaymentEntry.name)
private readonly billPaymentEntryModel: typeof BillPaymentEntry,
private readonly billPaymentEntryModel: TenantModelProxy<
typeof BillPaymentEntry
>,
) {}
/**
@@ -36,7 +39,7 @@ export class DeleteBillPayment {
*/
public async deleteBillPayment(billPaymentId: number) {
// Retrieve the bill payment or throw not found service error.
const oldBillPayment = await this.billPaymentModel
const oldBillPayment = await this.billPaymentModel()
.query()
.withGraphFetched('entries')
.findById(billPaymentId)
@@ -52,13 +55,13 @@ export class DeleteBillPayment {
} as IBillPaymentDeletingPayload);
// Deletes the bill payment associated entries.
await this.billPaymentEntryModel
await this.billPaymentEntryModel()
.query(trx)
.where('bill_payment_id', billPaymentId)
.delete();
// Deletes the bill payment transaction.
await this.billPaymentModel
await this.billPaymentModel()
.query(trx)
.where('id', billPaymentId)
.delete();

View File

@@ -12,6 +12,7 @@ import { CommandBillPaymentDTOTransformer } from './CommandBillPaymentDTOTransfo
import { Vendor } from '@/modules/Vendors/models/Vendor';
import { events } from '@/common/events/events';
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class EditBillPayment {
@@ -23,10 +24,10 @@ export class EditBillPayment {
private readonly tenancyContext: TenancyContext,
@Inject(BillPayment.name)
private readonly billPaymentModel: typeof BillPayment,
private readonly billPaymentModel: TenantModelProxy<typeof BillPayment>,
@Inject(Vendor.name)
private readonly vendorModel: typeof Vendor,
private readonly vendorModel: TenantModelProxy<typeof Vendor>,
) {}
/**
@@ -53,13 +54,13 @@ export class EditBillPayment {
): Promise<BillPayment> {
const tenantMeta = await this.tenancyContext.getTenant(true);
const oldBillPayment = await this.billPaymentModel
const oldBillPayment = await this.billPaymentModel()
.query()
.findById(billPaymentId)
.withGraphFetched('entries')
.throwIfNotFound();
const vendor = await this.vendorModel
const vendor = await this.vendorModel()
.query()
.findById(billPaymentDTO.vendorId)
.throwIfNotFound();
@@ -115,7 +116,7 @@ export class EditBillPayment {
} as IBillPaymentEditingPayload);
// Edits the bill payment transaction graph on the storage.
const billPayment = await this.billPaymentModel
const billPayment = await this.billPaymentModel()
.query(trx)
.upsertGraphAndFetch({
id: billPaymentId,

View File

@@ -2,6 +2,7 @@ import { Inject, Injectable } from '@nestjs/common';
import { TransformerInjectable } from '../../Transformer/TransformerInjectable.service';
import { BillPayment } from '../models/BillPayment';
import { BillPaymentTransformer } from './BillPaymentTransformer';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class GetBillPayment {
@@ -9,7 +10,7 @@ export class GetBillPayment {
private readonly transformer: TransformerInjectable,
@Inject(BillPayment.name)
private readonly billPaymentModel: typeof BillPayment,
private readonly billPaymentModel: TenantModelProxy<typeof BillPayment>,
) {}
/**
@@ -18,7 +19,7 @@ export class GetBillPayment {
* @return {Promise<BillPayment>}
*/
public async getBillPayment(billPyamentId: number): Promise<BillPayment> {
const billPayment = await this.billPaymentModel
const billPayment = await this.billPaymentModel()
.query()
.withGraphFetched('entries.bill')
.withGraphFetched('vendor')

View File

@@ -1,6 +1,7 @@
import { Inject, Injectable } from '@nestjs/common';
import { Bill } from '@/modules/Bills/models/Bill';
import { BillPayment } from '../models/BillPayment';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class GetPaymentBills {
@@ -10,10 +11,10 @@ export class GetPaymentBills {
*/
constructor(
@Inject(Bill.name)
private readonly billModel: typeof Bill,
private readonly billModel: TenantModelProxy<typeof Bill>,
@Inject(BillPayment.name)
private readonly billPaymentModel: typeof BillPayment,
private readonly billPaymentModel: TenantModelProxy<typeof BillPayment>,
) {}
/**
@@ -21,14 +22,14 @@ export class GetPaymentBills {
* @param {number} billPaymentId - Bill payment id.
*/
public async getPaymentBills(billPaymentId: number) {
const billPayment = await this.billPaymentModel
const billPayment = await this.billPaymentModel()
.query()
.findById(billPaymentId)
.throwIfNotFound();
const paymentBillsIds = billPayment.entries.map((entry) => entry.id);
const bills = await this.billModel.query().whereIn('id', paymentBillsIds);
const bills = await this.billModel().query().whereIn('id', paymentBillsIds);
return bills;
}

View File

@@ -14,6 +14,7 @@ import { IBillDTO } from '../Bills.types';
import { Bill } from '../models/Bill';
import { assocItemEntriesDefaultIndex } from '@/utils/associate-item-entries-index';
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class BillDTOTransformer {
@@ -23,8 +24,9 @@ export class BillDTOTransformer {
private taxDTOTransformer: ItemEntriesTaxTransactions,
private tenancyContext: TenancyContext,
@Inject(ItemEntry.name) private itemEntryModel: typeof ItemEntry,
@Inject(Item.name) private itemModel: typeof Item,
@Inject(ItemEntry.name)
private itemEntryModel: TenantModelProxy<typeof ItemEntry>,
@Inject(Item.name) private itemModel: TenantModelProxy<typeof Item>,
) {}
/**
@@ -33,7 +35,7 @@ export class BillDTOTransformer {
* @returns {number}
*/
private getBillEntriesTotal(entries: ItemEntry[]): number {
return sumBy(entries, (e) => this.itemEntryModel.calcAmount(e));
return sumBy(entries, (e) => this.itemEntryModel().calcAmount(e));
}
/**
@@ -61,7 +63,7 @@ export class BillDTOTransformer {
oldBill?: Bill,
): Promise<Bill> {
const amount = sumBy(billDTO.entries, (e) =>
this.itemEntryModel.calcAmount(e),
this.itemEntryModel().calcAmount(e),
);
// Retrieve the landed cost amount from landed cost entries.
const landedCostAmount = this.getBillLandedCostAmount(billDTO);
@@ -125,7 +127,9 @@ export class BillDTOTransformer {
private setBillEntriesDefaultAccounts() {
return async (entries: ItemEntry[]) => {
const entriesItemsIds = entries.map((e) => e.itemId);
const items = await this.itemModel.query().whereIn('id', entriesItemsIds);
const items = await this.itemModel()
.query()
.whereIn('id', entriesItemsIds);
return entries.map((entry) => {
const item = items.find((i) => i.id === entry.itemId);

View File

@@ -4,6 +4,8 @@ import { Bill } from '../models/Bill';
import { Inject, Injectable } from '@nestjs/common';
import { ItemsEntriesService } from '@/modules/Items/ItemsEntries.service';
import { InventoryTransactionsService } from '@/modules/InventoryCost/InventoryTransactions.service';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class BillInventoryTransactions {
constructor(
@@ -11,7 +13,7 @@ export class BillInventoryTransactions {
private readonly inventoryTransactionsService: InventoryTransactionsService,
@Inject(Bill.name)
private readonly bill: typeof Bill,
private readonly bill: TenantModelProxy<typeof Bill>,
) {}
/**
@@ -26,7 +28,7 @@ export class BillInventoryTransactions {
): Promise<void> {
// Retireve bill with assocaited entries and allocated cost entries.
const bill = await this.bill
const bill = await this.bill()
.query(trx)
.findById(billId)
.withGraphFetched('entries.allocatedCostEntries');

View File

@@ -4,6 +4,7 @@ import { AccountRepository } from '@/modules/Accounts/repositories/Account.repos
import { Bill } from '../models/Bill';
import { Inject, Injectable } from '@nestjs/common';
import { BillGL } from './BillsGL';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class BillGLEntries {
@@ -17,7 +18,7 @@ export class BillGLEntries {
private readonly accountRepository: AccountRepository,
@Inject(Bill.name)
private readonly billModel: typeof Bill,
private readonly billModel: TenantModelProxy<typeof Bill>,
) {}
/**
@@ -30,7 +31,7 @@ export class BillGLEntries {
trx?: Knex.Transaction,
) => {
// Retrieves bill with associated entries and landed costs.
const bill = await this.billModel
const bill = await this.billModel()
.query(trx)
.findById(billId)
.withGraphFetched('entries.item')

View File

@@ -8,22 +8,25 @@ import { BillPaymentEntry } from '@/modules/BillPayments/models/BillPaymentEntry
import { BillLandedCost } from '@/modules/BillLandedCosts/models/BillLandedCost';
import { VendorCreditAppliedBill } from '@/modules/VendorCreditsApplyBills/models/VendorCreditAppliedBill';
import { transformToMap } from '@/utils/transform-to-key';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class BillsValidators {
constructor(
@Inject(Bill.name) private billModel: typeof Bill,
@Inject(Bill.name) private billModel: TenantModelProxy<typeof Bill>,
@Inject(BillPaymentEntry.name)
private billPaymentEntryModel: typeof BillPaymentEntry,
private billPaymentEntryModel: TenantModelProxy<typeof BillPaymentEntry>,
@Inject(BillLandedCost.name)
private billLandedCostModel: typeof BillLandedCost,
private billLandedCostModel: TenantModelProxy<typeof BillLandedCost>,
@Inject(VendorCreditAppliedBill.name)
private vendorCreditAppliedBillModel: typeof VendorCreditAppliedBill,
private vendorCreditAppliedBillModel: TenantModelProxy<
typeof VendorCreditAppliedBill
>,
@Inject(Item.name) private itemModel: typeof Item,
@Inject(Item.name) private itemModel: TenantModelProxy<typeof Item>,
) {}
/**
@@ -57,7 +60,7 @@ export class BillsValidators {
billNumber: string,
notBillId?: number,
) {
const foundBills = await this.billModel
const foundBills = await this.billModel()
.query()
.where('bill_number', billNumber)
.onBuild((builder) => {
@@ -80,7 +83,7 @@ export class BillsValidators {
*/
public async validateBillHasNoEntries(billId: number) {
// Retrieve the bill associate payment made entries.
const entries = await this.billPaymentEntryModel
const entries = await this.billPaymentEntryModel()
.query()
.where('bill_id', billId);
@@ -105,7 +108,7 @@ export class BillsValidators {
* @param {number} billId
*/
public async validateBillHasNoLandedCost(billId: number) {
const billLandedCosts = await this.billLandedCostModel
const billLandedCosts = await this.billLandedCostModel()
.query()
.where('billId', billId);
@@ -123,7 +126,7 @@ export class BillsValidators {
newEntriesDTO: IItemEntryDTO[],
) {
const entriesItemsIds = newEntriesDTO.map((e) => e.itemId);
const entriesItems = await this.itemModel
const entriesItems = await this.itemModel()
.query()
.whereIn('id', entriesItemsIds);
@@ -147,9 +150,10 @@ export class BillsValidators {
* @param {number} billId
*/
public validateBillHasNoAppliedToCredit = async (billId: number) => {
const appliedTransactions = await this.vendorCreditAppliedBillModel
const appliedTransactions = await this.vendorCreditAppliedBillModel()
.query()
.where('billId', billId);
if (appliedTransactions.length > 0) {
throw new ServiceError(ERRORS.BILL_HAS_APPLIED_TO_VENDOR_CREDIT);
}
@@ -160,7 +164,7 @@ export class BillsValidators {
* @param {number} vendorId - Vendor id.
*/
public async validateVendorHasNoBills(vendorId: number) {
const bills = await this.billModel.query().where('vendor_id', vendorId);
const bills = await this.billModel().query().where('vendor_id', vendorId);
if (bills.length > 0) {
throw new ServiceError(ERRORS.VENDOR_HAS_BILLS);

View File

@@ -13,6 +13,7 @@ import { ItemsEntriesService } from '@/modules/Items/ItemsEntries.service';
import { Bill } from '../models/Bill';
import { Vendor } from '@/modules/Vendors/models/Vendor';
import { events } from '@/common/events/events';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class CreateBill {
@@ -24,10 +25,10 @@ export class CreateBill {
private transformerDTO: BillDTOTransformer,
@Inject(Bill.name)
private billModel: typeof Bill,
private billModel: TenantModelProxy<typeof Bill>,
@Inject(Vendor.name)
private vendorModel: typeof Vendor,
private vendorModel: TenantModelProxy<typeof Vendor>,
) {}
/**
@@ -49,7 +50,7 @@ export class CreateBill {
trx?: Knex.Transaction,
): Promise<Bill> {
// Retrieves the given bill vendor or throw not found error.
const vendor = await this.vendorModel
const vendor = await this.vendorModel()
.query()
.findById(billDTO.vendorId)
.throwIfNotFound();
@@ -80,7 +81,9 @@ export class CreateBill {
} as IBillCreatingPayload);
// Inserts the bill graph object to the storage.
const bill = await this.billModel.query(trx).upsertGraphAndFetch(billObj);
const bill = await this.billModel()
.query(trx)
.upsertGraphAndFetch(billObj);
// Triggers `onBillCreated` event.
await this.eventPublisher.emitAsync(events.bill.onCreated, {

View File

@@ -10,6 +10,7 @@ import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { ItemEntry } from '@/modules/TransactionItemEntry/models/ItemEntry';
import { Bill } from '../models/Bill';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class DeleteBill {
@@ -17,8 +18,12 @@ export class DeleteBill {
private readonly validators: BillsValidators,
private readonly uow: UnitOfWork,
private readonly eventPublisher: EventEmitter2,
@Inject(Bill.name) private readonly billModel: typeof Bill,
@Inject(ItemEntry.name) private readonly itemEntryModel: typeof ItemEntry,
@Inject(Bill.name)
private readonly billModel: TenantModelProxy<typeof Bill>,
@Inject(ItemEntry.name)
private readonly itemEntryModel: TenantModelProxy<typeof ItemEntry>,
) {}
/**
@@ -28,7 +33,7 @@ export class DeleteBill {
*/
public async deleteBill(billId: number) {
// Retrieve the given bill or throw not found error.
const oldBill = await this.billModel
const oldBill = await this.billModel()
.query()
.findById(billId)
.withGraphFetched('entries');
@@ -55,7 +60,8 @@ export class DeleteBill {
} as IBillEventDeletingPayload);
// Delete all associated bill entries.
await this.itemEntryModel.query(trx)
await this.itemEntryModel()
.query(trx)
.where('reference_type', 'Bill')
.where('reference_id', billId)
.delete();

View File

@@ -14,6 +14,7 @@ import { events } from '@/common/events/events';
import { Vendor } from '@/modules/Vendors/models/Vendor';
import { Knex } from 'knex';
import { TransactionLandedCostEntriesService } from '@/modules/BillLandedCosts/TransactionLandedCostEntries.service';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class EditBillService {
@@ -25,8 +26,8 @@ export class EditBillService {
private transactionLandedCostEntries: TransactionLandedCostEntriesService,
private transformerDTO: BillDTOTransformer,
@Inject(Bill.name) private billModel: typeof Bill,
@Inject(Vendor.name) private contactModel: typeof Vendor,
@Inject(Bill.name) private billModel: TenantModelProxy<typeof Bill>,
@Inject(Vendor.name) private contactModel: TenantModelProxy<typeof Vendor>,
) {}
/**
@@ -45,12 +46,9 @@ export class EditBillService {
* @param {IBillEditDTO} billDTO - The given new bill details.
* @return {Promise<IBill>}
*/
public async editBill(
billId: number,
billDTO: IBillEditDTO,
): Promise<Bill> {
public async editBill(billId: number, billDTO: IBillEditDTO): Promise<Bill> {
// Retrieve the given bill or throw not found error.
const oldBill = await this.billModel
const oldBill = await this.billModel()
.query()
.findById(billId)
.withGraphFetched('entries');
@@ -59,7 +57,7 @@ export class EditBillService {
this.validators.validateBillExistance(oldBill);
// Retrieve vendor details or throw not found service error.
const vendor = await this.contactModel
const vendor = await this.contactModel()
.query()
.findById(billDTO.vendorId)
.modify('vendor')
@@ -69,44 +67,42 @@ export class EditBillService {
if (billDTO.billNumber) {
await this.validators.validateBillNumberExists(
billDTO.billNumber,
billId
billId,
);
}
// Validate the entries ids existance.
await this.itemsEntriesService.validateEntriesIdsExistance(
billId,
'Bill',
billDTO.entries
billDTO.entries,
);
// Validate the items ids existance on the storage.
await this.itemsEntriesService.validateItemsIdsExistance(
billDTO.entries
);
await this.itemsEntriesService.validateItemsIdsExistance(billDTO.entries);
// Accept the purchasable items only.
await this.itemsEntriesService.validateNonPurchasableEntriesItems(
billDTO.entries
billDTO.entries,
);
// Transforms the bill DTO to model object.
const billObj = await this.transformerDTO.billDTOToModel(
billDTO,
vendor,
oldBill
oldBill,
);
// Validate bill total amount should be bigger than paid amount.
this.validators.validateBillAmountBiggerPaidAmount(
billObj.amount,
oldBill.paymentAmount
oldBill.paymentAmount,
);
// Validate landed cost entries that have allocated cost could not be deleted.
await this.transactionLandedCostEntries.validateLandedCostEntriesNotDeleted(
oldBill.entries,
billObj.entries
billObj.entries,
);
// Validate new landed cost entries should be bigger than new entries.
await this.transactionLandedCostEntries.validateLocatedCostEntriesSmallerThanNewEntries(
oldBill.entries,
billObj.entries
billObj.entries,
);
// Edits bill transactions and associated transactions under UOW envirement.
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
@@ -118,10 +114,12 @@ export class EditBillService {
} as IBillEditingPayload);
// Update the bill transaction.
const bill = await this.billModel.query(trx).upsertGraphAndFetch({
id: billId,
...billObj,
});
const bill = await this.billModel()
.query(trx)
.upsertGraphAndFetch({
id: billId,
...billObj,
});
// Triggers event `onBillEdited`.
await this.eventPublisher.emitAsync(events.bill.onEdited, {
oldBill,

View File

@@ -8,6 +8,7 @@ import { EventEmitter2 } from '@nestjs/event-emitter';
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { ServiceError } from '@/modules/Items/ServiceError';
import { events } from '@/common/events/events';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class OpenBillService {
@@ -17,7 +18,7 @@ export class OpenBillService {
private readonly eventPublisher: EventEmitter2,
@Inject(Bill.name)
private readonly billModel: typeof Bill,
private readonly billModel: TenantModelProxy<typeof Bill>,
) {}
/**
@@ -27,7 +28,7 @@ export class OpenBillService {
*/
public async openBill(billId: number): Promise<void> {
// Retrieve the given bill or throw not found error.
const oldBill = await this.billModel
const oldBill = await this.billModel()
.query()
.findById(billId)
.withGraphFetched('entries');
@@ -47,7 +48,7 @@ export class OpenBillService {
} as IBillOpeningPayload);
// Save the bill opened at on the storage.
const bill = await this.billModel
const bill = await this.billModel()
.query(trx)
.patchAndFetchById(billId, {
openedAt: moment().toMySqlDateTime(),

View File

@@ -3,11 +3,12 @@ import { BillsValidators } from '../commands/BillsValidators.service';
import { BillTransformer } from './Bill.transformer';
import { Bill } from '../models/Bill';
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class GetBill {
constructor(
@Inject(Bill.name) private billModel: typeof Bill,
@Inject(Bill.name) private billModel: TenantModelProxy<typeof Bill>,
private transformer: TransformerInjectable,
private validators: BillsValidators,
@@ -19,7 +20,7 @@ export class GetBill {
* @returns {Promise<IBill>}
*/
public async getBill(billId: number): Promise<Bill> {
const bill = await this.billModel
const bill = await this.billModel()
.query()
.findById(billId)
.withGraphFetched('vendor')
@@ -31,9 +32,6 @@ export class GetBill {
// Validates the bill existence.
this.validators.validateBillExistance(bill);
return this.transformer.transform(
bill,
new BillTransformer(),
);
return this.transformer.transform(bill, new BillTransformer());
}
}

View File

@@ -6,6 +6,7 @@ import { Bill } from '../models/Bill';
import { IFilterMeta, IPaginationMeta } from '@/interfaces/Model';
import { BillTransformer } from './Bill.transformer';
import { IBillsFilter } from '../Bills.types';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class GetBillsService {
@@ -13,7 +14,7 @@ export class GetBillsService {
private transformer: TransformerInjectable,
private dynamicListService: DynamicListService,
@Inject(Bill.name) private billModel: typeof Bill,
@Inject(Bill.name) private billModel: TenantModelProxy<typeof Bill>,
) {}
/**
@@ -30,10 +31,10 @@ export class GetBillsService {
// Dynamic list service.
const dynamicFilter = await this.dynamicListService.dynamicList(
this.billModel,
this.billModel(),
filter,
);
const { results, pagination } = await this.billModel
const { results, pagination } = await this.billModel()
.query()
.onBuild((builder) => {
builder.withGraphFetched('vendor');

View File

@@ -1,11 +1,11 @@
import { Inject, Injectable } from '@nestjs/common';
import { Bill } from '../models/Bill';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class GetDueBills {
constructor(
@Inject(Bill.name)
private billModel: typeof Bill,
@Inject(Bill.name) private billModel: TenantModelProxy<typeof Bill>,
) {}
/**
@@ -13,14 +13,16 @@ export class GetDueBills {
* @param {number} vendorId -
*/
public async getDueBills(vendorId?: number): Promise<Bill[]> {
const dueBills = await this.billModel.query().onBuild((query) => {
query.orderBy('bill_date', 'DESC');
query.modify('dueBills');
const dueBills = await this.billModel()
.query()
.onBuild((query) => {
query.orderBy('bill_date', 'DESC');
query.modify('dueBills');
if (vendorId) {
query.where('vendor_id', vendorId);
}
});
if (vendorId) {
query.where('vendor_id', vendorId);
}
});
return dueBills;
}
}

View File

@@ -13,6 +13,7 @@ import { ServiceError } from '@/modules/Items/ServiceError';
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { Branch } from '../models/Branch.model';
import { events } from '@/common/events/events';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class ActivateBranches {
@@ -24,7 +25,7 @@ export class ActivateBranches {
private readonly i18n: I18nService,
@Inject(Branch.name)
private readonly branchModel: typeof Branch,
private readonly branchModel: TenantModelProxy<typeof Branch>,
) {}
/**

View File

@@ -3,11 +3,12 @@ import { ERRORS } from '../constants';
import { Branch } from '../models/Branch.model';
import { ServiceError } from '../../Items/ServiceError';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class BranchCommandValidator {
constructor(
@Inject(Branch.name)
private readonly branchModel: typeof Branch,
private readonly branchModel: TenantModelProxy<typeof Branch>,
) {}
/**
@@ -15,7 +16,9 @@ export class BranchCommandValidator {
* @param {number} branchId
*/
public validateBranchNotOnlyWarehouse = async (branchId: number) => {
const warehouses = await this.branchModel.query().whereNot('id', branchId);
const warehouses = await this.branchModel()
.query()
.whereNot('id', branchId);
if (warehouses.length === 0) {
throw new ServiceError(ERRORS.COULD_NOT_DELETE_ONLY_BRANCH);
@@ -31,7 +34,7 @@ export class BranchCommandValidator {
code: string,
exceptBranchId?: number,
): Promise<void> => {
const branch = await this.branchModel
const branch = await this.branchModel()
.query()
.onBuild((query) => {
query.select(['id']);

View File

@@ -9,6 +9,7 @@ import { UnitOfWork } from '../../Tenancy/TenancyDB/UnitOfWork.service';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { Branch } from '../models/Branch.model';
import { events } from '@/common/events/events';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class CreateBranchService {
@@ -22,7 +23,7 @@ export class CreateBranchService {
private readonly eventPublisher: EventEmitter2,
@Inject(Branch.name)
private readonly branchModel: typeof Branch,
private readonly branchModel: TenantModelProxy<typeof Branch>,
) {}
/**
@@ -41,10 +42,11 @@ export class CreateBranchService {
trx,
} as IBranchCreatePayload);
const branch = await this.branchModel.query().insertAndFetch({
...createBranchDTO,
});
const branch = await this.branchModel()
.query()
.insertAndFetch({
...createBranchDTO,
});
// Triggers `onBranchCreated` event.
await this.eventPublisher.emitAsync(events.warehouse.onEdited, {
createBranchDTO,

View File

@@ -7,12 +7,13 @@ import { Branch } from '../models/Branch.model';
import { UnitOfWork } from '../../Tenancy/TenancyDB/UnitOfWork.service';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { events } from '@/common/events/events';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class DeleteBranchService {
constructor(
@Inject(Branch.name)
private readonly branchModel: typeof Branch,
private readonly branchModel: TenantModelProxy<typeof Branch>,
private readonly uow: UnitOfWork,
private readonly eventPublisher: EventEmitter2,
private readonly validator: BranchCommandValidator,
@@ -29,18 +30,18 @@ export class DeleteBranchService {
/**
* Deletes branch.
* @param {number} branchId
* @param {number} branchId
* @returns {Promise<void>}
*/
public deleteBranch = async (branchId: number): Promise<void> => {
// Retrieves the old branch or throw not found service error.
const oldBranch = await this.branchModel
const oldBranch = await this.branchModel()
.query()
.findById(branchId)
.throwIfNotFound();
// .queryAndThrowIfHasRelations({
// type: ERRORS.BRANCH_HAS_ASSOCIATED_TRANSACTIONS,
// });
// .queryAndThrowIfHasRelations({
// type: ERRORS.BRANCH_HAS_ASSOCIATED_TRANSACTIONS,
// });
// Authorize the branch before deleting.
await this.authorize(branchId);
@@ -53,7 +54,7 @@ export class DeleteBranchService {
trx,
} as IBranchDeletePayload);
await this.branchModel.query().findById(branchId).delete();
await this.branchModel().query().findById(branchId).delete();
// Triggers `onBranchCreate` event.
await this.eventPublisher.emitAsync(events.warehouse.onEdited, {

View File

@@ -9,12 +9,13 @@ import { Branch } from '../models/Branch.model';
import { UnitOfWork } from '../../Tenancy/TenancyDB/UnitOfWork.service';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { events } from '@/common/events/events';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class EditBranchService {
constructor(
@Inject(Branch.name)
private readonly branchModel: typeof Branch,
private readonly branchModel: TenantModelProxy<typeof Branch>,
private readonly uow: UnitOfWork,
private readonly eventPublisher: EventEmitter2,
) {}
@@ -29,7 +30,7 @@ export class EditBranchService {
editBranchDTO: IEditBranchDTO,
) => {
// Retrieves the old branch or throw not found service error.
const oldBranch = await this.branchModel
const oldBranch = await this.branchModel()
.query()
.findById(branchId)
.throwIfNotFound();
@@ -43,7 +44,7 @@ export class EditBranchService {
} as IBranchEditPayload);
// Edits the branch on the storage.
const branch = await this.branchModel
const branch = await this.branchModel()
.query()
.patchAndFetchById(branchId, {
...editBranchDTO,

View File

@@ -8,6 +8,7 @@ import { EventEmitter2 } from '@nestjs/event-emitter';
import { UnitOfWork } from '../../Tenancy/TenancyDB/UnitOfWork.service';
import { Branch } from '../models/Branch.model';
import { events } from '@/common/events/events';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class MarkBranchAsPrimaryService {
@@ -16,7 +17,7 @@ export class MarkBranchAsPrimaryService {
private readonly eventPublisher: EventEmitter2,
@Inject(Branch.name)
private readonly branchModel: typeof Branch,
private readonly branchModel: TenantModelProxy<typeof Branch>,
) {}
/**
@@ -26,7 +27,7 @@ export class MarkBranchAsPrimaryService {
*/
public async markAsPrimary(branchId: number): Promise<Branch> {
// Retrieves the old branch or throw not found service error.
const oldBranch = await this.branchModel
const oldBranch = await this.branchModel()
.query()
.findById(branchId)
.throwIfNotFound();
@@ -40,10 +41,10 @@ export class MarkBranchAsPrimaryService {
} as IBranchMarkAsPrimaryPayload);
// Updates all branches as not primary.
await this.branchModel.query(trx).update({ primary: false });
await this.branchModel().query(trx).update({ primary: false });
// Updates the given branch as primary.
const markedBranch = await this.branchModel
const markedBranch = await this.branchModel()
.query(trx)
.patchAndFetchById(branchId, {
primary: true,

View File

@@ -1,12 +1,13 @@
import { Inject } from '@nestjs/common';
import { Injectable } from '@nestjs/common';
import { Branch } from '../models/Branch.model';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class GetBranchService {
constructor(
@Inject(Branch.name)
private readonly branch: typeof Branch,
private readonly branch: TenantModelProxy<typeof Branch>,
) {}
/**
@@ -15,7 +16,7 @@ export class GetBranchService {
* @returns {Promise<IBranch>}
*/
public getBranch = async (branchId: number): Promise<Branch> => {
const branch = await this.branch
const branch = await this.branch()
.query()
.findById(branchId)
.throwIfNotFound();

View File

@@ -1,11 +1,12 @@
import { Inject, Injectable } from '@nestjs/common';
import { Branch } from '../models/Branch.model';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class GetBranchesService {
constructor(
@Inject(Branch.name)
private readonly branch: typeof Branch,
private readonly branch: TenantModelProxy<typeof Branch>,
) {}
/**
@@ -13,7 +14,7 @@ export class GetBranchesService {
* @returns
*/
public getBranches = async () => {
const branches = await this.branch.query().orderBy('name', 'DESC');
const branches = await this.branch().query().orderBy('name', 'DESC');
return branches;
};

View File

@@ -10,17 +10,20 @@ import {
getPdfFilesStorageDir,
} from './utils';
import { Document } from './models/Document';
import { TenantModelProxy } from '../System/models/TenantBaseModel';
@Injectable()
export class ChromiumlyHtmlConvert {
/**
* @param {typeof Document} documentModel - Document model.
* @param {TenantModelProxy<typeof Document>} documentModel - Document model.
*/
constructor(@Inject(Document.name) private documentModel: typeof Document) {}
constructor(
@Inject(Document.name)
private documentModel: TenantModelProxy<typeof Document>,
) {}
/**
* Write HTML content to temporary file.
* @param {number} tenantId - Tenant id.
* @param {string} content - HTML content.
* @returns {Promise<[string, () => Promise<void>]>}
*/
@@ -31,7 +34,7 @@ export class ChromiumlyHtmlConvert {
const filePath = getPdfFilePath(filename);
await fs.writeFile(filePath, content);
await this.documentModel
await this.documentModel()
.query()
.insert({ key: filename, mimeType: 'text/html' });

View File

@@ -12,6 +12,7 @@ import { RefundCreditNote } from '@/modules/CreditNoteRefunds/models/RefundCredi
import { CommandCreditNoteDTOTransform } from '@/modules/CreditNotes/commands/CommandCreditNoteDTOTransform.service';
import { CreditNote } from '@/modules/CreditNotes/models/CreditNote';
import { events } from '@/common/events/events';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class CreateRefundCreditNoteService {
@@ -19,9 +20,9 @@ export class CreateRefundCreditNoteService {
* @param {UnitOfWork} uow - The unit of work service.
* @param {EventEmitter2} eventPublisher - The event emitter service.
* @param {CommandCreditNoteDTOTransform} commandCreditNoteDTOTransform - The command credit note DTO transform service.
* @param {typeof RefundCreditNote} refundCreditNoteModel - The refund credit note model.
* @param {typeof Account} accountModel - The account model.
* @param {typeof CreditNote} creditNoteModel - The credit note model.
* @param {TenantModelProxy<typeof RefundCreditNote>} refundCreditNoteModel - The refund credit note model.
* @param {TenantModelProxy<typeof Account>} accountModel - The account model.
* @param {TenantModelProxy<typeof CreditNote>} creditNoteModel - The credit note model.
*/
constructor(
private uow: UnitOfWork,
@@ -29,10 +30,13 @@ export class CreateRefundCreditNoteService {
private commandCreditNoteDTOTransform: CommandCreditNoteDTOTransform,
@Inject(RefundCreditNote.name)
private refundCreditNoteModel: typeof RefundCreditNote,
private refundCreditNoteModel: TenantModelProxy<typeof RefundCreditNote>,
@Inject(Account.name) private accountModel: typeof Account,
@Inject(CreditNote.name) private creditNoteModel: typeof CreditNote,
@Inject(Account.name)
private accountModel: TenantModelProxy<typeof Account>,
@Inject(CreditNote.name)
private creditNoteModel: TenantModelProxy<typeof CreditNote>,
) {}
/**
@@ -46,13 +50,13 @@ export class CreateRefundCreditNoteService {
newCreditNoteDTO: ICreditNoteRefundDTO,
): Promise<RefundCreditNote> {
// Retrieve the credit note or throw not found service error.
const creditNote = await this.creditNoteModel
const creditNote = await this.creditNoteModel()
.query()
.findById(creditNoteId)
.throwIfNotFound();
// Retrieve the withdrawal account or throw not found service error.
const fromAccount = await this.accountModel
const fromAccount = await this.accountModel()
.query()
.findById(newCreditNoteDTO.fromAccountId)
.throwIfNotFound();
@@ -76,7 +80,7 @@ export class CreateRefundCreditNoteService {
} as IRefundCreditNoteCreatingPayload);
// Stores the refund credit note graph to the storage layer.
const refundCreditNote = await this.refundCreditNoteModel
const refundCreditNote = await this.refundCreditNoteModel()
.query(trx)
.insertAndFetch({
...this.transformDTOToModel(creditNote, newCreditNoteDTO),

View File

@@ -8,6 +8,7 @@ import {
import { RefundCreditNote } from '../models/RefundCreditNote';
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { events } from '@/common/events/events';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class DeleteRefundCreditNoteService {
@@ -21,7 +22,9 @@ export class DeleteRefundCreditNoteService {
private readonly eventPublisher: EventEmitter2,
@Inject(RefundCreditNote.name)
private readonly refundCreditNoteModel: typeof RefundCreditNote,
private readonly refundCreditNoteModel: TenantModelProxy<
typeof RefundCreditNote
>,
) {}
/**
@@ -31,7 +34,7 @@ export class DeleteRefundCreditNoteService {
*/
public deleteCreditNoteRefund = async (refundCreditId: number) => {
// Retrieve the old credit note or throw not found service error.
const oldRefundCredit = await this.refundCreditNoteModel
const oldRefundCredit = await this.refundCreditNoteModel()
.query()
.findById(refundCreditId)
.throwIfNotFound();
@@ -56,7 +59,7 @@ export class DeleteRefundCreditNoteService {
eventPayload,
);
// Deletes the refund credit note graph from the storage.
await this.refundCreditNoteModel
await this.refundCreditNoteModel()
.query(trx)
.findById(refundCreditId)
.delete();

View File

@@ -3,15 +3,18 @@ import { ERRORS } from '../../CreditNotes/constants';
import { RefundCreditNote } from '../models/RefundCreditNote';
import { ServiceError } from '@/modules/Items/ServiceError';
import { Account } from '@/modules/Accounts/models/Account.model';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class RefundCreditNoteService {
/**
* @param {typeof RefundCreditNote} refundCreditNoteModel - The refund credit note model.
* @param {TenantModelProxy<typeof RefundCreditNote>} refundCreditNoteModel - The refund credit note model.
*/
constructor(
@Inject(RefundCreditNote.name)
private readonly refundCreditNoteModel: typeof RefundCreditNote,
private readonly refundCreditNoteModel: TenantModelProxy<
typeof RefundCreditNote
>,
) {}
/**
@@ -22,7 +25,7 @@ export class RefundCreditNoteService {
public getCreditNoteRefundOrThrowError = async (
refundCreditId: number,
): Promise<RefundCreditNote> => {
const refundCreditNote = await this.refundCreditNoteModel
const refundCreditNote = await this.refundCreditNoteModel()
.query()
.findById(refundCreditId);
if (!refundCreditNote) {

View File

@@ -1,15 +1,16 @@
import { Knex } from 'knex';
import { Inject, Injectable } from '@nestjs/common';
import { CreditNote } from '@/modules/CreditNotes/models/CreditNote';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class RefundSyncCreditNoteBalanceService {
/**
* @param {typeof CreditNote} creditNoteModel - The credit note model.
* @param {TenantModelProxy<typeof CreditNote>} creditNoteModel - The credit note model.
*/
constructor(
@Inject(CreditNote.name)
private readonly creditNoteModel: typeof CreditNote,
private readonly creditNoteModel: TenantModelProxy<typeof CreditNote>,
) {}
/**
@@ -21,9 +22,9 @@ export class RefundSyncCreditNoteBalanceService {
public incrementCreditNoteRefundAmount = async (
creditNoteId: number,
amount: number,
trx?: Knex.Transaction
trx?: Knex.Transaction,
): Promise<void> => {
await this.creditNoteModel
await this.creditNoteModel()
.query(trx)
.findById(creditNoteId)
.increment('refunded_amount', amount);
@@ -38,9 +39,9 @@ export class RefundSyncCreditNoteBalanceService {
public decrementCreditNoteRefundAmount = async (
creditNoteId: number,
amount: number,
trx?: Knex.Transaction
trx?: Knex.Transaction,
): Promise<void> => {
await this.creditNoteModel
await this.creditNoteModel()
.query(trx)
.findById(creditNoteId)
.decrement('refunded_amount', amount);

View File

@@ -3,6 +3,7 @@ import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectab
import { RefundCreditNote } from '../models/RefundCreditNote';
import { RefundCreditNoteTransformer } from '@/modules/CreditNotes/queries/RefundCreditNoteTransformer';
import { IRefundCreditNotePOJO } from '../types/CreditNoteRefunds.types';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class ListCreditNoteRefunds {
@@ -10,7 +11,9 @@ export class ListCreditNoteRefunds {
private readonly transformer: TransformerInjectable,
@Inject(RefundCreditNote.name)
private readonly refundCreditNoteModel: typeof RefundCreditNote,
private readonly refundCreditNoteModel: TenantModelProxy<
typeof RefundCreditNote
>,
) {}
/**
@@ -22,7 +25,7 @@ export class ListCreditNoteRefunds {
creditNoteId: number,
): Promise<IRefundCreditNotePOJO[]> {
// Retrieve refund credit notes associated to the given credit note.
const refundCreditTransactions = await this.refundCreditNoteModel
const refundCreditTransactions = await this.refundCreditNoteModel()
.query()
.where('creditNoteId', creditNoteId)
.withGraphFetched('creditNote')

View File

@@ -1,6 +1,7 @@
import { Inject, Injectable } from '@nestjs/common';
import { RefundCreditNote } from '../models/RefundCreditNote';
import { RefundCreditNoteTransformer } from '../../CreditNotes/queries/RefundCreditNoteTransformer';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class GetRefundCreditNoteTransaction {
@@ -12,9 +13,10 @@ export class GetRefundCreditNoteTransaction {
private readonly transformer: RefundCreditNoteTransformer,
@Inject(RefundCreditNote.name)
private readonly refundCreditNoteModel: typeof RefundCreditNote,
) {
}
private readonly refundCreditNoteModel: TenantModelProxy<
typeof RefundCreditNote
>,
) {}
/**
* Retrieve credit note associated invoices to apply.
@@ -22,9 +24,9 @@ export class GetRefundCreditNoteTransaction {
* @returns {Promise<IRefundCreditNote>}
*/
public async getRefundCreditTransaction(
refundCreditId: number
refundCreditId: number,
): Promise<RefundCreditNote> {
const refundCreditNote = await this.refundCreditNoteModel
const refundCreditNote = await this.refundCreditNoteModel()
.query()
.findById(refundCreditId)
.withGraphFetched('fromAccount')

View File

@@ -12,6 +12,7 @@ import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { ItemsEntriesService } from '@/modules/Items/ItemsEntries.service';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { events } from '@/common/events/events';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class CreateCreditNoteService {
@@ -30,10 +31,10 @@ export class CreateCreditNoteService {
private readonly commandCreditNoteDTOTransform: CommandCreditNoteDTOTransform,
@Inject(CreditNote.name)
private readonly creditNoteModel: typeof CreditNote,
private readonly creditNoteModel: TenantModelProxy<typeof CreditNote>,
@Inject(Contact.name)
private readonly contactModel: typeof Contact,
private readonly contactModel: TenantModelProxy<typeof Contact>,
) {}
/**
@@ -49,7 +50,7 @@ export class CreateCreditNoteService {
creditNoteDTO,
});
// Validate customer existance.
const customer = await this.contactModel
const customer = await this.contactModel()
.query()
.modify('customer')
.findById(creditNoteDTO.customerId)
@@ -78,9 +79,11 @@ export class CreateCreditNoteService {
} as ICreditNoteCreatingPayload);
// Upsert the credit note graph.
const creditNote = await this.creditNoteModel.query(trx).upsertGraph({
...creditNoteModel,
});
const creditNote = await this.creditNoteModel()
.query(trx)
.upsertGraph({
...creditNoteModel,
});
// Triggers `onCreditNoteCreated` event.
await this.eventPublisher.emitAsync(events.creditNote.onCreated, {
creditNoteDTO,

View File

@@ -4,6 +4,7 @@ import { Inject, Injectable } from '@nestjs/common';
import { LedgerStorageService } from '@/modules/Ledger/LedgerStorage.service';
import { CreditNote } from '../models/CreditNote';
import { AccountRepository } from '@/modules/Accounts/repositories/Account.repository';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class CreditNoteGLEntries {
@@ -12,7 +13,7 @@ export class CreditNoteGLEntries {
private readonly accountRepository: AccountRepository,
@Inject(CreditNote.name)
private readonly creditNoteModel: typeof CreditNote,
private readonly creditNoteModel: TenantModelProxy<typeof CreditNote>,
) {}
/**

View File

@@ -7,14 +7,13 @@ import {
import { ERRORS } from '../constants';
import { CreditNote } from '../models/CreditNote';
import { CreditNoteAppliedInvoice } from '../../CreditNotesApplyInvoice/models/CreditNoteAppliedInvoice';
import {
RefundCreditNote as RefundCreditNoteModel,
} from '../../CreditNoteRefunds/models/RefundCreditNote';
import { RefundCreditNote as RefundCreditNoteModel } from '../../CreditNoteRefunds/models/RefundCreditNote';
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { ItemEntry } from '@/modules/TransactionItemEntry/models/ItemEntry';
import { ServiceError } from '@/modules/Items/ServiceError';
import { events } from '@/common/events/events';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class DeleteCreditNoteService {
@@ -31,16 +30,20 @@ export class DeleteCreditNoteService {
private readonly eventPublisher: EventEmitter2,
@Inject(CreditNote.name)
private readonly creditNoteModel: typeof CreditNote,
private readonly creditNoteModel: TenantModelProxy<typeof CreditNote>,
@Inject(ItemEntry.name)
private readonly itemEntryModel: typeof ItemEntry,
private readonly itemEntryModel: TenantModelProxy<typeof ItemEntry>,
@Inject(CreditNoteAppliedInvoice.name)
private readonly creditNoteAppliedInvoiceModel: typeof CreditNoteAppliedInvoice,
private readonly creditNoteAppliedInvoiceModel: TenantModelProxy<
typeof CreditNoteAppliedInvoice
>,
@Inject(RefundCreditNoteModel.name)
private readonly refundCreditNoteModel: typeof RefundCreditNoteModel,
private readonly refundCreditNoteModel: TenantModelProxy<
typeof RefundCreditNoteModel
>,
) {}
/**
@@ -50,7 +53,7 @@ export class DeleteCreditNoteService {
*/
public async deleteCreditNote(creditNoteId: number): Promise<void> {
// Retrieve the credit note or throw not found service error.
const oldCreditNote = await this.creditNoteModel
const oldCreditNote = await this.creditNoteModel()
.query()
.findById(creditNoteId)
.throwIfNotFound();
@@ -70,14 +73,14 @@ export class DeleteCreditNoteService {
} as ICreditNoteDeletingPayload);
// Deletes the associated credit note entries.
await this.itemEntryModel
await this.itemEntryModel()
.query(trx)
.where('reference_id', creditNoteId)
.where('reference_type', 'CreditNote')
.delete();
// Deletes the credit note transaction.
await this.creditNoteModel.query(trx).findById(creditNoteId).delete();
await this.creditNoteModel().query(trx).findById(creditNoteId).delete();
// Triggers `onCreditNoteDeleted` event.
await this.eventPublisher.emitAsync(events.creditNote.onDeleted, {
@@ -96,7 +99,7 @@ export class DeleteCreditNoteService {
private async validateCreditNoteHasNoRefundTransactions(
creditNoteId: number,
): Promise<void> {
const refundTransactions = await this.refundCreditNoteModel
const refundTransactions = await this.refundCreditNoteModel()
.query()
.where('creditNoteId', creditNoteId);
@@ -113,7 +116,7 @@ export class DeleteCreditNoteService {
private async validateCreditNoteHasNoApplyInvoiceTransactions(
creditNoteId: number,
): Promise<void> {
const appliedTransactions = await this.creditNoteAppliedInvoiceModel
const appliedTransactions = await this.creditNoteAppliedInvoiceModel()
.query()
.where('creditNoteId', creditNoteId);

View File

@@ -12,6 +12,7 @@ import { EventEmitter2 } from '@nestjs/event-emitter';
import { UnitOfWork } from '../../Tenancy/TenancyDB/UnitOfWork.service';
import { events } from '@/common/events/events';
import { CommandCreditNoteDTOTransform } from './CommandCreditNoteDTOTransform.service';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class EditCreditNoteService {
@@ -24,8 +25,11 @@ export class EditCreditNoteService {
* @param {UnitOfWork} uow - The unit of work.
*/
constructor(
@Inject(CreditNote.name) private creditNoteModel: typeof CreditNote,
@Inject(Contact.name) private contactModel: typeof Contact,
@Inject(CreditNote.name)
private creditNoteModel: TenantModelProxy<typeof CreditNote>,
@Inject(Contact.name)
private contactModel: TenantModelProxy<typeof Contact>,
private commandCreditNoteDTOTransform: CommandCreditNoteDTOTransform,
private itemsEntriesService: ItemsEntriesService,
private eventPublisher: EventEmitter2,
@@ -41,13 +45,13 @@ export class EditCreditNoteService {
creditNoteEditDTO: ICreditNoteEditDTO,
) {
// Retrieve the sale invoice or throw not found service error.
const oldCreditNote = await this.creditNoteModel
const oldCreditNote = await this.creditNoteModel()
.query()
.findById(creditNoteId)
.throwIfNotFound();
// Validate customer existance.
const customer = await this.contactModel
const customer = await this.contactModel()
.query()
.findById(creditNoteEditDTO.customerId);
@@ -82,10 +86,12 @@ export class EditCreditNoteService {
} as ICreditNoteEditingPayload);
// Saves the credit note graph to the storage.
const creditNote = await this.creditNoteModel.query(trx).upsertGraph({
id: creditNoteId,
...creditNoteModel,
});
const creditNote = await this.creditNoteModel()
.query(trx)
.upsertGraph({
id: creditNoteId,
...creditNoteModel,
});
// Triggers `onCreditNoteEdited` event.
await this.eventPublisher.emitAsync(events.creditNote.onEdited, {
trx,

View File

@@ -10,6 +10,7 @@ import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { CreditNote } from '../models/CreditNote';
import { events } from '@/common/events/events';
import { ServiceError } from '@/modules/Items/ServiceError';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class OpenCreditNoteService {
@@ -23,7 +24,7 @@ export class OpenCreditNoteService {
private readonly uow: UnitOfWork,
@Inject(CreditNote.name)
private readonly creditNoteModel: typeof CreditNote,
private readonly creditNoteModel: TenantModelProxy<typeof CreditNote>,
) {}
/**
@@ -33,7 +34,7 @@ export class OpenCreditNoteService {
*/
public openCreditNote = async (creditNoteId: number): Promise<CreditNote> => {
// Retrieve the sale invoice or throw not found service error.
const oldCreditNote = await this.creditNoteModel
const oldCreditNote = await this.creditNoteModel()
.query()
.findById(creditNoteId)
.throwIfNotFound();
@@ -59,7 +60,7 @@ export class OpenCreditNoteService {
eventPayload,
);
// Saves the credit note graph to the storage.
const creditNote = await this.creditNoteModel
const creditNote = await this.creditNoteModel()
.query(trx)
.updateAndFetchById(creditNoteId, {
openedAt: new Date(),

View File

@@ -4,6 +4,7 @@ import { CreditNoteTransformer } from './CreditNoteTransformer';
import { Inject, Injectable } from '@nestjs/common';
import { CreditNote } from '../models/CreditNote';
import { ServiceError } from '@/modules/Items/ServiceError';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class GetCreditNote {
@@ -11,18 +12,16 @@ export class GetCreditNote {
private readonly transformer: TransformerInjectable,
@Inject(CreditNote.name)
private readonly creditNoteModel: typeof CreditNote,
private readonly creditNoteModel: TenantModelProxy<typeof CreditNote>,
) {}
/**
* Retrieve the credit note graph.
* @param {number} tenantId
* @param {number} creditNoteId
* @returns
*/
public async getCreditNote(creditNoteId: number) {
// Retrieve the vendor credit model graph.
const creditNote = await this.creditNoteModel
const creditNote = await this.creditNoteModel()
.query()
.findById(creditNoteId)
.withGraphFetched('entries.item')

View File

@@ -9,6 +9,7 @@ import { EventEmitter2 } from '@nestjs/event-emitter';
import { PdfTemplateModel } from '@/modules/PdfTemplate/models/PdfTemplate';
import { CreditNotePdfTemplateAttributes } from '../types/CreditNotes.types';
import { events } from '@/common/events/events';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class GetCreditNotePdf {
@@ -29,10 +30,12 @@ export class GetCreditNotePdf {
private readonly eventPublisher: EventEmitter2,
@Inject(CreditNote.name)
private readonly creditNoteModel: typeof CreditNote,
private readonly creditNoteModel: TenantModelProxy<typeof CreditNote>,
@Inject(PdfTemplateModel.name)
private readonly pdfTemplateModel: typeof PdfTemplateModel,
private readonly pdfTemplateModel: TenantModelProxy<
typeof PdfTemplateModel
>,
) {}
/**
@@ -69,7 +72,7 @@ export class GetCreditNotePdf {
* @returns {Promise<string>}
*/
public async getCreditNoteFilename(creditNoteId: number): Promise<string> {
const creditNote = await this.creditNoteModel
const creditNote = await this.creditNoteModel()
.query()
.findById(creditNoteId);
return `Credit-${creditNote.creditNoteNumber}`;
@@ -90,7 +93,7 @@ export class GetCreditNotePdf {
const templateId =
creditNote.pdfTemplateId ??
(
await this.pdfTemplateModel.query().findOne({
await this.pdfTemplateModel().query().findOne({
resource: 'CreditNote',
default: true,
})

View File

@@ -1,12 +1,13 @@
import { Inject, Injectable } from '@nestjs/common';
import { ICreditNoteState } from '../types/CreditNotes.types';
import { PdfTemplateModel } from '@/modules/PdfTemplate/models/PdfTemplate';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class GetCreditNoteState {
constructor(
@Inject(PdfTemplateModel.name)
private pdfTemplateModel: typeof PdfTemplateModel,
private pdfTemplateModel: TenantModelProxy<typeof PdfTemplateModel>,
) {}
/**
@@ -14,7 +15,8 @@ export class GetCreditNoteState {
* @return {Promise<ICreditNoteState>}
*/
public async getCreditNoteState(): Promise<ICreditNoteState> {
const defaultPdfTemplate = await this.pdfTemplateModel.query()
const defaultPdfTemplate = await this.pdfTemplateModel()
.query()
.findOne({ resource: 'CreditNote' })
.modify('default');

View File

@@ -1,11 +1,15 @@
import * as R from 'ramda';
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
import { DynamicListService } from '@/modules/DynamicListing/DynamicList.service';
import { GetCreditNotesResponse, ICreditNotesQueryDTO } from '../types/CreditNotes.types';
import {
GetCreditNotesResponse,
ICreditNotesQueryDTO,
} from '../types/CreditNotes.types';
import { CreditNote } from '../models/CreditNote';
import { CreditNoteTransformer } from './CreditNoteTransformer';
import { Inject } from '@nestjs/common';
import { Injectable } from '@nestjs/common';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class GetCreditNotesService {
@@ -14,7 +18,7 @@ export class GetCreditNotesService {
private readonly transformer: TransformerInjectable,
@Inject(CreditNote.name)
private readonly creditNoteModel: typeof CreditNote,
private readonly creditNoteModel: TenantModelProxy<typeof CreditNote>,
) {}
/**
@@ -39,10 +43,10 @@ export class GetCreditNotesService {
// Dynamic list service.
const dynamicFilter = await this.dynamicListService.dynamicList(
this.creditNoteModel,
this.creditNoteModel(),
filter,
);
const { results, pagination } = await this.creditNoteModel
const { results, pagination } = await this.creditNoteModel()
.query()
.onBuild((builder) => {
builder.withGraphFetched('entries.item');

Some files were not shown because too many files have changed in this diff Show More