import { Service, Inject } from 'typedi'; import { sumBy, difference } from 'lodash'; import { ServiceError } from '@/exceptions'; import { ERRORS, SUPPORTED_EXPENSE_PAYMENT_ACCOUNT_TYPES } 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} */ 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.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} 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); } } }