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,240 @@
import { isEmpty, get, last, sumBy } from 'lodash';
import {
IGeneralLedgerSheetQuery,
IGeneralLedgerSheetAccount,
IGeneralLedgerSheetAccountBalance,
IGeneralLedgerSheetAccountTransaction,
IAccount,
IJournalPoster,
IJournalEntry,
IContact,
} from '@/interfaces';
import FinancialSheet from '../FinancialSheet';
/**
* General ledger sheet.
*/
export default class GeneralLedgerSheet extends FinancialSheet {
tenantId: number;
accounts: IAccount[];
query: IGeneralLedgerSheetQuery;
openingBalancesJournal: IJournalPoster;
transactions: IJournalPoster;
contactsMap: Map<number, IContact>;
baseCurrency: string;
i18n: any;
/**
* Constructor method.
* @param {number} tenantId -
* @param {IAccount[]} accounts -
* @param {IJournalPoster} transactions -
* @param {IJournalPoster} openingBalancesJournal -
* @param {IJournalPoster} closingBalancesJournal -
*/
constructor(
tenantId: number,
query: IGeneralLedgerSheetQuery,
accounts: IAccount[],
contactsByIdMap: Map<number, IContact>,
transactions: IJournalPoster,
openingBalancesJournal: IJournalPoster,
baseCurrency: string,
i18n
) {
super();
this.tenantId = tenantId;
this.query = query;
this.numberFormat = this.query.numberFormat;
this.accounts = accounts;
this.contactsMap = contactsByIdMap;
this.transactions = transactions;
this.openingBalancesJournal = openingBalancesJournal;
this.baseCurrency = baseCurrency;
this.i18n = i18n;
}
/**
* Retrieve the transaction amount.
* @param {number} credit - Credit amount.
* @param {number} debit - Debit amount.
* @param {string} normal - Credit or debit.
*/
getAmount(credit: number, debit: number, normal: string) {
return normal === 'credit' ? credit - debit : debit - credit;
}
/**
* Entry mapper.
* @param {IJournalEntry} entry -
* @return {IGeneralLedgerSheetAccountTransaction}
*/
entryReducer(
entries: IGeneralLedgerSheetAccountTransaction[],
entry: IJournalEntry,
openingBalance: number
): IGeneralLedgerSheetAccountTransaction[] {
const lastEntry = last(entries);
const contact = this.contactsMap.get(entry.contactId);
const amount = this.getAmount(
entry.credit,
entry.debit,
entry.accountNormal
);
const runningBalance =
amount + (!isEmpty(entries) ? lastEntry.runningBalance : openingBalance);
const newEntry = {
date: entry.date,
entryId: entry.id,
referenceType: entry.referenceType,
referenceId: entry.referenceId,
referenceTypeFormatted: this.i18n.__(entry.referenceTypeFormatted),
contactName: get(contact, 'displayName'),
contactType: get(contact, 'contactService'),
transactionType: entry.transactionType,
index: entry.index,
note: entry.note,
credit: entry.credit,
debit: entry.debit,
amount,
runningBalance,
formattedAmount: this.formatNumber(amount),
formattedCredit: this.formatNumber(entry.credit),
formattedDebit: this.formatNumber(entry.debit),
formattedRunningBalance: this.formatNumber(runningBalance),
currencyCode: this.baseCurrency,
};
entries.push(newEntry);
return entries;
}
/**
* Mapping the account transactions to general ledger transactions of the given account.
* @param {IAccount} account
* @return {IGeneralLedgerSheetAccountTransaction[]}
*/
private accountTransactionsMapper(
account: IAccount,
openingBalance: number
): IGeneralLedgerSheetAccountTransaction[] {
const entries = this.transactions.getAccountEntries(account.id);
return entries.reduce(
(
entries: IGeneralLedgerSheetAccountTransaction[],
entry: IJournalEntry
) => {
return this.entryReducer(entries, entry, openingBalance);
},
[]
);
}
/**
* Retrieve account opening balance.
* @param {IAccount} account
* @return {IGeneralLedgerSheetAccountBalance}
*/
private accountOpeningBalance(
account: IAccount
): IGeneralLedgerSheetAccountBalance {
const amount = this.openingBalancesJournal.getAccountBalance(account.id);
const formattedAmount = this.formatTotalNumber(amount);
const currencyCode = this.baseCurrency;
const date = this.query.fromDate;
return { amount, formattedAmount, currencyCode, date };
}
/**
* Retrieve account closing balance.
* @param {IAccount} account
* @return {IGeneralLedgerSheetAccountBalance}
*/
private accountClosingBalance(
openingBalance: number,
transactions: IGeneralLedgerSheetAccountTransaction[]
): IGeneralLedgerSheetAccountBalance {
const amount = this.calcClosingBalance(openingBalance, transactions);
const formattedAmount = this.formatTotalNumber(amount);
const currencyCode = this.baseCurrency;
const date = this.query.toDate;
return { amount, formattedAmount, currencyCode, date };
}
private calcClosingBalance(
openingBalance: number,
transactions: IGeneralLedgerSheetAccountTransaction[]
) {
return openingBalance + sumBy(transactions, (trans) => trans.amount);
}
/**
* Retreive general ledger accounts sections.
* @param {IAccount} account
* @return {IGeneralLedgerSheetAccount}
*/
private accountMapper(account: IAccount): IGeneralLedgerSheetAccount {
const openingBalance = this.accountOpeningBalance(account);
const transactions = this.accountTransactionsMapper(
account,
openingBalance.amount
);
const closingBalance = this.accountClosingBalance(
openingBalance.amount,
transactions
);
return {
id: account.id,
name: account.name,
code: account.code,
index: account.index,
parentAccountId: account.parentAccountId,
openingBalance,
transactions,
closingBalance,
};
}
/**
* Retrieve mapped accounts with general ledger transactions and opeing/closing balance.
* @param {IAccount[]} accounts -
* @return {IGeneralLedgerSheetAccount[]}
*/
private accountsWalker(accounts: IAccount[]): IGeneralLedgerSheetAccount[] {
return (
accounts
.map((account: IAccount) => this.accountMapper(account))
// Filter general ledger accounts that have no transactions
// when`noneTransactions` is on.
.filter(
(generalLedgerAccount: IGeneralLedgerSheetAccount) =>
!(
generalLedgerAccount.transactions.length === 0 &&
this.query.noneTransactions
)
)
);
}
/**
* Retrieve general ledger report data.
* @return {IGeneralLedgerSheetAccount[]}
*/
public reportData(): IGeneralLedgerSheetAccount[] {
return this.accountsWalker(this.accounts);
}
}

View File

@@ -0,0 +1,172 @@
import { Service, Inject } from 'typedi';
import moment from 'moment';
import { ServiceError } from '@/exceptions';
import { difference } from 'lodash';
import { IGeneralLedgerSheetQuery, IGeneralLedgerMeta } from '@/interfaces';
import TenancyService from '@/services/Tenancy/TenancyService';
import Journal from '@/services/Accounting/JournalPoster';
import GeneralLedgerSheet from '@/services/FinancialStatements/GeneralLedger/GeneralLedger';
import InventoryService from '@/services/Inventory/Inventory';
import { transformToMap, parseBoolean } from 'utils';
import { Tenant } from '@/system/models';
const ERRORS = {
ACCOUNTS_NOT_FOUND: 'ACCOUNTS_NOT_FOUND',
};
@Service()
export default class GeneralLedgerService {
@Inject()
tenancy: TenancyService;
@Inject()
inventoryService: InventoryService;
@Inject('logger')
logger: any;
/**
* Defaults general ledger report filter query.
* @return {IBalanceSheetQuery}
*/
get defaultQuery() {
return {
fromDate: moment().startOf('year').format('YYYY-MM-DD'),
toDate: moment().endOf('year').format('YYYY-MM-DD'),
basis: 'cash',
numberFormat: {
noCents: false,
divideOn1000: false,
},
noneZero: false,
accountsIds: [],
};
}
/**
* Validates accounts existance on the storage.
* @param {number} tenantId
* @param {number[]} accountsIds
*/
async validateAccountsExistance(tenantId: number, accountsIds: number[]) {
const { Account } = this.tenancy.models(tenantId);
const storedAccounts = await Account.query().whereIn('id', accountsIds);
const storedAccountsIds = storedAccounts.map((a) => a.id);
if (difference(accountsIds, storedAccountsIds).length > 0) {
throw new ServiceError(ERRORS.ACCOUNTS_NOT_FOUND);
}
}
/**
* Retrieve the balance sheet meta.
* @param {number} tenantId -
* @returns {IGeneralLedgerMeta}
*/
reportMetadata(tenantId: number): IGeneralLedgerMeta {
const settings = this.tenancy.settings(tenantId);
const isCostComputeRunning = this.inventoryService
.isItemsCostComputeRunning(tenantId);
const organizationName = settings.get({
group: 'organization',
key: 'name',
});
const baseCurrency = settings.get({
group: 'organization',
key: 'base_currency',
});
return {
isCostComputeRunning: parseBoolean(isCostComputeRunning, false),
organizationName,
baseCurrency
};
}
/**
* Retrieve general ledger report statement.
* ----------
* @param {number} tenantId
* @param {IGeneralLedgerSheetQuery} query
* @return {IGeneralLedgerStatement}
*/
async generalLedger(
tenantId: number,
query: IGeneralLedgerSheetQuery
): Promise<{
data: any;
query: IGeneralLedgerSheetQuery;
meta: IGeneralLedgerMeta
}> {
const {
accountRepository,
transactionsRepository,
contactRepository
} = this.tenancy.repositories(tenantId);
const i18n = this.tenancy.i18n(tenantId);
const tenant = await Tenant.query()
.findById(tenantId)
.withGraphFetched('metadata');
const filter = {
...this.defaultQuery,
...query,
};
// Retrieve all accounts with associated type from the storage.
const accounts = await accountRepository.all();
const accountsGraph = await accountRepository.getDependencyGraph();
// Retrieve all contacts on the storage.
const contacts = await contactRepository.all();
const contactsByIdMap = transformToMap(contacts, 'id');
// Retreive journal transactions from/to the given date.
const transactions = await transactionsRepository.journal({
fromDate: filter.fromDate,
toDate: filter.toDate,
branchesIds: filter.branchesIds
});
// Retreive opening balance credit/debit sumation.
const openingBalanceTrans = await transactionsRepository.journal({
toDate: moment(filter.fromDate).subtract(1, 'day'),
sumationCreditDebit: true,
branchesIds: filter.branchesIds
});
// Transform array transactions to journal collection.
const transactionsJournal = Journal.fromTransactions(
transactions,
tenantId,
accountsGraph
);
// Accounts opening transactions.
const openingTransJournal = Journal.fromTransactions(
openingBalanceTrans,
tenantId,
accountsGraph
);
// General ledger report instance.
const generalLedgerInstance = new GeneralLedgerSheet(
tenantId,
filter,
accounts,
contactsByIdMap,
transactionsJournal,
openingTransJournal,
tenant.metadata.baseCurrency,
i18n
);
// Retrieve general ledger report data.
const reportData = generalLedgerInstance.reportData();
return {
data: reportData,
query: filter,
meta: this.reportMetadata(tenantId),
};
}
}