mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-16 04:40:32 +00:00
add server to monorepo.
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import { sumBy, difference } from 'lodash';
|
||||
import { ServiceError } from '@/exceptions';
|
||||
import { ERRORS } from '../constants';
|
||||
import {
|
||||
IAccount,
|
||||
IExpense,
|
||||
IExpenseCreateDTO,
|
||||
IExpenseEditDTO,
|
||||
} from '@/interfaces';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { ACCOUNT_PARENT_TYPE, ACCOUNT_ROOT_TYPE } from '@/data/AccountTypes';
|
||||
|
||||
@Service()
|
||||
export class CommandExpenseValidator {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
/**
|
||||
* Validates expense categories not equals zero.
|
||||
* @param {IExpenseCreateDTO | IExpenseEditDTO} expenseDTO
|
||||
* @throws {ServiceError}
|
||||
*/
|
||||
public validateCategoriesNotEqualZero = (
|
||||
expenseDTO: IExpenseCreateDTO | IExpenseEditDTO
|
||||
) => {
|
||||
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} tenantId
|
||||
* @param {number} expenseAccountsIds
|
||||
* @throws {ServiceError}
|
||||
* @returns {Promise<IAccount[]>}
|
||||
*/
|
||||
public validateExpensesAccountsExistance(
|
||||
expenseAccounts: IAccount[],
|
||||
DTOAccountsIds: number[]
|
||||
) {
|
||||
const storedExpenseAccountsIds = expenseAccounts.map((a: IAccount) => a.id);
|
||||
|
||||
const notStoredAccountsIds = difference(
|
||||
DTOAccountsIds,
|
||||
storedExpenseAccountsIds
|
||||
);
|
||||
if (notStoredAccountsIds.length > 0) {
|
||||
throw new ServiceError(ERRORS.SOME_ACCOUNTS_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate expenses accounts type.
|
||||
* @param {number} tenantId
|
||||
* @param {number[]} expensesAccountsIds
|
||||
*/
|
||||
public validateExpensesAccountsType = (expensesAccounts: IAccount[]) => {
|
||||
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 {number} tenantId
|
||||
* @param {number} paymentAccountId
|
||||
* @throws {ServiceError}
|
||||
*/
|
||||
public validatePaymentAccountType = (paymentAccount: number[]) => {
|
||||
if (!paymentAccount.isParentType(ACCOUNT_PARENT_TYPE.CURRENT_ASSET)) {
|
||||
throw new ServiceError(ERRORS.PAYMENT_ACCOUNT_HAS_INVALID_TYPE);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates the expense has not associated landed cost
|
||||
* references to the given expense.
|
||||
* @param {number} tenantId
|
||||
* @param {number} expenseId
|
||||
*/
|
||||
public async validateNoAssociatedLandedCost(
|
||||
tenantId: number,
|
||||
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: IExpense) {
|
||||
if (expense.publishedAt) {
|
||||
throw new ServiceError(ERRORS.EXPENSE_ALREADY_PUBLISHED);
|
||||
}
|
||||
}
|
||||
}
|
||||
130
packages/server/src/services/Expenses/CRUD/CreateExpense.ts
Normal file
130
packages/server/src/services/Expenses/CRUD/CreateExpense.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import { Knex } from 'knex';
|
||||
import events from '@/subscribers/events';
|
||||
import {
|
||||
IExpense,
|
||||
IExpenseCreateDTO,
|
||||
ISystemUser,
|
||||
IExpenseCreatedPayload,
|
||||
IExpenseCreatingPayload,
|
||||
} from '@/interfaces';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import UnitOfWork from '@/services/UnitOfWork';
|
||||
import { CommandExpenseValidator } from './CommandExpenseValidator';
|
||||
import { ExpenseDTOTransformer } from './ExpenseDTOTransformer';
|
||||
|
||||
@Service()
|
||||
export class CreateExpense {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
@Inject()
|
||||
private uow: UnitOfWork;
|
||||
|
||||
@Inject()
|
||||
private validator: CommandExpenseValidator;
|
||||
|
||||
@Inject()
|
||||
private transformDTO: ExpenseDTOTransformer;
|
||||
|
||||
/**
|
||||
* Authorize before create a new expense transaction.
|
||||
* @param {number} tenantId
|
||||
* @param {IExpenseDTO} expenseDTO
|
||||
*/
|
||||
private authorize = async (
|
||||
tenantId: number,
|
||||
expenseDTO: IExpenseCreateDTO
|
||||
) => {
|
||||
const { Account } = await this.tenancy.models(tenantId);
|
||||
|
||||
// Validate payment account existance on the storage.
|
||||
const paymentAccount = await Account.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 Account.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 (
|
||||
tenantId: number,
|
||||
expenseDTO: IExpenseCreateDTO,
|
||||
authorizedUser: ISystemUser
|
||||
): Promise<IExpense> => {
|
||||
const { Expense } = await this.tenancy.models(tenantId);
|
||||
|
||||
// Authorize before create a new expense.
|
||||
await this.authorize(tenantId, expenseDTO);
|
||||
|
||||
// Save the expense to the storage.
|
||||
const expenseObj = await this.transformDTO.expenseCreateDTO(
|
||||
tenantId,
|
||||
expenseDTO,
|
||||
authorizedUser
|
||||
);
|
||||
// Writes the expense transaction with associated transactions under
|
||||
// unit-of-work envirement.
|
||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||
// Triggers `onExpenseCreating` event.
|
||||
await this.eventPublisher.emitAsync(events.expenses.onCreating, {
|
||||
trx,
|
||||
tenantId,
|
||||
expenseDTO,
|
||||
} as IExpenseCreatingPayload);
|
||||
|
||||
// Creates a new expense transaction graph.
|
||||
const expense: IExpense = await Expense.query(trx).upsertGraph(
|
||||
expenseObj
|
||||
);
|
||||
// Triggers `onExpenseCreated` event.
|
||||
await this.eventPublisher.emitAsync(events.expenses.onCreated, {
|
||||
tenantId,
|
||||
expenseId: expense.id,
|
||||
authorizedUser,
|
||||
expense,
|
||||
trx,
|
||||
} as IExpenseCreatedPayload);
|
||||
|
||||
return expense;
|
||||
});
|
||||
};
|
||||
}
|
||||
78
packages/server/src/services/Expenses/CRUD/DeleteExpense.ts
Normal file
78
packages/server/src/services/Expenses/CRUD/DeleteExpense.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import { Knex } from 'knex';
|
||||
import {
|
||||
ISystemUser,
|
||||
IExpenseEventDeletePayload,
|
||||
IExpenseDeletingPayload,
|
||||
} from '@/interfaces';
|
||||
import events from '@/subscribers/events';
|
||||
import UnitOfWork from '@/services/UnitOfWork';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import { CommandExpenseValidator } from './CommandExpenseValidator';
|
||||
import { ExpenseCategory } from 'models';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
|
||||
@Service()
|
||||
export class DeleteExpense {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
@Inject()
|
||||
private uow: UnitOfWork;
|
||||
|
||||
@Inject()
|
||||
private validator: CommandExpenseValidator;
|
||||
|
||||
/**
|
||||
* Deletes the given expense.
|
||||
* @param {number} tenantId
|
||||
* @param {number} expenseId
|
||||
* @param {ISystemUser} authorizedUser
|
||||
*/
|
||||
public deleteExpense = async (
|
||||
tenantId: number,
|
||||
expenseId: number,
|
||||
authorizedUser: ISystemUser
|
||||
): Promise<void> => {
|
||||
const { Expense } = this.tenancy.models(tenantId);
|
||||
|
||||
// Retrieves the expense transaction with associated entries or
|
||||
// throw not found error.
|
||||
const oldExpense = await Expense.query()
|
||||
.findById(expenseId)
|
||||
.withGraphFetched('categories')
|
||||
.throwIfNotFound();
|
||||
|
||||
// Validates the expense has no associated landed cost.
|
||||
await this.validator.validateNoAssociatedLandedCost(tenantId, expenseId);
|
||||
|
||||
// Deletes expense transactions with associated transactions under
|
||||
// unit-of-work envirement.
|
||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||
// Triggers `onExpenseDeleting` event.
|
||||
await this.eventPublisher.emitAsync(events.expenses.onDeleting, {
|
||||
trx,
|
||||
tenantId,
|
||||
oldExpense,
|
||||
} as IExpenseDeletingPayload);
|
||||
|
||||
// Deletes expense associated entries.
|
||||
await ExpenseCategory.query(trx).findById(expenseId).delete();
|
||||
|
||||
// Deletes expense transactions.
|
||||
await Expense.query(trx).findById(expenseId).delete();
|
||||
|
||||
// Triggers `onExpenseDeleted` event.
|
||||
await this.eventPublisher.emitAsync(events.expenses.onDeleted, {
|
||||
tenantId,
|
||||
expenseId,
|
||||
authorizedUser,
|
||||
oldExpense,
|
||||
trx,
|
||||
} as IExpenseEventDeletePayload);
|
||||
});
|
||||
};
|
||||
}
|
||||
157
packages/server/src/services/Expenses/CRUD/EditExpense.ts
Normal file
157
packages/server/src/services/Expenses/CRUD/EditExpense.ts
Normal file
@@ -0,0 +1,157 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import { Knex } from 'knex';
|
||||
import {
|
||||
IExpense,
|
||||
ISystemUser,
|
||||
IExpenseEventEditPayload,
|
||||
IExpenseEventEditingPayload,
|
||||
IExpenseEditDTO,
|
||||
} from '@/interfaces';
|
||||
import events from '@/subscribers/events';
|
||||
import { CommandExpenseValidator } from './CommandExpenseValidator';
|
||||
import UnitOfWork from '@/services/UnitOfWork';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { ExpenseDTOTransformer } from './ExpenseDTOTransformer';
|
||||
import EntriesService from '@/services/Entries';
|
||||
|
||||
@Service()
|
||||
export class EditExpense {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
@Inject()
|
||||
private uow: UnitOfWork;
|
||||
|
||||
@Inject()
|
||||
private validator: CommandExpenseValidator;
|
||||
|
||||
@Inject()
|
||||
private transformDTO: ExpenseDTOTransformer;
|
||||
|
||||
@Inject()
|
||||
private entriesService: EntriesService;
|
||||
|
||||
/**
|
||||
* Authorize the DTO before editing expense transaction.
|
||||
* @param {number} tenantId
|
||||
* @param {number} expenseId
|
||||
* @param {IExpenseEditDTO} expenseDTO
|
||||
*/
|
||||
public authorize = async (
|
||||
tenantId: number,
|
||||
oldExpense: IExpense,
|
||||
expenseDTO: IExpenseEditDTO
|
||||
) => {
|
||||
const { Account } = this.tenancy.models(tenantId);
|
||||
|
||||
// Validate payment account existance on the storage.
|
||||
const paymentAccount = await Account.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 Account.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} tenantId
|
||||
* @param {number} expenseId
|
||||
* @param {IExpenseDTO} expenseDTO
|
||||
* @param {ISystemUser} authorizedUser
|
||||
*/
|
||||
public async editExpense(
|
||||
tenantId: number,
|
||||
expenseId: number,
|
||||
expenseDTO: IExpenseEditDTO,
|
||||
authorizedUser: ISystemUser
|
||||
): Promise<IExpense> {
|
||||
const { Expense } = this.tenancy.models(tenantId);
|
||||
|
||||
// Retrieves the expense model or throw not found error.
|
||||
const oldExpense = await Expense.query()
|
||||
.findById(expenseId)
|
||||
.withGraphFetched('categories')
|
||||
.throwIfNotFound();
|
||||
|
||||
// Authorize expense DTO before editing.
|
||||
await this.authorize(tenantId, oldExpense, expenseDTO);
|
||||
|
||||
// Update the expense on the storage.
|
||||
const expenseObj = await this.transformDTO.expenseEditDTO(
|
||||
tenantId,
|
||||
expenseDTO
|
||||
);
|
||||
// Edits expense transactions and associated transactions under UOW envirement.
|
||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||
// Triggers `onExpenseEditing` event.
|
||||
await this.eventPublisher.emitAsync(events.expenses.onEditing, {
|
||||
tenantId,
|
||||
oldExpense,
|
||||
expenseDTO,
|
||||
trx,
|
||||
} as IExpenseEventEditingPayload);
|
||||
|
||||
// Upsert the expense object with expense entries.
|
||||
const expense: IExpense = await Expense.query(trx).upsertGraph({
|
||||
id: expenseId,
|
||||
...expenseObj,
|
||||
});
|
||||
// Triggers `onExpenseCreated` event.
|
||||
await this.eventPublisher.emitAsync(events.expenses.onEdited, {
|
||||
tenantId,
|
||||
expenseId,
|
||||
expense,
|
||||
expenseDTO,
|
||||
authorizedUser,
|
||||
oldExpense,
|
||||
trx,
|
||||
} as IExpenseEventEditPayload);
|
||||
|
||||
return expense;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import { omit, sumBy } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import * as R from 'ramda';
|
||||
import {
|
||||
IExpense,
|
||||
IExpenseCreateDTO,
|
||||
IExpenseDTO,
|
||||
IExpenseEditDTO,
|
||||
ISystemUser,
|
||||
} from '@/interfaces';
|
||||
import { BranchTransactionDTOTransform } from '@/services/Branches/Integrations/BranchTransactionDTOTransform';
|
||||
import { TenantMetadata } from '@/system/models';
|
||||
|
||||
@Service()
|
||||
export class ExpenseDTOTransformer {
|
||||
@Inject()
|
||||
private branchDTOTransform: BranchTransactionDTOTransform;
|
||||
|
||||
/**
|
||||
* Retrieve the expense landed cost amount.
|
||||
* @param {IExpenseDTO} expenseDTO
|
||||
* @return {number}
|
||||
*/
|
||||
private getExpenseLandedCostAmount = (expenseDTO: IExpenseDTO): 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 expenseDTOToModel(
|
||||
tenantId: number,
|
||||
expenseDTO: IExpenseCreateDTO | IExpenseEditDTO,
|
||||
user?: ISystemUser
|
||||
): IExpense {
|
||||
const landedCostAmount = this.getExpenseLandedCostAmount(expenseDTO);
|
||||
const totalAmount = this.getExpenseCategoriesTotal(expenseDTO.categories);
|
||||
|
||||
const initialDTO = {
|
||||
categories: [],
|
||||
...omit(expenseDTO, ['publish']),
|
||||
totalAmount,
|
||||
landedCostAmount,
|
||||
paymentDate: moment(expenseDTO.paymentDate).toMySqlDateTime(),
|
||||
...(expenseDTO.publish
|
||||
? {
|
||||
publishedAt: moment().toMySqlDateTime(),
|
||||
}
|
||||
: {}),
|
||||
};
|
||||
return R.compose(this.branchDTOTransform.transformDTO<IExpense>(tenantId))(
|
||||
initialDTO
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transformes the expense create DTO.
|
||||
* @param {number} tenantId
|
||||
* @param {IExpenseCreateDTO} expenseDTO
|
||||
* @param {ISystemUser} user
|
||||
* @returns {IExpense}
|
||||
*/
|
||||
public expenseCreateDTO = async (
|
||||
tenantId: number,
|
||||
expenseDTO: IExpenseCreateDTO,
|
||||
user?: ISystemUser
|
||||
): Promise<IExpense> => {
|
||||
const initialDTO = this.expenseDTOToModel(tenantId, expenseDTO, user);
|
||||
|
||||
// Retrieves the tenant metadata.
|
||||
const tenantMetadata = await TenantMetadata.query().findOne({ tenantId });
|
||||
|
||||
return {
|
||||
...initialDTO,
|
||||
currencyCode: expenseDTO.currencyCode || tenantMetadata?.baseCurrency,
|
||||
exchangeRate: expenseDTO.exchangeRate || 1,
|
||||
...(user
|
||||
? {
|
||||
userId: user.id,
|
||||
}
|
||||
: {}),
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Transformes the expense edit DTO.
|
||||
* @param {number} tenantId
|
||||
* @param {IExpenseEditDTO} expenseDTO
|
||||
* @param {ISystemUser} user
|
||||
* @returns {IExpense}
|
||||
*/
|
||||
public expenseEditDTO = async (
|
||||
tenantId: number,
|
||||
expenseDTO: IExpenseEditDTO,
|
||||
user?: ISystemUser
|
||||
): Promise<IExpense> => {
|
||||
return this.expenseDTOToModel(tenantId, expenseDTO, user);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import { Transformer } from '@/lib/Transformer/Transformer';
|
||||
import { formatNumber } from 'utils';
|
||||
import { IExpense } from '@/interfaces';
|
||||
|
||||
export class ExpenseTransfromer extends Transformer {
|
||||
/**
|
||||
* Include these attributes to expense object.
|
||||
* @returns {Array}
|
||||
*/
|
||||
public includeAttributes = (): string[] => {
|
||||
return [
|
||||
'formattedAmount',
|
||||
'formattedLandedCostAmount',
|
||||
'formattedAllocatedCostAmount',
|
||||
'formattedDate'
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve formatted expense amount.
|
||||
* @param {IExpense} expense
|
||||
* @returns {string}
|
||||
*/
|
||||
protected formattedAmount = (expense: IExpense): string => {
|
||||
return formatNumber(expense.totalAmount, {
|
||||
currencyCode: expense.currencyCode,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve formatted expense landed cost amount.
|
||||
* @param {IExpense} expense
|
||||
* @returns {string}
|
||||
*/
|
||||
protected formattedLandedCostAmount = (expense: IExpense): string => {
|
||||
return formatNumber(expense.landedCostAmount, {
|
||||
currencyCode: expense.currencyCode,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve formatted allocated cost amount.
|
||||
* @param {IExpense} expense
|
||||
* @returns {string}
|
||||
*/
|
||||
protected formattedAllocatedCostAmount = (expense: IExpense): string => {
|
||||
return formatNumber(expense.allocatedCostAmount, {
|
||||
currencyCode: expense.currencyCode,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Retriecve fromatted date.
|
||||
* @param {IExpense} expense
|
||||
* @returns {string}
|
||||
*/
|
||||
protected formattedDate = (expense: IExpense): string => {
|
||||
return this.formatDate(expense.paymentDate);
|
||||
}
|
||||
}
|
||||
41
packages/server/src/services/Expenses/CRUD/GetExpense.ts
Normal file
41
packages/server/src/services/Expenses/CRUD/GetExpense.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { IExpense } from '@/interfaces';
|
||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { Service, Inject } from 'typedi';
|
||||
import { ExpenseTransfromer } from './ExpenseTransformer';
|
||||
|
||||
@Service()
|
||||
export class GetExpense {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private transformer: TransformerInjectable;
|
||||
|
||||
/**
|
||||
* Retrieve expense details.
|
||||
* @param {number} tenantId
|
||||
* @param {number} expenseId
|
||||
* @return {Promise<IExpense>}
|
||||
*/
|
||||
public async getExpense(
|
||||
tenantId: number,
|
||||
expenseId: number
|
||||
): Promise<IExpense> {
|
||||
const { Expense } = this.tenancy.models(tenantId);
|
||||
|
||||
const expense = await Expense.query()
|
||||
.findById(expenseId)
|
||||
.withGraphFetched('categories.expenseAccount')
|
||||
.withGraphFetched('paymentAccount')
|
||||
.withGraphFetched('branch')
|
||||
.throwIfNotFound();
|
||||
|
||||
// Transformes expense model to POJO.
|
||||
return this.transformer.transform(
|
||||
tenantId,
|
||||
expense,
|
||||
new ExpenseTransfromer()
|
||||
);
|
||||
}
|
||||
}
|
||||
80
packages/server/src/services/Expenses/CRUD/GetExpenses.ts
Normal file
80
packages/server/src/services/Expenses/CRUD/GetExpenses.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import * as R from 'ramda';
|
||||
import {
|
||||
IExpensesFilter,
|
||||
IExpense,
|
||||
IPaginationMeta,
|
||||
IFilterMeta,
|
||||
} from '@/interfaces';
|
||||
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { ExpenseTransfromer } from './ExpenseTransformer';
|
||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||
|
||||
@Service()
|
||||
export class GetExpenses {
|
||||
@Inject()
|
||||
private dynamicListService: DynamicListingService;
|
||||
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private transformer: TransformerInjectable;
|
||||
|
||||
/**
|
||||
* Retrieve expenses paginated list.
|
||||
* @param {number} tenantId
|
||||
* @param {IExpensesFilter} expensesFilter
|
||||
* @return {IExpense[]}
|
||||
*/
|
||||
public getExpensesList = async (
|
||||
tenantId: number,
|
||||
filterDTO: IExpensesFilter
|
||||
): Promise<{
|
||||
expenses: IExpense[];
|
||||
pagination: IPaginationMeta;
|
||||
filterMeta: IFilterMeta;
|
||||
}> => {
|
||||
const { Expense } = this.tenancy.models(tenantId);
|
||||
|
||||
// Parses list filter DTO.
|
||||
const filter = this.parseListFilterDTO(filterDTO);
|
||||
|
||||
// Dynamic list service.
|
||||
const dynamicList = await this.dynamicListService.dynamicList(
|
||||
tenantId,
|
||||
Expense,
|
||||
filter
|
||||
);
|
||||
// Retrieves the paginated results.
|
||||
const { results, pagination } = await Expense.query()
|
||||
.onBuild((builder) => {
|
||||
builder.withGraphFetched('paymentAccount');
|
||||
builder.withGraphFetched('categories.expenseAccount');
|
||||
|
||||
dynamicList.buildQuery()(builder);
|
||||
})
|
||||
.pagination(filter.page - 1, filter.pageSize);
|
||||
|
||||
// Transformes the expenses models to POJO.
|
||||
const expenses = await this.transformer.transform(
|
||||
tenantId,
|
||||
results,
|
||||
new ExpenseTransfromer()
|
||||
);
|
||||
return {
|
||||
expenses,
|
||||
pagination,
|
||||
filterMeta: dynamicList.getResponseMeta(),
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Parses filter DTO of expenses list.
|
||||
* @param filterDTO -
|
||||
*/
|
||||
private parseListFilterDTO(filterDTO) {
|
||||
return R.compose(this.dynamicListService.parseStringifiedFilter)(filterDTO);
|
||||
}
|
||||
}
|
||||
79
packages/server/src/services/Expenses/CRUD/PublishExpense.ts
Normal file
79
packages/server/src/services/Expenses/CRUD/PublishExpense.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import { Knex } from 'knex';
|
||||
import {
|
||||
ISystemUser,
|
||||
IExpensePublishingPayload,
|
||||
IExpenseEventPublishedPayload,
|
||||
} from '@/interfaces';
|
||||
import events from '@/subscribers/events';
|
||||
import { CommandExpenseValidator } from './CommandExpenseValidator';
|
||||
import UnitOfWork from '@/services/UnitOfWork';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
|
||||
@Inject()
|
||||
export class PublishExpense {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
@Inject()
|
||||
private uow: UnitOfWork;
|
||||
|
||||
@Inject()
|
||||
private validator: CommandExpenseValidator;
|
||||
|
||||
/**
|
||||
* Publish the given expense.
|
||||
* @param {number} tenantId
|
||||
* @param {number} expenseId
|
||||
* @param {ISystemUser} authorizedUser
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
public async publishExpense(
|
||||
tenantId: number,
|
||||
expenseId: number,
|
||||
authorizedUser: ISystemUser
|
||||
) {
|
||||
const { Expense } = this.tenancy.models(tenantId);
|
||||
|
||||
// Retrieves the old expense or throw not found error.
|
||||
const oldExpense = await Expense.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(tenantId, async (trx: Knex.Transaction) => {
|
||||
// Trigggers `onExpensePublishing` event.
|
||||
await this.eventPublisher.emitAsync(events.expenses.onPublishing, {
|
||||
trx,
|
||||
oldExpense,
|
||||
tenantId,
|
||||
} as IExpensePublishingPayload);
|
||||
|
||||
// Publish the given expense on the storage.
|
||||
await Expense.query().findById(expenseId).modify('publish');
|
||||
|
||||
// Retrieve the new expense after modification.
|
||||
const expense = await Expense.query()
|
||||
.findById(expenseId)
|
||||
.withGraphFetched('categories');
|
||||
|
||||
// Triggers `onExpensePublished` event.
|
||||
await this.eventPublisher.emitAsync(events.expenses.onPublished, {
|
||||
tenantId,
|
||||
expenseId,
|
||||
oldExpense,
|
||||
expense,
|
||||
authorizedUser,
|
||||
trx,
|
||||
} as IExpenseEventPublishedPayload);
|
||||
});
|
||||
}
|
||||
}
|
||||
106
packages/server/src/services/Expenses/ExpenseGLEntries.ts
Normal file
106
packages/server/src/services/Expenses/ExpenseGLEntries.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import * as R from 'ramda';
|
||||
import { Service } from 'typedi';
|
||||
import {
|
||||
AccountNormal,
|
||||
IExpense,
|
||||
IExpenseCategory,
|
||||
ILedger,
|
||||
ILedgerEntry,
|
||||
} from '@/interfaces';
|
||||
import Ledger from '@/services/Accounting/Ledger';
|
||||
|
||||
@Service()
|
||||
export class ExpenseGLEntries {
|
||||
/**
|
||||
* Retrieves the expense GL common entry.
|
||||
* @param {IExpense} expense
|
||||
* @returns
|
||||
*/
|
||||
private getExpenseGLCommonEntry = (expense: IExpense) => {
|
||||
return {
|
||||
currencyCode: expense.currencyCode,
|
||||
exchangeRate: expense.exchangeRate,
|
||||
|
||||
transactionType: 'Expense',
|
||||
transactionId: expense.id,
|
||||
|
||||
date: expense.paymentDate,
|
||||
userId: expense.userId,
|
||||
|
||||
debit: 0,
|
||||
credit: 0,
|
||||
|
||||
branchId: expense.branchId,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the expense GL payment entry.
|
||||
* @param {IExpense} expense
|
||||
* @returns {ILedgerEntry}
|
||||
*/
|
||||
private getExpenseGLPaymentEntry = (expense: IExpense): ILedgerEntry => {
|
||||
const commonEntry = this.getExpenseGLCommonEntry(expense);
|
||||
|
||||
return {
|
||||
...commonEntry,
|
||||
credit: expense.localAmount,
|
||||
accountId: expense.paymentAccountId,
|
||||
accountNormal: AccountNormal.CREDIT,
|
||||
index: 1,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the expense GL category entry.
|
||||
* @param {IExpense} expense -
|
||||
* @param {IExpenseCategory} expenseCategory -
|
||||
* @param {number} index
|
||||
* @returns {ILedgerEntry}
|
||||
*/
|
||||
private getExpenseGLCategoryEntry = R.curry(
|
||||
(
|
||||
expense: IExpense,
|
||||
category: IExpenseCategory,
|
||||
index: number
|
||||
): ILedgerEntry => {
|
||||
const commonEntry = this.getExpenseGLCommonEntry(expense);
|
||||
const localAmount = category.amount * expense.exchangeRate;
|
||||
|
||||
return {
|
||||
...commonEntry,
|
||||
accountId: category.expenseAccountId,
|
||||
accountNormal: AccountNormal.DEBIT,
|
||||
debit: localAmount,
|
||||
note: category.description,
|
||||
index: index + 2,
|
||||
projectId: category.projectId,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Retrieves the expense GL entries.
|
||||
* @param {IExpense} expense
|
||||
* @returns {ILedgerEntry[]}
|
||||
*/
|
||||
public getExpenseGLEntries = (expense: IExpense): ILedgerEntry[] => {
|
||||
const getCategoryEntry = this.getExpenseGLCategoryEntry(expense);
|
||||
|
||||
const paymentEntry = this.getExpenseGLPaymentEntry(expense);
|
||||
const categoryEntries = expense.categories.map(getCategoryEntry);
|
||||
|
||||
return [paymentEntry, ...categoryEntries];
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the given expense ledger.
|
||||
* @param {IExpense} expense
|
||||
* @returns {ILedger}
|
||||
*/
|
||||
public getExpenseLedger = (expense: IExpense): ILedger => {
|
||||
const entries = this.getExpenseGLEntries(expense);
|
||||
|
||||
return new Ledger(entries);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
import { Knex } from 'knex';
|
||||
import LedgerStorageService from '@/services/Accounting/LedgerStorageService';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { Service, Inject } from 'typedi';
|
||||
import { ExpenseGLEntries } from './ExpenseGLEntries';
|
||||
|
||||
@Service()
|
||||
export class ExpenseGLEntriesStorage {
|
||||
@Inject()
|
||||
private expenseGLEntries: ExpenseGLEntries;
|
||||
|
||||
@Inject()
|
||||
private ledgerStorage: LedgerStorageService;
|
||||
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
/**
|
||||
* Writes the expense GL entries.
|
||||
* @param {number} tenantId
|
||||
* @param {number} expenseId
|
||||
* @param {Knex.Transaction} trx
|
||||
*/
|
||||
public writeExpenseGLEntries = async (
|
||||
tenantId: number,
|
||||
expenseId: number,
|
||||
trx?: Knex.Transaction
|
||||
) => {
|
||||
const { Expense } = await this.tenancy.models(tenantId);
|
||||
|
||||
const expense = await Expense.query(trx)
|
||||
.findById(expenseId)
|
||||
.withGraphFetched('categories');
|
||||
|
||||
// Retrieves the given expense ledger.
|
||||
const expenseLedger = this.expenseGLEntries.getExpenseLedger(expense);
|
||||
|
||||
// Commits the expense ledger entries.
|
||||
await this.ledgerStorage.commit(tenantId, expenseLedger, trx);
|
||||
};
|
||||
|
||||
/**
|
||||
* Reverts the given expense GL entries.
|
||||
* @param {number} tenantId
|
||||
* @param {number} expenseId
|
||||
* @param {Knex.Transaction} trx
|
||||
*/
|
||||
public revertExpenseGLEntries = async (
|
||||
tenantId: number,
|
||||
expenseId: number,
|
||||
trx?: Knex.Transaction
|
||||
) => {
|
||||
await this.ledgerStorage.deleteByReference(
|
||||
tenantId,
|
||||
expenseId,
|
||||
'Expense',
|
||||
trx
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Rewrites the expense GL entries.
|
||||
* @param {number} tenantId
|
||||
* @param {number} expenseId
|
||||
* @param {Knex.Transaction} trx
|
||||
*/
|
||||
public rewriteExpenseGLEntries = async (
|
||||
tenantId: number,
|
||||
expenseId: number,
|
||||
trx?: Knex.Transaction
|
||||
) => {
|
||||
// Reverts the expense GL entries.
|
||||
await this.revertExpenseGLEntries(tenantId, expenseId, trx);
|
||||
|
||||
// Writes the expense GL entries.
|
||||
await this.writeExpenseGLEntries(tenantId, expenseId, trx);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import events from '@/subscribers/events';
|
||||
import TenancyService from '@/services/Tenancy/TenancyService';
|
||||
import {
|
||||
IExpenseCreatedPayload,
|
||||
IExpenseEventDeletePayload,
|
||||
IExpenseEventEditPayload,
|
||||
IExpenseEventPublishedPayload,
|
||||
} from '@/interfaces';
|
||||
import { ExpenseGLEntriesStorage } from './ExpenseGLEntriesStorage';
|
||||
|
||||
@Service()
|
||||
export class ExpensesWriteGLSubscriber {
|
||||
@Inject()
|
||||
private tenancy: TenancyService;
|
||||
|
||||
@Inject()
|
||||
private expenseGLEntries: ExpenseGLEntriesStorage;
|
||||
|
||||
/**
|
||||
* Attaches events with handlers.
|
||||
* @param bus
|
||||
*/
|
||||
attach(bus) {
|
||||
bus.subscribe(
|
||||
events.expenses.onCreated,
|
||||
this.handleWriteGLEntriesOnceCreated
|
||||
);
|
||||
bus.subscribe(
|
||||
events.expenses.onEdited,
|
||||
this.handleRewriteGLEntriesOnceEdited
|
||||
);
|
||||
bus.subscribe(
|
||||
events.expenses.onDeleted,
|
||||
this.handleRevertGLEntriesOnceDeleted
|
||||
);
|
||||
bus.subscribe(
|
||||
events.expenses.onPublished,
|
||||
this.handleWriteGLEntriesOncePublished
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the writing journal entries once the expense created.
|
||||
* @param {IExpenseCreatedPayload} payload -
|
||||
*/
|
||||
public handleWriteGLEntriesOnceCreated = async ({
|
||||
expense,
|
||||
tenantId,
|
||||
trx,
|
||||
}: IExpenseCreatedPayload) => {
|
||||
// In case expense published, write journal entries.
|
||||
if (!expense.publishedAt) return;
|
||||
|
||||
await this.expenseGLEntries.writeExpenseGLEntries(
|
||||
tenantId,
|
||||
expense.id,
|
||||
trx
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle writing expense journal entries once the expense edited.
|
||||
* @param {IExpenseEventEditPayload} payload -
|
||||
*/
|
||||
public handleRewriteGLEntriesOnceEdited = async ({
|
||||
expenseId,
|
||||
tenantId,
|
||||
expense,
|
||||
authorizedUser,
|
||||
trx,
|
||||
}: IExpenseEventEditPayload) => {
|
||||
// In case expense published, write journal entries.
|
||||
if (expense.publishedAt) return;
|
||||
|
||||
await this.expenseGLEntries.writeExpenseGLEntries(
|
||||
tenantId,
|
||||
expense.id,
|
||||
trx
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Reverts expense journal entries once the expense deleted.
|
||||
* @param {IExpenseEventDeletePayload} payload -
|
||||
*/
|
||||
public handleRevertGLEntriesOnceDeleted = async ({
|
||||
expenseId,
|
||||
tenantId,
|
||||
trx,
|
||||
}: IExpenseEventDeletePayload) => {
|
||||
await this.expenseGLEntries.revertExpenseGLEntries(
|
||||
tenantId,
|
||||
expenseId,
|
||||
trx
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles writing expense journal once the expense publish.
|
||||
* @param {IExpenseEventPublishedPayload} payload -
|
||||
*/
|
||||
public handleWriteGLEntriesOncePublished = async ({
|
||||
tenantId,
|
||||
expense,
|
||||
trx,
|
||||
}: IExpenseEventPublishedPayload) => {
|
||||
// In case expense published, write journal entries.
|
||||
if (!expense.publishedAt) return;
|
||||
|
||||
await this.expenseGLEntries.rewriteExpenseGLEntries(
|
||||
tenantId,
|
||||
expense.id,
|
||||
trx
|
||||
);
|
||||
};
|
||||
}
|
||||
132
packages/server/src/services/Expenses/ExpensesApplication.ts
Normal file
132
packages/server/src/services/Expenses/ExpensesApplication.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
import {
|
||||
IExpense,
|
||||
IExpenseCreateDTO,
|
||||
IExpenseEditDTO,
|
||||
IExpensesFilter,
|
||||
ISystemUser,
|
||||
} from '@/interfaces';
|
||||
import { Service, Inject } from 'typedi';
|
||||
import { CreateExpense } from './CRUD/CreateExpense';
|
||||
import { DeleteExpense } from './CRUD/DeleteExpense';
|
||||
import { EditExpense } from './CRUD/EditExpense';
|
||||
import { GetExpense } from './CRUD/GetExpense';
|
||||
import { GetExpenses } from './CRUD/GetExpenses';
|
||||
import { PublishExpense } from './CRUD/PublishExpense';
|
||||
|
||||
@Service()
|
||||
export class ExpensesApplication {
|
||||
@Inject()
|
||||
private createExpenseService: CreateExpense;
|
||||
|
||||
@Inject()
|
||||
private editExpenseService: EditExpense;
|
||||
|
||||
@Inject()
|
||||
private deleteExpenseService: DeleteExpense;
|
||||
|
||||
@Inject()
|
||||
private publishExpenseService: PublishExpense;
|
||||
|
||||
@Inject()
|
||||
private getExpenseService: GetExpense;
|
||||
|
||||
@Inject()
|
||||
private getExpensesService: GetExpenses;
|
||||
|
||||
/**
|
||||
* Create a new expense transaction.
|
||||
* @param {number} tenantId
|
||||
* @param {IExpenseDTO} expenseDTO
|
||||
* @param {ISystemUser} authorizedUser
|
||||
* @returns {Promise<IExpense>}
|
||||
*/
|
||||
public createExpense = (
|
||||
tenantId: number,
|
||||
expenseDTO: IExpenseCreateDTO,
|
||||
authorizedUser: ISystemUser
|
||||
): Promise<IExpense> => {
|
||||
return this.createExpenseService.newExpense(
|
||||
tenantId,
|
||||
expenseDTO,
|
||||
authorizedUser
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Edits the given expense transaction.
|
||||
* @param {number} tenantId
|
||||
* @param {number} expenseId
|
||||
* @param {IExpenseDTO} expenseDTO
|
||||
* @param {ISystemUser} authorizedUser
|
||||
*/
|
||||
public editExpense = (
|
||||
tenantId: number,
|
||||
expenseId: number,
|
||||
expenseDTO: IExpenseEditDTO,
|
||||
authorizedUser: ISystemUser
|
||||
) => {
|
||||
return this.editExpenseService.editExpense(
|
||||
tenantId,
|
||||
expenseId,
|
||||
expenseDTO,
|
||||
authorizedUser
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Deletes the given expense.
|
||||
* @param {number} tenantId
|
||||
* @param {number} expenseId
|
||||
* @param {ISystemUser} authorizedUser
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public deleteExpense = (
|
||||
tenantId: number,
|
||||
expenseId: number,
|
||||
authorizedUser: ISystemUser
|
||||
) => {
|
||||
return this.deleteExpenseService.deleteExpense(
|
||||
tenantId,
|
||||
expenseId,
|
||||
authorizedUser
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Publishes the given expense.
|
||||
* @param {number} tenantId
|
||||
* @param {number} expenseId
|
||||
* @param {ISystemUser} authorizedUser
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
public publishExpense = (
|
||||
tenantId: number,
|
||||
expenseId: number,
|
||||
authorizedUser: ISystemUser
|
||||
) => {
|
||||
return this.publishExpenseService.publishExpense(
|
||||
tenantId,
|
||||
expenseId,
|
||||
authorizedUser
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the given expense details.
|
||||
* @param {number} tenantId
|
||||
* @param {number} expenseId
|
||||
* @return {Promise<IExpense>}
|
||||
*/
|
||||
public getExpense = (tenantId: number, expenseId: number) => {
|
||||
return this.getExpenseService.getExpense(tenantId, expenseId);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve expenses paginated list.
|
||||
* @param {number} tenantId
|
||||
* @param {IExpensesFilter} expensesFilter
|
||||
*/
|
||||
public getExpenses = (tenantId: number, filterDTO: IExpensesFilter) => {
|
||||
return this.getExpensesService.getExpensesList(tenantId, filterDTO);
|
||||
};
|
||||
}
|
||||
38
packages/server/src/services/Expenses/constants.ts
Normal file
38
packages/server/src/services/Expenses/constants.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
export const DEFAULT_VIEW_COLUMNS = [];
|
||||
export const DEFAULT_VIEWS = [
|
||||
{
|
||||
name: 'Draft',
|
||||
slug: 'draft',
|
||||
rolesLogicExpression: '1',
|
||||
roles: [
|
||||
{ index: 1, fieldKey: 'status', comparator: 'equals', value: 'draft' },
|
||||
],
|
||||
columns: DEFAULT_VIEW_COLUMNS,
|
||||
},
|
||||
{
|
||||
name: 'Published',
|
||||
slug: 'published',
|
||||
rolesLogicExpression: '1',
|
||||
roles: [
|
||||
{
|
||||
index: 1,
|
||||
fieldKey: 'status',
|
||||
comparator: 'equals',
|
||||
value: 'published',
|
||||
},
|
||||
],
|
||||
columns: DEFAULT_VIEW_COLUMNS,
|
||||
},
|
||||
];
|
||||
|
||||
export const ERRORS = {
|
||||
EXPENSE_NOT_FOUND: 'expense_not_found',
|
||||
EXPENSES_NOT_FOUND: 'EXPENSES_NOT_FOUND',
|
||||
PAYMENT_ACCOUNT_NOT_FOUND: 'payment_account_not_found',
|
||||
SOME_ACCOUNTS_NOT_FOUND: 'some_expenses_not_found',
|
||||
TOTAL_AMOUNT_EQUALS_ZERO: 'total_amount_equals_zero',
|
||||
PAYMENT_ACCOUNT_HAS_INVALID_TYPE: 'payment_account_has_invalid_type',
|
||||
EXPENSES_ACCOUNT_HAS_INVALID_TYPE: 'expenses_account_has_invalid_type',
|
||||
EXPENSE_ALREADY_PUBLISHED: 'expense_already_published',
|
||||
EXPENSE_HAS_ASSOCIATED_LANDED_COST: 'EXPENSE_HAS_ASSOCIATED_LANDED_COST',
|
||||
};
|
||||
Reference in New Issue
Block a user