add server to monorepo.

This commit is contained in:
a.bouhuolia
2023-02-03 11:57:50 +02:00
parent 28e309981b
commit 80b97b5fdc
1303 changed files with 137049 additions and 0 deletions

View File

@@ -0,0 +1,40 @@
import { IAccount } from '@/interfaces';
import { Transformer } from '@/lib/Transformer/Transformer';
import { formatNumber } from 'utils';
export class CashflowAccountTransformer extends Transformer {
/**
* Include these attributes to sale invoice object.
* @returns {string[]}
*/
public includeAttributes = (): string[] => {
return ['formattedAmount'];
};
/**
* Exclude these attributes to sale invoice object.
* @returns {string[]}
*/
public excludeAttributes = (): string[] => {
return [
'predefined',
'index',
'accountRootType',
'accountTypeLabel',
'accountParentType',
'isBalanceSheetAccount',
'isPlSheet',
];
};
/**
* Retrieve formatted account amount.
* @param {IAccount} invoice
* @returns {string}
*/
protected formattedAmount = (account: IAccount): string => {
return formatNumber(account.amount, {
currencyCode: account.currencyCode,
});
};
}

View File

@@ -0,0 +1,30 @@
import { Service, Inject } from 'typedi';
import { ServiceError } from '@/exceptions';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { ERRORS } from './constants';
@Service()
export default class CashflowDeleteAccount {
@Inject()
tenancy: HasTenancyService;
/**
* Validate the account has no associated cashflow transactions.
* @param {number} tenantId
* @param {number} accountId
*/
public validateAccountHasNoCashflowEntries = async (
tenantId: number,
accountId: number
) => {
const { CashflowTransactionLine } = this.tenancy.models(tenantId);
const associatedLines = await CashflowTransactionLine.query()
.where('creditAccountId', accountId)
.orWhere('cashflowAccountId', accountId);
if (associatedLines.length > 0) {
throw new ServiceError(ERRORS.ACCOUNT_HAS_ASSOCIATED_TRANSACTIONS)
}
};
}

View File

@@ -0,0 +1,31 @@
import { Service, Inject } from 'typedi';
import AutoIncrementOrdersService from '@/services/Sales/AutoIncrementOrdersService';
@Service()
export class CashflowTransactionAutoIncrement {
@Inject()
private autoIncrementOrdersService: AutoIncrementOrdersService;
/**
* Retrieve the next unique invoice number.
* @param {number} tenantId - Tenant id.
* @return {string}
*/
public getNextTransactionNumber = (tenantId: number): string => {
return this.autoIncrementOrdersService.getNextTransactionNumber(
tenantId,
'cashflow'
);
};
/**
* Increment the invoice next number.
* @param {number} tenantId -
*/
public incrementNextTransactionNumber = (tenantId: number) => {
return this.autoIncrementOrdersService.incrementSettingsNextNumber(
tenantId,
'cashflow'
);
};
}

View File

@@ -0,0 +1,171 @@
import { Inject, Service } from 'typedi';
import { Knex } from 'knex';
import * as R from 'ramda';
import {
ILedgerEntry,
ICashflowTransaction,
AccountNormal,
ICashflowTransactionLine,
} from '../../interfaces';
import {
transformCashflowTransactionType,
getCashflowAccountTransactionsTypes,
} from './utils';
import LedgerStorageService from '@/services/Accounting/LedgerStorageService';
import Ledger from '@/services/Accounting/Ledger';
import HasTenancyService from '@/services/Tenancy/TenancyService';
@Service()
export default class CashflowTransactionJournalEntries {
@Inject()
private ledgerStorage: LedgerStorageService;
@Inject()
private tenancy: HasTenancyService;
/**
* Retrieves the common entry of cashflow transaction.
* @param {ICashflowTransaction} cashflowTransaction
* @returns {}
*/
private getCommonEntry = (cashflowTransaction: ICashflowTransaction) => {
const { entries, ...transaction } = cashflowTransaction;
return {
date: transaction.date,
currencyCode: transaction.currencyCode,
exchangeRate: transaction.exchangeRate,
transactionType: transformCashflowTransactionType(
transaction.transactionType
),
transactionId: transaction.id,
transactionNumber: transaction.transactionNumber,
referenceNo: transaction.referenceNo,
branchId: cashflowTransaction.branchId,
userId: cashflowTransaction.userId,
};
};
/**
* Retrieves the cashflow debit GL entry.
* @param {ICashflowTransaction} cashflowTransaction
* @param {ICashflowTransactionLine} entry
* @param {number} index
* @returns {ILedgerEntry}
*/
private getCashflowDebitGLEntry = (
cashflowTransaction: ICashflowTransaction
): ILedgerEntry => {
const commonEntry = this.getCommonEntry(cashflowTransaction);
return {
...commonEntry,
accountId: cashflowTransaction.cashflowAccountId,
credit: cashflowTransaction.isCashCredit
? cashflowTransaction.localAmount
: 0,
debit: cashflowTransaction.isCashDebit
? cashflowTransaction.localAmount
: 0,
accountNormal: AccountNormal.DEBIT,
index: 1,
};
};
/**
* Retrieves the cashflow credit GL entry.
* @param {ICashflowTransaction} cashflowTransaction
* @param {ICashflowTransactionLine} entry
* @param {number} index
* @returns {ILedgerEntry}
*/
private getCashflowCreditGLEntry = (
cashflowTransaction: ICashflowTransaction
): ILedgerEntry => {
const commonEntry = this.getCommonEntry(cashflowTransaction);
return {
...commonEntry,
credit: cashflowTransaction.isCashDebit
? cashflowTransaction.localAmount
: 0,
debit: cashflowTransaction.isCashCredit
? cashflowTransaction.localAmount
: 0,
accountId: cashflowTransaction.creditAccountId,
accountNormal: cashflowTransaction.creditAccount.accountNormal,
index: 2,
};
};
/**
* Retrieves the cashflow transaction GL entry.
* @param {ICashflowTransaction} cashflowTransaction
* @param {ICashflowTransactionLine} entry
* @param {number} index
* @returns
*/
private getJournalEntries = (
cashflowTransaction: ICashflowTransaction
): ILedgerEntry[] => {
const debitEntry = this.getCashflowDebitGLEntry(cashflowTransaction);
const creditEntry = this.getCashflowCreditGLEntry(cashflowTransaction);
return [debitEntry, creditEntry];
};
/**
* Retrieves the cashflow GL ledger.
* @param {ICashflowTransaction} cashflowTransaction
* @returns {Ledger}
*/
private getCashflowLedger = (cashflowTransaction: ICashflowTransaction) => {
const entries = this.getJournalEntries(cashflowTransaction);
return new Ledger(entries);
};
/**
* Write the journal entries of the given cashflow transaction.
* @param {number} tenantId
* @param {ICashflowTransaction} cashflowTransaction
*/
public writeJournalEntries = async (
tenantId: number,
cashflowTransactionId: number,
trx?: Knex.Transaction
): Promise<void> => {
const { CashflowTransaction } = this.tenancy.models(tenantId);
// Retrieves the cashflow transactions with associated entries.
const transaction = await CashflowTransaction.query(trx)
.findById(cashflowTransactionId)
.withGraphFetched('creditAccount');
// Retrieves the cashflow transaction ledger.
const ledger = this.getCashflowLedger(transaction);
await this.ledgerStorage.commit(tenantId, ledger, trx);
};
/**
* Delete the journal entries.
* @param {number} tenantId - Tenant id.
* @param {number} cashflowTransactionId - Cashflow transaction id.
*/
public revertJournalEntries = async (
tenantId: number,
cashflowTransactionId: number,
trx?: Knex.Transaction
): Promise<void> => {
const transactionTypes = getCashflowAccountTransactionsTypes();
await this.ledgerStorage.deleteByReference(
tenantId,
cashflowTransactionId,
transactionTypes,
trx
);
};
}

View File

@@ -0,0 +1,83 @@
import { Inject, Service } from 'typedi';
import events from '@/subscribers/events';
import CashflowTransactionJournalEntries from './CashflowTransactionJournalEntries';
import {
ICommandCashflowCreatedPayload,
ICommandCashflowDeletedPayload,
} from '@/interfaces';
import { CashflowTransactionAutoIncrement } from './CashflowTransactionAutoIncrement';
@Service()
export default class CashflowTransactionSubscriber {
@Inject()
private cashflowTransactionEntries: CashflowTransactionJournalEntries;
@Inject()
private cashflowTransactionAutoIncrement: CashflowTransactionAutoIncrement;
/**
* Attaches events with handles.
*/
public attach(bus) {
bus.subscribe(
events.cashflow.onTransactionCreated,
this.writeJournalEntriesOnceTransactionCreated
);
bus.subscribe(
events.cashflow.onTransactionCreated,
this.incrementTransactionNumberOnceTransactionCreated
);
bus.subscribe(
events.cashflow.onTransactionDeleted,
this.revertGLEntriesOnceTransactionDeleted
);
return bus;
}
/**
* Writes the journal entries once the cashflow transaction create.
* @param {ICommandCashflowCreatedPayload} payload -
*/
private writeJournalEntriesOnceTransactionCreated = async ({
tenantId,
cashflowTransaction,
trx,
}: ICommandCashflowCreatedPayload) => {
// Can't write GL entries if the transaction not published yet.
if (!cashflowTransaction.isPublished) return;
await this.cashflowTransactionEntries.writeJournalEntries(
tenantId,
cashflowTransaction.id,
trx
);
};
/**
* Increment the cashflow transaction number once the transaction created.
* @param {ICommandCashflowCreatedPayload} payload -
*/
private incrementTransactionNumberOnceTransactionCreated = async ({
tenantId,
}: ICommandCashflowCreatedPayload) => {
this.cashflowTransactionAutoIncrement.incrementNextTransactionNumber(
tenantId
);
};
/**
* Deletes the GL entries once the cashflow transaction deleted.
* @param {ICommandCashflowDeletedPayload} payload -
*/
private revertGLEntriesOnceTransactionDeleted = async ({
tenantId,
cashflowTransactionId,
trx,
}: ICommandCashflowDeletedPayload) => {
await this.cashflowTransactionEntries.revertJournalEntries(
tenantId,
cashflowTransactionId,
trx
);
};
}

View File

@@ -0,0 +1,33 @@
import { Transformer } from '@/lib/Transformer/Transformer';
import { formatNumber } from 'utils';
export class CashflowTransactionTransformer extends Transformer {
/**
* Include these attributes to sale invoice object.
* @returns {string[]}
*/
public includeAttributes = (): string[] => {
return ['formattedAmount', 'transactionTypeFormatted'];
};
/**
* Formatted amount.
* @param {} transaction
* @returns {string}
*/
protected formattedAmount = (transaction) => {
return formatNumber(transaction.amount, {
currencyCode: transaction.currencyCode,
excerptZero: true,
});
};
/**
* Formatted transaction type.
* @param transaction
* @returns {string}
*/
protected transactionTypeFormatted = (transaction) => {
return this.context.i18n.__(transaction.transactionTypeFormatted);
}
}

View File

@@ -0,0 +1,71 @@
import { Transformer } from '@/lib/Transformer/Transformer';
import { formatNumber } from 'utils';
export class CashflowTransactionTransformer extends Transformer {
/**
* Include these attributes to sale invoice object.
* @returns {string[]}
*/
protected includeAttributes = (): string[] => {
return ['deposit', 'withdrawal', 'formattedDeposit', 'formattedWithdrawal'];
};
/**
* Exclude these attributes.
* @returns {string[]}
*/
protected excludeAttributes = (): string[] => {
return [
'credit',
'debit',
'index',
'index_group',
'item_id',
'item_quantity',
'contact_type',
'contact_id',
];
};
/**
* Deposit amount attribute.
* @param transaction
* @returns
*/
protected deposit = (transaction) => {
return transaction.debit;
};
/**
* Withdrawal amount attribute.
* @param transaction
* @returns
*/
protected withdrawal = (transaction) => {
return transaction.credit;
};
/**
* Formatted withdrawal amount.
* @param transaction
* @returns
*/
protected formattedWithdrawal = (transaction) => {
return formatNumber(transaction.credit, {
currencyCode: transaction.currencyCode,
excerptZero: true,
});
};
/**
* Formatted deposit account.
* @param transaction
* @returns
*/
protected formattedDeposit = (transaction) => {
return formatNumber(transaction.debit, {
currencyCode: transaction.currencyCode,
excerptZero: true,
});
};
}

View File

@@ -0,0 +1,34 @@
import { Service, Inject } from 'typedi';
import events from '@/subscribers/events';
import { IAccountEventDeletePayload } from '@/interfaces';
import CashflowDeleteAccount from './CashflowDeleteAccount';
@Service()
export default class CashflowWithAccountSubscriber {
@Inject()
cashflowDeleteAccount: CashflowDeleteAccount;
/**
* Attaches events with handlers.
*/
public attach = (bus) => {
bus.subscribe(
events.accounts.onDelete,
this.validateAccountHasNoCashflowTransactionsOnDelete
);
};
/**
* Validate chart account has no associated cashflow transactions on delete.
* @param {IAccountEventDeletePayload} payload -
*/
private validateAccountHasNoCashflowTransactionsOnDelete = async ({
tenantId,
oldAccount,
}: IAccountEventDeletePayload) => {
await this.cashflowDeleteAccount.validateAccountHasNoCashflowEntries(
tenantId,
oldAccount.id
);
};
}

View File

@@ -0,0 +1,54 @@
import { Service, Inject } from 'typedi';
import { includes, difference, camelCase, upperFirst } from 'lodash';
import { ACCOUNT_TYPE } from '@/data/AccountTypes';
import { IAccount, ICashflowTransactionLine } from '@/interfaces';
import { getCashflowTransactionType } from './utils';
import { ServiceError } from '@/exceptions';
import { CASHFLOW_TRANSACTION_TYPE, ERRORS } from './constants';
import HasTenancyService from '@/services/Tenancy/TenancyService';
@Service()
export class CommandCashflowValidator {
@Inject()
private tenancy: HasTenancyService;
/**
* Validates the lines accounts type should be cash or bank account.
* @param {IAccount} accounts -
*/
public validateCreditAccountWithCashflowType = (
creditAccount: IAccount,
cashflowTransactionType: CASHFLOW_TRANSACTION_TYPE
): void => {
const transactionTypeMeta = getCashflowTransactionType(
cashflowTransactionType
);
const noneCashflowAccount = !includes(
transactionTypeMeta.creditType,
creditAccount.accountType
);
if (noneCashflowAccount) {
throw new ServiceError(ERRORS.CREDIT_ACCOUNTS_HAS_INVALID_TYPE);
}
};
/**
* Validates the cashflow transaction type.
* @param {string} transactionType
* @returns {string}
*/
public validateCashflowTransactionType = (transactionType: string) => {
const transformedType = upperFirst(
camelCase(transactionType)
) as CASHFLOW_TRANSACTION_TYPE;
// Retrieve the given transaction type meta.
const transactionTypeMeta = getCashflowTransactionType(transformedType);
// Throw service error in case not the found the given transaction type.
if (!transactionTypeMeta) {
throw new ServiceError(ERRORS.CASHFLOW_TRANSACTION_TYPE_INVALID);
}
return transformedType;
};
}

View File

@@ -0,0 +1,14 @@
import { difference, includes } from 'lodash';
import { ICashflowTransactionLine } from '@/interfaces';
import { ServiceError } from '@/exceptions';
import { Inject, Service } from 'typedi';
import { CASHFLOW_TRANSACTION_TYPE, ERRORS } from './constants';
import { IAccount } from '@/interfaces';
import HasTenancyService from '@/services/Tenancy/TenancyService';
@Service()
export default class CommandCashflowTransaction {
@Inject()
private tenancy: HasTenancyService;
}

View File

@@ -0,0 +1,85 @@
import { Service, Inject } from 'typedi';
import { Knex } from 'knex';
import events from '@/subscribers/events';
import {
ICashflowTransaction,
ICommandCashflowDeletedPayload,
ICommandCashflowDeletingPayload,
} from '@/interfaces';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { ServiceError } from '@/exceptions';
import { ERRORS } from './constants';
import UnitOfWork from '@/services/UnitOfWork';
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
@Service()
export default class CommandCashflowTransactionService {
@Inject()
tenancy: HasTenancyService;
@Inject()
eventPublisher: EventPublisher;
@Inject()
uow: UnitOfWork;
/**
* Deletes the cashflow transaction with associated journal entries.
* @param {number} tenantId -
* @param {number} userId - User id.
*/
public deleteCashflowTransaction = async (
tenantId: number,
cashflowTransactionId: number
): Promise<{ oldCashflowTransaction: ICashflowTransaction }> => {
const { CashflowTransaction, CashflowTransactionLine } =
this.tenancy.models(tenantId);
// Retrieve the cashflow transaction.
const oldCashflowTransaction = await CashflowTransaction.query().findById(
cashflowTransactionId
);
// Throw not found error if the given transaction id not found.
this.throwErrorIfTransactionNotFound(oldCashflowTransaction);
// Starting database transaction.
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
// Triggers `onCashflowTransactionDelete` event.
await this.eventPublisher.emitAsync(events.cashflow.onTransactionDeleting, {
trx,
tenantId,
oldCashflowTransaction,
} as ICommandCashflowDeletingPayload);
// Delete cashflow transaction associated lines first.
await CashflowTransactionLine.query(trx)
.where('cashflow_transaction_id', cashflowTransactionId)
.delete();
// Delete cashflow transaction.
await CashflowTransaction.query(trx)
.findById(cashflowTransactionId)
.delete();
// Triggers `onCashflowTransactionDeleted` event.
await this.eventPublisher.emitAsync(events.cashflow.onTransactionDeleted, {
trx,
tenantId,
cashflowTransactionId,
oldCashflowTransaction,
} as ICommandCashflowDeletedPayload);
return { oldCashflowTransaction };
});
};
/**
* Throw not found error if the given transaction id not found.
* @param transaction
*/
private throwErrorIfTransactionNotFound(transaction) {
if (!transaction) {
throw new ServiceError(ERRORS.CASHFLOW_TRANSACTION_NOT_FOUND);
}
}
}

View File

@@ -0,0 +1,54 @@
import { Service, Inject } from 'typedi';
import { ICashflowAccount, ICashflowAccountsFilter } from '@/interfaces';
import { CashflowAccountTransformer } from './CashflowAccountTransformer';
import TenancyService from '@/services/Tenancy/TenancyService';
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
@Service()
export default class GetCashflowAccountsService {
@Inject()
private tenancy: TenancyService;
@Inject()
private dynamicListService: DynamicListingService;
@Inject()
private transformer: TransformerInjectable;
/**
* Retrieve the cash flow accounts.
* @param {number} tenantId - Tenant id.
* @param {ICashflowAccountsFilter} filterDTO - Filter DTO.
* @returns {ICashflowAccount[]}
*/
public async getCashflowAccounts(
tenantId: number,
filterDTO: ICashflowAccountsFilter
): Promise<{ cashflowAccounts: ICashflowAccount[] }> {
const { CashflowAccount } = this.tenancy.models(tenantId);
// Parsees accounts list filter DTO.
const filter = this.dynamicListService.parseStringifiedFilter(filterDTO);
// Dynamic list service.
const dynamicList = await this.dynamicListService.dynamicList(
tenantId,
CashflowAccount,
filter
);
// Retrieve accounts model based on the given query.
const accounts = await CashflowAccount.query().onBuild((builder) => {
dynamicList.buildQuery()(builder);
builder.whereIn('account_type', ['bank', 'cash']);
builder.modify('inactiveMode', filter.inactiveMode);
});
// Retrieves the transformed accounts.
return this.transformer.transform(
tenantId,
accounts,
new CashflowAccountTransformer()
);
}
}

View File

@@ -0,0 +1,61 @@
import { Service, Inject } from 'typedi';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { CashflowTransactionTransformer } from './CashflowTransactionTransformer';
import { ERRORS } from './constants';
import { ICashflowTransaction } from '@/interfaces';
import { ServiceError } from '@/exceptions';
import I18nService from '@/services/I18n/I18nService';
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
@Service()
export default class GetCashflowTransactionsService {
@Inject()
private tenancy: HasTenancyService;
@Inject()
private i18nService: I18nService;
@Inject()
private transfromer: TransformerInjectable;
/**
* Retrieve the given cashflow transaction.
* @param {number} tenantId
* @param {number} cashflowTransactionId
* @returns
*/
public getCashflowTransaction = async (
tenantId: number,
cashflowTransactionId: number
) => {
const { CashflowTransaction } = this.tenancy.models(tenantId);
const cashflowTransaction = await CashflowTransaction.query()
.findById(cashflowTransactionId)
.withGraphFetched('entries.cashflowAccount')
.withGraphFetched('entries.creditAccount')
.withGraphFetched('transactions.account')
.throwIfNotFound();
this.throwErrorCashflowTranscationNotFound(cashflowTransaction);
// Transformes the cashflow transaction model to POJO.
return this.transfromer.transform(
tenantId,
cashflowTransaction,
new CashflowTransactionTransformer()
);
};
/**
* Throw not found error if the given cashflow undefined.
* @param {ICashflowTransaction} cashflowTransaction -
*/
private throwErrorCashflowTranscationNotFound = (
cashflowTransaction: ICashflowTransaction
) => {
if (!cashflowTransaction) {
throw new ServiceError(ERRORS.CASHFLOW_TRANSACTION_NOT_FOUND);
}
};
}

View File

@@ -0,0 +1,180 @@
import { Service, Inject } from 'typedi';
import { isEmpty, pick } from 'lodash';
import { Knex } from 'knex';
import * as R from 'ramda';
import {
ICashflowNewCommandDTO,
ICashflowTransaction,
ICashflowTransactionLine,
ICommandCashflowCreatedPayload,
ICommandCashflowCreatingPayload,
ICashflowTransactionInput,
IAccount,
} from '@/interfaces';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { CASHFLOW_TRANSACTION_TYPE } from './constants';
import { transformCashflowTransactionType } from './utils';
import events from '@/subscribers/events';
import { CommandCashflowValidator } from './CommandCasflowValidator';
import UnitOfWork from '@/services/UnitOfWork';
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
import { CashflowTransactionAutoIncrement } from './CashflowTransactionAutoIncrement';
import { BranchTransactionDTOTransform } from '@/services/Branches/Integrations/BranchTransactionDTOTransform';
@Service()
export default class NewCashflowTransactionService {
@Inject()
private tenancy: HasTenancyService;
@Inject()
private validator: CommandCashflowValidator;
@Inject()
private uow: UnitOfWork;
@Inject()
private eventPublisher: EventPublisher;
@Inject()
private autoIncrement: CashflowTransactionAutoIncrement;
@Inject()
private branchDTOTransform: BranchTransactionDTOTransform;
/**
* Authorize the cashflow creating transaction.
* @param {number} tenantId
* @param {ICashflowNewCommandDTO} newCashflowTransactionDTO
*/
public authorize = async (
tenantId: number,
newCashflowTransactionDTO: ICashflowNewCommandDTO,
creditAccount: IAccount
) => {
const transactionType = transformCashflowTransactionType(
newCashflowTransactionDTO.transactionType
);
// Validates the cashflow transaction type.
this.validator.validateCashflowTransactionType(transactionType);
// Retrieve accounts of the cashflow lines object.
this.validator.validateCreditAccountWithCashflowType(
creditAccount,
transactionType as CASHFLOW_TRANSACTION_TYPE
);
};
/**
* Transformes owner contribution DTO to cashflow transaction.
* @param {ICashflowNewCommandDTO} newCashflowTransactionDTO - New transaction DTO.
* @returns {ICashflowTransaction} - Cashflow transaction object.
*/
private transformCashflowTransactionDTO = (
tenantId: number,
newCashflowTransactionDTO: ICashflowNewCommandDTO,
cashflowAccount: IAccount,
userId: number
): ICashflowTransactionInput => {
const amount = newCashflowTransactionDTO.amount;
const fromDTO = pick(newCashflowTransactionDTO, [
'date',
'referenceNo',
'description',
'transactionType',
'exchangeRate',
'cashflowAccountId',
'creditAccountId',
'branchId',
]);
// Retreive the next invoice number.
const autoNextNumber =
this.autoIncrement.getNextTransactionNumber(tenantId);
// Retrieve the transaction number.
const transactionNumber =
newCashflowTransactionDTO.transactionNumber || autoNextNumber;
const initialDTO = {
amount,
...fromDTO,
transactionNumber,
currencyCode: cashflowAccount.currencyCode,
transactionType: transformCashflowTransactionType(
fromDTO.transactionType
),
userId,
...(newCashflowTransactionDTO.publish
? {
publishedAt: new Date(),
}
: {}),
};
return R.compose(
this.branchDTOTransform.transformDTO<ICashflowTransactionInput>(tenantId)
)(initialDTO);
};
/**
* Owner contribution money in.
* @param {number} tenantId -
* @param {ICashflowOwnerContributionDTO} ownerContributionDTO
* @param {number} userId - User id.
*/
public newCashflowTransaction = async (
tenantId: number,
newTransactionDTO: ICashflowNewCommandDTO,
userId: number
): Promise<{ cashflowTransaction: ICashflowTransaction }> => {
const { CashflowTransaction, Account } = this.tenancy.models(tenantId);
// Retrieves the cashflow account or throw not found error.
const cashflowAccount = await Account.query()
.findById(newTransactionDTO.cashflowAccountId)
.throwIfNotFound();
// Retrieves the credit account or throw not found error.
const creditAccount = await Account.query()
.findById(newTransactionDTO.creditAccountId)
.throwIfNotFound();
// Authorize before creating cashflow transaction.
await this.authorize(tenantId, newTransactionDTO, creditAccount);
// Transformes owner contribution DTO to cashflow transaction.
const cashflowTransactionObj = this.transformCashflowTransactionDTO(
tenantId,
newTransactionDTO,
cashflowAccount,
userId
);
// Creates a new cashflow transaction under UOW envirement.
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
// Triggers `onCashflowTransactionCreate` event.
await this.eventPublisher.emitAsync(
events.cashflow.onTransactionCreating,
{
trx,
tenantId,
newTransactionDTO,
} as ICommandCashflowCreatingPayload
);
// Inserts cashflow owner contribution transaction.
const cashflowTransaction = await CashflowTransaction.query(
trx
).upsertGraph(cashflowTransactionObj);
// Triggers `onCashflowTransactionCreated` event.
await this.eventPublisher.emitAsync(
events.cashflow.onTransactionCreated,
{
tenantId,
newTransactionDTO,
cashflowTransaction,
trx,
} as ICommandCashflowCreatedPayload
);
return { cashflowTransaction };
});
};
}

View File

@@ -0,0 +1,73 @@
import { ACCOUNT_TYPE } from '@/data/AccountTypes';
export const ERRORS = {
CASHFLOW_TRANSACTION_TYPE_INVALID: 'CASHFLOW_TRANSACTION_TYPE_INVALID',
CASHFLOW_ACCOUNTS_HAS_INVALID_TYPE: 'CASHFLOW_ACCOUNTS_HAS_INVALID_TYPE',
CASHFLOW_TRANSACTION_NOT_FOUND: 'CASHFLOW_TRANSACTION_NOT_FOUND',
CASHFLOW_ACCOUNTS_IDS_NOT_FOUND: 'CASHFLOW_ACCOUNTS_IDS_NOT_FOUND',
CREDIT_ACCOUNTS_IDS_NOT_FOUND: 'CREDIT_ACCOUNTS_IDS_NOT_FOUND',
CREDIT_ACCOUNTS_HAS_INVALID_TYPE: 'CREDIT_ACCOUNTS_HAS_INVALID_TYPE',
ACCOUNT_ID_HAS_INVALID_TYPE: 'ACCOUNT_ID_HAS_INVALID_TYPE',
ACCOUNT_HAS_ASSOCIATED_TRANSACTIONS: 'account_has_associated_transactions'
};
export enum CASHFLOW_DIRECTION {
IN = 'In',
OUT = 'Out',
}
export enum CASHFLOW_TRANSACTION_TYPE {
ONWERS_DRAWING = 'OwnerDrawing',
OWNER_CONTRIBUTION = 'OwnerContribution',
OTHER_INCOME = 'OtherIncome',
TRANSFER_FROM_ACCOUNT = 'TransferFromAccount',
TRANSFER_TO_ACCOUNT = 'TransferToAccount',
OTHER_EXPENSE = 'OtherExpense',
}
export const CASHFLOW_TRANSACTION_TYPE_META = {
[`${CASHFLOW_TRANSACTION_TYPE.ONWERS_DRAWING}`]: {
type: 'OwnerDrawing',
direction: CASHFLOW_DIRECTION.OUT,
creditType: [ACCOUNT_TYPE.EQUITY],
},
[`${CASHFLOW_TRANSACTION_TYPE.OWNER_CONTRIBUTION}`]: {
type: 'OwnerContribution',
direction: CASHFLOW_DIRECTION.IN,
creditType: [ACCOUNT_TYPE.EQUITY],
},
[`${CASHFLOW_TRANSACTION_TYPE.OTHER_INCOME}`]: {
type: 'OtherIncome',
direction: CASHFLOW_DIRECTION.IN,
creditType: [ACCOUNT_TYPE.INCOME, ACCOUNT_TYPE.OTHER_INCOME],
},
[`${CASHFLOW_TRANSACTION_TYPE.TRANSFER_FROM_ACCOUNT}`]: {
type: 'TransferFromAccount',
direction: CASHFLOW_DIRECTION.IN,
creditType: [
ACCOUNT_TYPE.CASH,
ACCOUNT_TYPE.BANK,
ACCOUNT_TYPE.CREDIT_CARD,
],
},
[`${CASHFLOW_TRANSACTION_TYPE.TRANSFER_TO_ACCOUNT}`]: {
type: 'TransferToAccount',
direction: CASHFLOW_DIRECTION.OUT,
creditType: [
ACCOUNT_TYPE.CASH,
ACCOUNT_TYPE.BANK,
ACCOUNT_TYPE.CREDIT_CARD,
],
},
[`${CASHFLOW_TRANSACTION_TYPE.OTHER_EXPENSE}`]: {
type: 'OtherExpense',
direction: CASHFLOW_DIRECTION.OUT,
creditType: [ACCOUNT_TYPE.EXPENSE, ACCOUNT_TYPE.OTHER_EXPENSE],
},
};
export interface ICashflowTransactionTypeMeta {
type: string;
direction: CASHFLOW_DIRECTION;
creditType: string[];
}

View File

@@ -0,0 +1,34 @@
import { upperFirst, camelCase } from 'lodash';
import {
CASHFLOW_TRANSACTION_TYPE,
CASHFLOW_TRANSACTION_TYPE_META,
ICashflowTransactionTypeMeta,
} from './constants';
/**
* Ensures the given transaction type to transformed to properiate format.
* @param {string} type
* @returns {string}
*/
export const transformCashflowTransactionType = (type) => {
return upperFirst(camelCase(type));
};
/**
* Retrieve the cashflow transaction type meta.
* @param {CASHFLOW_TRANSACTION_TYPE} transactionType
* @returns {ICashflowTransactionTypeMeta}
*/
export function getCashflowTransactionType(
transactionType: CASHFLOW_TRANSACTION_TYPE
): ICashflowTransactionTypeMeta {
return CASHFLOW_TRANSACTION_TYPE_META[transactionType];
}
/**
* Retrieve cashflow accounts transactions types
* @returns {string}
*/
export const getCashflowAccountTransactionsTypes = () => {
return Object.values(CASHFLOW_TRANSACTION_TYPE_META).map((meta) => meta.type);
};