import { Inject, Service } from 'typedi'; import { kebabCase } from 'lodash'; import { Knex } from 'knex'; import TenancyService from '@/services/Tenancy/TenancyService'; import { IAccount, IAccountEventCreatedPayload, IAccountEventCreatingPayload, IAccountCreateDTO, CreateAccountParams, } from '@/interfaces'; import events from '@/subscribers/events'; import UnitOfWork from '@/services/UnitOfWork'; import { EventPublisher } from '@/lib/EventPublisher/EventPublisher'; import { TenantMetadata } from '@/system/models'; import { CommandAccountValidators } from './CommandAccountValidators'; @Service() export class CreateAccount { @Inject() private tenancy: TenancyService; @Inject() private eventPublisher: EventPublisher; @Inject() private uow: UnitOfWork; @Inject() private validator: CommandAccountValidators; /** * Authorize the account creation. * @param {number} tenantId * @param {IAccountCreateDTO} accountDTO */ private authorize = async ( tenantId: number, accountDTO: IAccountCreateDTO, baseCurrency: string, params?: CreateAccountParams ) => { // Validate account name uniquiness. if (!params.ignoreUniqueName) { await this.validator.validateAccountNameUniquiness( tenantId, accountDTO.name ); } // Validate the account code uniquiness. if (accountDTO.code) { await this.validator.isAccountCodeUniqueOrThrowError( tenantId, accountDTO.code ); } // Retrieve the account type meta or throw service error if not found. this.validator.getAccountTypeOrThrowError(accountDTO.accountType); // Ingore the parent account validation if not presented. if (accountDTO.parentAccountId) { const parentAccount = await this.validator.getParentAccountOrThrowError( tenantId, accountDTO.parentAccountId ); this.validator.throwErrorIfParentHasDiffType(accountDTO, parentAccount); // Inherit active status from parent account. accountDTO.active = parentAccount.active; // Validate should currency code be the same currency of parent account. this.validator.validateCurrentSameParentAccount( accountDTO, parentAccount, baseCurrency ); // Validates the max depth level of accounts chart. await this.validator.validateMaxParentAccountDepthLevels( tenantId, accountDTO.parentAccountId ); } // Validates the given account type supports the multi-currency. this.validator.validateAccountTypeSupportCurrency(accountDTO, baseCurrency); }; /** * Transformes the create account DTO to input model. * @param {IAccountCreateDTO} createAccountDTO */ private transformDTOToModel = ( createAccountDTO: IAccountCreateDTO, baseCurrency: string ) => { return { ...createAccountDTO, slug: kebabCase(createAccountDTO.name), currencyCode: createAccountDTO.currencyCode || baseCurrency, // Mark the account is Plaid owner since Plaid item/account is defined on creating. isSyncingOwner: Boolean( createAccountDTO.plaidAccountId || createAccountDTO.plaidItemId ), }; }; /** * Creates a new account on the storage. * @param {number} tenantId * @param {IAccountCreateDTO} accountDTO * @returns {Promise} */ public createAccount = async ( tenantId: number, accountDTO: IAccountCreateDTO, trx?: Knex.Transaction, params: CreateAccountParams = { ignoreUniqueName: false } ): Promise => { const { Account } = this.tenancy.models(tenantId); // Retrieves the given tenant metadata. const tenantMeta = await TenantMetadata.query().findOne({ tenantId }); // Authorize the account creation. await this.authorize(tenantId, accountDTO, tenantMeta.baseCurrency, params); // Transformes the DTO to model. const accountInputModel = this.transformDTOToModel( accountDTO, tenantMeta.baseCurrency ); // Creates a new account with associated transactions under unit-of-work envirement. return this.uow.withTransaction( tenantId, async (trx: Knex.Transaction) => { // Triggers `onAccountCreating` event. await this.eventPublisher.emitAsync(events.accounts.onCreating, { tenantId, accountDTO, trx, } as IAccountEventCreatingPayload); // Inserts account to the storage. const account = await Account.query(trx).insertAndFetch({ ...accountInputModel, }); // Triggers `onAccountCreated` event. await this.eventPublisher.emitAsync(events.accounts.onCreated, { tenantId, account, accountId: account.id, trx, } as IAccountEventCreatedPayload); return account; }, trx ); }; }