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,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,
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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'
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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 };
|
||||
});
|
||||
};
|
||||
}
|
||||
73
packages/server/src/services/Cashflow/constants.ts
Normal file
73
packages/server/src/services/Cashflow/constants.ts
Normal 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[];
|
||||
}
|
||||
34
packages/server/src/services/Cashflow/utils.ts
Normal file
34
packages/server/src/services/Cashflow/utils.ts
Normal 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);
|
||||
};
|
||||
Reference in New Issue
Block a user