mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-19 06:10:31 +00:00
feat(nestjs): migrate to NestJS
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { omit, sumBy } from 'lodash';
|
||||
import * as moment from 'moment';
|
||||
import * as R from 'ramda';
|
||||
import * as composeAsync from 'async/compose';
|
||||
import { BranchTransactionDTOTransformer } from '@/modules/Branches/integrations/BranchTransactionDTOTransform';
|
||||
import { Expense } from '../models/Expense.model';
|
||||
import { assocItemEntriesDefaultIndex } from '@/utils/associate-item-entries-index';
|
||||
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
|
||||
import { CreateExpenseDto, EditExpenseDto } from '../dtos/Expense.dto';
|
||||
|
||||
@Injectable()
|
||||
export class ExpenseDTOTransformer {
|
||||
/**
|
||||
* @param {BranchTransactionDTOTransformer} branchDTOTransform - Branch transaction DTO transformer.
|
||||
* @param {TenancyContext} tenancyContext - Tenancy context.
|
||||
*/
|
||||
constructor(
|
||||
private readonly branchDTOTransform: BranchTransactionDTOTransformer,
|
||||
private readonly tenancyContext: TenancyContext,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Retrieve the expense landed cost amount.
|
||||
* @param {IExpenseDTO} expenseDTO
|
||||
* @return {number}
|
||||
*/
|
||||
private getExpenseLandedCostAmount = (
|
||||
expenseDTO: CreateExpenseDto | EditExpenseDto,
|
||||
): number => {
|
||||
const landedCostEntries = expenseDTO.categories.filter((entry) => {
|
||||
return entry.landedCost === true;
|
||||
});
|
||||
return this.getExpenseCategoriesTotal(landedCostEntries);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the given expense categories total.
|
||||
* @param {IExpenseCategory} categories
|
||||
* @returns {number}
|
||||
*/
|
||||
private getExpenseCategoriesTotal = (categories): number => {
|
||||
return sumBy(categories, 'amount');
|
||||
};
|
||||
|
||||
/**
|
||||
* Mapping expense DTO to model.
|
||||
* @param {IExpenseDTO} expenseDTO
|
||||
* @param {ISystemUser} authorizedUser
|
||||
* @return {IExpense}
|
||||
*/
|
||||
private async expenseDTOToModel(
|
||||
expenseDTO: CreateExpenseDto | EditExpenseDto,
|
||||
): Promise<Expense> {
|
||||
const landedCostAmount = this.getExpenseLandedCostAmount(expenseDTO);
|
||||
const totalAmount = this.getExpenseCategoriesTotal(expenseDTO.categories);
|
||||
|
||||
const categories = R.compose(
|
||||
// Associate the default index to categories lines.
|
||||
assocItemEntriesDefaultIndex,
|
||||
)(expenseDTO.categories || []);
|
||||
|
||||
const initialDTO = {
|
||||
...omit(expenseDTO, ['publish', 'attachments']),
|
||||
categories,
|
||||
totalAmount,
|
||||
landedCostAmount,
|
||||
paymentDate: moment(expenseDTO.paymentDate).toMySqlDateTime(),
|
||||
...(expenseDTO.publish
|
||||
? {
|
||||
publishedAt: moment().toMySqlDateTime(),
|
||||
}
|
||||
: {}),
|
||||
};
|
||||
const asyncDto = await composeAsync(
|
||||
this.branchDTOTransform.transformDTO<Expense>,
|
||||
)(initialDTO);
|
||||
|
||||
return asyncDto as Expense;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms the expense create DTO.
|
||||
* @param {IExpenseCreateDTO} expenseDTO
|
||||
* @returns {Promise<Expense>}
|
||||
*/
|
||||
public expenseCreateDTO = async (
|
||||
expenseDTO: CreateExpenseDto | EditExpenseDto,
|
||||
): Promise<Partial<Expense>> => {
|
||||
const initialDTO = await this.expenseDTOToModel(expenseDTO);
|
||||
const tenant = await this.tenancyContext.getTenant(true);
|
||||
|
||||
return {
|
||||
...initialDTO,
|
||||
currencyCode: expenseDTO.currencyCode || tenant?.metadata?.baseCurrency,
|
||||
exchangeRate: expenseDTO.exchangeRate || 1,
|
||||
// ...(user
|
||||
// ? {
|
||||
// userId: user.id,
|
||||
// }
|
||||
// : {}),
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Transformes the expense edit DTO.
|
||||
* @param {EditExpenseDto} expenseDTO
|
||||
* @returns {Promise<Expense>}
|
||||
*/
|
||||
public expenseEditDTO = async (
|
||||
expenseDTO: EditExpenseDto,
|
||||
): Promise<Expense> => {
|
||||
return this.expenseDTOToModel(expenseDTO);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
import { sumBy, difference } from 'lodash';
|
||||
import { ERRORS, SUPPORTED_EXPENSE_PAYMENT_ACCOUNT_TYPES } from '../constants';
|
||||
import { ACCOUNT_ROOT_TYPE } from '@/constants/accounts';
|
||||
import { Account } from '@/modules/Accounts/models/Account.model';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Expense } from '../models/Expense.model';
|
||||
import { ServiceError } from '@/modules/Items/ServiceError';
|
||||
import { CreateExpenseDto, EditExpenseDto } from '../dtos/Expense.dto';
|
||||
|
||||
@Injectable()
|
||||
export class CommandExpenseValidator {
|
||||
/**
|
||||
* Validates expense categories not equals zero.
|
||||
* @param {IExpenseCreateDTO | IExpenseEditDTO} expenseDTO
|
||||
* @throws {ServiceError}
|
||||
*/
|
||||
public validateCategoriesNotEqualZero = (
|
||||
expenseDTO: CreateExpenseDto | EditExpenseDto,
|
||||
) => {
|
||||
const totalAmount = sumBy(expenseDTO.categories, 'amount') || 0;
|
||||
|
||||
if (totalAmount <= 0) {
|
||||
throw new ServiceError(ERRORS.TOTAL_AMOUNT_EQUALS_ZERO);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve expense accounts or throw error in case one of the given accounts
|
||||
* not found not the storage.
|
||||
* @param {number} expenseAccountsIds
|
||||
* @throws {ServiceError}
|
||||
* @returns {Promise<IAccount[]>}
|
||||
*/
|
||||
public validateExpensesAccountsExistance(
|
||||
expenseAccounts: Account[],
|
||||
DTOAccountsIds: number[],
|
||||
) {
|
||||
const storedExpenseAccountsIds = expenseAccounts.map((a: Account) => a.id);
|
||||
const notStoredAccountsIds = difference(
|
||||
DTOAccountsIds,
|
||||
storedExpenseAccountsIds,
|
||||
);
|
||||
if (notStoredAccountsIds.length > 0) {
|
||||
throw new ServiceError(ERRORS.SOME_ACCOUNTS_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate expenses accounts type.
|
||||
* @param {Account[]} expensesAccounts
|
||||
* @throws {ServiceError}
|
||||
*/
|
||||
public validateExpensesAccountsType = (expensesAccounts: Account[]) => {
|
||||
const invalidExpenseAccounts: number[] = [];
|
||||
|
||||
expensesAccounts.forEach((expenseAccount) => {
|
||||
if (!expenseAccount.isRootType(ACCOUNT_ROOT_TYPE.EXPENSE)) {
|
||||
invalidExpenseAccounts.push(expenseAccount.id);
|
||||
}
|
||||
});
|
||||
if (invalidExpenseAccounts.length > 0) {
|
||||
throw new ServiceError(ERRORS.EXPENSES_ACCOUNT_HAS_INVALID_TYPE);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates payment account type in case has invalid type throws errors.
|
||||
* @param {Account} paymentAccount
|
||||
* @throws {ServiceError}
|
||||
*/
|
||||
public validatePaymentAccountType = (paymentAccount: Account) => {
|
||||
if (
|
||||
!paymentAccount.isAccountType(SUPPORTED_EXPENSE_PAYMENT_ACCOUNT_TYPES)
|
||||
) {
|
||||
throw new ServiceError(ERRORS.PAYMENT_ACCOUNT_HAS_INVALID_TYPE);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates the expense has not associated landed cost
|
||||
* references to the given expense.
|
||||
* @param {number} expenseId
|
||||
*/
|
||||
public async validateNoAssociatedLandedCost(expenseId: number) {
|
||||
// const { BillLandedCost } = this.tenancy.models(tenantId);
|
||||
// const associatedLandedCosts = await BillLandedCost.query()
|
||||
// .where('fromTransactionType', 'Expense')
|
||||
// .where('fromTransactionId', expenseId);
|
||||
// if (associatedLandedCosts.length > 0) {
|
||||
// throw new ServiceError(ERRORS.EXPENSE_HAS_ASSOCIATED_LANDED_COST);
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates expenses is not already published before.
|
||||
* @param {IExpense} expense
|
||||
*/
|
||||
public validateExpenseIsNotPublished(expense: Expense) {
|
||||
if (expense.publishedAt) {
|
||||
throw new ServiceError(ERRORS.EXPENSE_ALREADY_PUBLISHED);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Knex } from 'knex';
|
||||
import {
|
||||
IExpenseCreatedPayload,
|
||||
IExpenseCreatingPayload,
|
||||
} from '../interfaces/Expenses.interface';
|
||||
import { CommandExpenseValidator } from './CommandExpenseValidator.service';
|
||||
import { ExpenseDTOTransformer } from './CommandExpenseDTO.transformer';
|
||||
import { Account } from '@/modules/Accounts/models/Account.model';
|
||||
import { Expense } from '@/modules/Expenses/models/Expense.model';
|
||||
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';
|
||||
import { CreateExpenseDto } from '../dtos/Expense.dto';
|
||||
|
||||
@Injectable()
|
||||
export class CreateExpense {
|
||||
/**
|
||||
* @param {EventEmitter2} eventEmitter - Event emitter.
|
||||
* @param {UnitOfWork} uow - Unit of work.
|
||||
* @param {CommandExpenseValidator} validator - Command expense validator.
|
||||
* @param {ExpenseDTOTransformer} transformDTO - Expense DTO transformer.
|
||||
* @param {typeof Account} accountModel - Account model.
|
||||
* @param {typeof Expense} expenseModel - Expense model.
|
||||
*/
|
||||
constructor(
|
||||
private readonly eventEmitter: EventEmitter2,
|
||||
private readonly uow: UnitOfWork,
|
||||
private readonly validator: CommandExpenseValidator,
|
||||
private readonly transformDTO: ExpenseDTOTransformer,
|
||||
|
||||
@Inject(Account.name)
|
||||
private readonly accountModel: TenantModelProxy<typeof Account>,
|
||||
|
||||
@Inject(Expense.name)
|
||||
private readonly expenseModel: TenantModelProxy<typeof Expense>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Authorize before create a new expense transaction.
|
||||
* @param {IExpenseDTO} expenseDTO
|
||||
*/
|
||||
private authorize = async (expenseDTO: CreateExpenseDto) => {
|
||||
// Validate payment account existance on the storage.
|
||||
const paymentAccount = await this.accountModel()
|
||||
.query()
|
||||
.findById(expenseDTO.paymentAccountId)
|
||||
.throwIfNotFound();
|
||||
|
||||
// Retrieves the DTO expense accounts ids.
|
||||
const DTOExpenseAccountsIds = expenseDTO.categories.map(
|
||||
(category) => category.expenseAccountId,
|
||||
);
|
||||
// Retrieves the expenses accounts.
|
||||
const expenseAccounts = await this.accountModel()
|
||||
.query()
|
||||
.whereIn('id', DTOExpenseAccountsIds);
|
||||
// Validate expense accounts exist on the storage.
|
||||
this.validator.validateExpensesAccountsExistance(
|
||||
expenseAccounts,
|
||||
DTOExpenseAccountsIds,
|
||||
);
|
||||
// Validate payment account type.
|
||||
this.validator.validatePaymentAccountType(paymentAccount);
|
||||
|
||||
// Validate expenses accounts type.
|
||||
this.validator.validateExpensesAccountsType(expenseAccounts);
|
||||
|
||||
// Validate the given expense categories not equal zero.
|
||||
this.validator.validateCategoriesNotEqualZero(expenseDTO);
|
||||
};
|
||||
|
||||
/**
|
||||
* Precedures.
|
||||
* ---------
|
||||
* 1. Validate payment account existance on the storage.
|
||||
* 2. Validate expense accounts exist on the storage.
|
||||
* 3. Validate payment account type.
|
||||
* 4. Validate expenses accounts type.
|
||||
* 5. Validate the expense payee contact id existance on storage.
|
||||
* 6. Validate the given expense categories not equal zero.
|
||||
* 7. Stores the expense to the storage.
|
||||
* ---------
|
||||
* @param {number} tenantId
|
||||
* @param {IExpenseDTO} expenseDTO
|
||||
*/
|
||||
public newExpense = async (
|
||||
expenseDTO: CreateExpenseDto,
|
||||
trx?: Knex.Transaction,
|
||||
): Promise<Expense> => {
|
||||
// Authorize before create a new expense.
|
||||
await this.authorize(expenseDTO);
|
||||
|
||||
// Save the expense to the storage.
|
||||
const expenseObj = await this.transformDTO.expenseCreateDTO(expenseDTO);
|
||||
|
||||
// Writes the expense transaction with associated transactions under
|
||||
// unit-of-work envirement.
|
||||
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
|
||||
// Triggers `onExpenseCreating` event.
|
||||
await this.eventEmitter.emitAsync(events.expenses.onCreating, {
|
||||
trx,
|
||||
expenseDTO,
|
||||
} as IExpenseCreatingPayload);
|
||||
|
||||
// Creates a new expense transaction graph.
|
||||
const expense = await this.expenseModel()
|
||||
.query(trx)
|
||||
.upsertGraph(expenseObj);
|
||||
// Triggers `onExpenseCreated` event.
|
||||
await this.eventEmitter.emitAsync(events.expenses.onCreated, {
|
||||
expenseId: expense.id,
|
||||
expenseDTO,
|
||||
expense,
|
||||
trx,
|
||||
} as IExpenseCreatedPayload);
|
||||
|
||||
return expense;
|
||||
}, trx);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Knex } from 'knex';
|
||||
import { CommandExpenseValidator } from './CommandExpenseValidator.service';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
|
||||
import { Expense } from '../models/Expense.model';
|
||||
import { ExpenseCategory } from '../models/ExpenseCategory.model';
|
||||
import { events } from '@/common/events/events';
|
||||
import {
|
||||
IExpenseEventDeletePayload,
|
||||
IExpenseDeletingPayload,
|
||||
} from '../interfaces/Expenses.interface';
|
||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||
|
||||
@Injectable()
|
||||
export class DeleteExpense {
|
||||
/**
|
||||
* @param {EventEmitter2} eventEmitter - Event emitter.
|
||||
* @param {UnitOfWork} uow - Unit of work.
|
||||
* @param {CommandExpenseValidator} validator - Command expense validator.
|
||||
* @param {TenantModelProxy<typeof Expense>} expenseModel - Expense model.
|
||||
* @param {TenantModelProxy<typeof ExpenseCategory>} expenseCategoryModel - Expense category model.
|
||||
*/
|
||||
constructor(
|
||||
private readonly eventEmitter: EventEmitter2,
|
||||
private readonly uow: UnitOfWork,
|
||||
private readonly validator: CommandExpenseValidator,
|
||||
|
||||
@Inject(Expense.name)
|
||||
private expenseModel: TenantModelProxy<typeof Expense>,
|
||||
|
||||
@Inject(ExpenseCategory.name)
|
||||
private expenseCategoryModel: TenantModelProxy<typeof ExpenseCategory>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Deletes the given expense.
|
||||
* @param {number} expenseId
|
||||
* @param {ISystemUser} authorizedUser
|
||||
*/
|
||||
public async deleteExpense(expenseId: number): Promise<void> {
|
||||
// Retrieves the expense transaction with associated entries or
|
||||
// throw not found error.
|
||||
const oldExpense = await this.expenseModel()
|
||||
.query()
|
||||
.findById(expenseId)
|
||||
.withGraphFetched('categories')
|
||||
.throwIfNotFound();
|
||||
|
||||
// Validates the expense has no associated landed cost.
|
||||
await this.validator.validateNoAssociatedLandedCost(expenseId);
|
||||
|
||||
// Deletes expense transactions with associated transactions under
|
||||
// unit-of-work envirement.
|
||||
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
|
||||
// Triggers `onExpenseDeleting` event.
|
||||
await this.eventEmitter.emitAsync(events.expenses.onDeleting, {
|
||||
trx,
|
||||
oldExpense,
|
||||
} as IExpenseDeletingPayload);
|
||||
|
||||
// Deletes expense associated entries.
|
||||
await this.expenseCategoryModel()
|
||||
.query(trx)
|
||||
.where('expenseId', expenseId)
|
||||
.delete();
|
||||
|
||||
// Deletes expense transactions.
|
||||
await this.expenseModel().query(trx).findById(expenseId).delete();
|
||||
|
||||
// Triggers `onExpenseDeleted` event.
|
||||
await this.eventEmitter.emitAsync(events.expenses.onDeleted, {
|
||||
expenseId,
|
||||
oldExpense,
|
||||
trx,
|
||||
} as IExpenseEventDeletePayload);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
import { Injectable, Inject } from '@nestjs/common';
|
||||
import { Knex } from 'knex';
|
||||
import {
|
||||
IExpenseEventEditPayload,
|
||||
IExpenseEventEditingPayload,
|
||||
} from '../interfaces/Expenses.interface';
|
||||
import { CommandExpenseValidator } from './CommandExpenseValidator.service';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { ExpenseDTOTransformer } from './CommandExpenseDTO.transformer';
|
||||
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
|
||||
import { Account } from '@/modules/Accounts/models/Account.model';
|
||||
import { Expense } from '../models/Expense.model';
|
||||
import { events } from '@/common/events/events';
|
||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||
import { EditExpenseDto } from '../dtos/Expense.dto';
|
||||
|
||||
@Injectable()
|
||||
export class EditExpense {
|
||||
/**
|
||||
* @param {EventEmitter2} eventEmitter - Event emitter.
|
||||
* @param {UnitOfWork} uow - Unit of work.
|
||||
* @param {CommandExpenseValidator} validator - Command expense validator.
|
||||
* @param {ExpenseDTOTransformer} transformDTO - Expense DTO transformer.
|
||||
* @param {typeof Expense} expenseModel - Expense model.
|
||||
* @param {typeof Account} accountModel - Account model.
|
||||
*/
|
||||
constructor(
|
||||
private eventEmitter: EventEmitter2,
|
||||
private uow: UnitOfWork,
|
||||
private validator: CommandExpenseValidator,
|
||||
private transformDTO: ExpenseDTOTransformer,
|
||||
@Inject(Expense.name)
|
||||
private expenseModel: TenantModelProxy<typeof Expense>,
|
||||
|
||||
@Inject(Account.name)
|
||||
private accountModel: TenantModelProxy<typeof Account>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Authorize the DTO before editing expense transaction.
|
||||
* @param {EditExpenseDto} expenseDTO
|
||||
*/
|
||||
public authorize = async (
|
||||
oldExpense: Expense,
|
||||
expenseDTO: EditExpenseDto,
|
||||
) => {
|
||||
// Validate payment account existance on the storage.
|
||||
const paymentAccount = await this.accountModel()
|
||||
.query()
|
||||
.findById(expenseDTO.paymentAccountId)
|
||||
.throwIfNotFound();
|
||||
|
||||
// Retrieves the DTO expense accounts ids.
|
||||
const DTOExpenseAccountsIds = expenseDTO.categories.map(
|
||||
(category) => category.expenseAccountId,
|
||||
);
|
||||
// Retrieves the expenses accounts.
|
||||
const expenseAccounts = await this.accountModel()
|
||||
.query()
|
||||
.whereIn('id', DTOExpenseAccountsIds);
|
||||
// Validate expense accounts exist on the storage.
|
||||
this.validator.validateExpensesAccountsExistance(
|
||||
expenseAccounts,
|
||||
DTOExpenseAccountsIds,
|
||||
);
|
||||
// Validate payment account type.
|
||||
await this.validator.validatePaymentAccountType(paymentAccount);
|
||||
|
||||
// Validate expenses accounts type.
|
||||
await this.validator.validateExpensesAccountsType(expenseAccounts);
|
||||
// Validate the given expense categories not equal zero.
|
||||
this.validator.validateCategoriesNotEqualZero(expenseDTO);
|
||||
|
||||
// Validate expense entries that have allocated landed cost cannot be deleted.
|
||||
// this.entriesService.validateLandedCostEntriesNotDeleted(
|
||||
// oldExpense.categories,
|
||||
// expenseDTO.categories,
|
||||
// );
|
||||
// // Validate expense entries that have allocated cost amount should be bigger than amount.
|
||||
// this.entriesService.validateLocatedCostEntriesSmallerThanNewEntries(
|
||||
// oldExpense.categories,
|
||||
// expenseDTO.categories,
|
||||
// );
|
||||
};
|
||||
|
||||
/**
|
||||
* Precedures.
|
||||
* ---------
|
||||
* 1. Validate expense existance.
|
||||
* 2. Validate payment account existance on the storage.
|
||||
* 3. Validate expense accounts exist on the storage.
|
||||
* 4. Validate payment account type.
|
||||
* 5. Validate expenses accounts type.
|
||||
* 6. Validate the given expense categories not equal zero.
|
||||
* 7. Stores the expense to the storage.
|
||||
* ---------
|
||||
* @param {number} expenseId
|
||||
* @param {IExpenseDTO} expenseDTO
|
||||
* @param {ISystemUser} authorizedUser
|
||||
*/
|
||||
public async editExpense(
|
||||
expenseId: number,
|
||||
expenseDTO: EditExpenseDto,
|
||||
): Promise<Expense> {
|
||||
// Retrieves the expense model or throw not found error.
|
||||
const oldExpense = await this.expenseModel()
|
||||
.query()
|
||||
.findById(expenseId)
|
||||
.withGraphFetched('categories')
|
||||
.throwIfNotFound();
|
||||
|
||||
// Authorize expense DTO before editing.
|
||||
await this.authorize(oldExpense, expenseDTO);
|
||||
|
||||
// Update the expense on the storage.
|
||||
const expenseObj = await this.transformDTO.expenseEditDTO(expenseDTO);
|
||||
|
||||
// Edits expense transactions and associated transactions under UOW envirement.
|
||||
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
|
||||
// Triggers `onExpenseEditing` event.
|
||||
await this.eventEmitter.emitAsync(events.expenses.onEditing, {
|
||||
oldExpense,
|
||||
expenseDTO,
|
||||
trx,
|
||||
} as IExpenseEventEditingPayload);
|
||||
|
||||
// Upsert the expense object with expense entries.
|
||||
const expense = await this.expenseModel()
|
||||
.query(trx)
|
||||
.upsertGraphAndFetch({
|
||||
id: expenseId,
|
||||
...expenseObj,
|
||||
});
|
||||
|
||||
// Triggers `onExpenseCreated` event.
|
||||
await this.eventEmitter.emitAsync(events.expenses.onEdited, {
|
||||
expenseId,
|
||||
expense,
|
||||
expenseDTO,
|
||||
oldExpense,
|
||||
trx,
|
||||
} as IExpenseEventEditPayload);
|
||||
|
||||
return expense;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Knex } from 'knex';
|
||||
import {
|
||||
IExpensePublishingPayload,
|
||||
IExpenseEventPublishedPayload,
|
||||
} from '../interfaces/Expenses.interface';
|
||||
import { CommandExpenseValidator } from './CommandExpenseValidator.service';
|
||||
import { Expense } from '../models/Expense.model';
|
||||
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
|
||||
import { events } from '@/common/events/events';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||
|
||||
@Injectable()
|
||||
export class PublishExpense {
|
||||
/**
|
||||
* @param {EventEmitter2} eventPublisher - Event emitter.
|
||||
* @param {UnitOfWork} uow - Unit of work.
|
||||
* @param {CommandExpenseValidator} validator - Command expense validator.
|
||||
* @param {typeof Expense} expenseModel - Expense model.
|
||||
*/
|
||||
constructor(
|
||||
private readonly eventPublisher: EventEmitter2,
|
||||
private readonly uow: UnitOfWork,
|
||||
private readonly validator: CommandExpenseValidator,
|
||||
|
||||
@Inject(Expense.name)
|
||||
private readonly expenseModel: TenantModelProxy<typeof Expense>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Publish the given expense.
|
||||
* @param {number} expenseId
|
||||
* @param {ISystemUser} authorizedUser
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
public async publishExpense(expenseId: number) {
|
||||
// Retrieves the old expense or throw not found error.
|
||||
const oldExpense = await this.expenseModel()
|
||||
.query()
|
||||
.findById(expenseId)
|
||||
.throwIfNotFound();
|
||||
|
||||
// Validate the expense whether is published before.
|
||||
this.validator.validateExpenseIsNotPublished(oldExpense);
|
||||
|
||||
// Publishes expense transactions with associated transactions
|
||||
// under unit-of-work envirement.
|
||||
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
|
||||
// Trigggers `onExpensePublishing` event.
|
||||
await this.eventPublisher.emitAsync(events.expenses.onPublishing, {
|
||||
trx,
|
||||
oldExpense,
|
||||
} as IExpensePublishingPayload);
|
||||
|
||||
// Publish the given expense on the storage.
|
||||
await this.expenseModel().query().findById(expenseId).modify('publish');
|
||||
|
||||
// Retrieve the new expense after modification.
|
||||
const expense = await this.expenseModel()
|
||||
.query()
|
||||
.findById(expenseId)
|
||||
.withGraphFetched('categories');
|
||||
|
||||
// Triggers `onExpensePublished` event.
|
||||
await this.eventPublisher.emitAsync(events.expenses.onPublished, {
|
||||
expenseId,
|
||||
oldExpense,
|
||||
expense,
|
||||
trx,
|
||||
} as IExpenseEventPublishedPayload);
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user