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,111 @@
import { isEmpty } from 'lodash';
import * as R from 'ramda';
import {
ILedger,
ICustomer,
ICustomerBalanceSummaryCustomer,
ICustomerBalanceSummaryQuery,
ICustomerBalanceSummaryData,
INumberFormatQuery,
} from '@/interfaces';
import { ContactBalanceSummaryReport } from '../ContactBalanceSummary/ContactBalanceSummary';
export class CustomerBalanceSummaryReport extends ContactBalanceSummaryReport {
readonly ledger: ILedger;
readonly baseCurrency: string;
readonly customers: ICustomer[];
readonly filter: ICustomerBalanceSummaryQuery;
readonly numberFormat: INumberFormatQuery;
/**
* Constructor method.
* @param {IJournalPoster} receivableLedger
* @param {ICustomer[]} customers
* @param {ICustomerBalanceSummaryQuery} filter
* @param {string} baseCurrency
*/
constructor(
ledger: ILedger,
customers: ICustomer[],
filter: ICustomerBalanceSummaryQuery,
baseCurrency: string
) {
super();
this.ledger = ledger;
this.baseCurrency = baseCurrency;
this.customers = customers;
this.filter = filter;
this.numberFormat = this.filter.numberFormat;
}
/**
* Customer section mapper.
* @param {ICustomer} customer
* @returns {ICustomerBalanceSummaryCustomer}
*/
private customerMapper = (
customer: ICustomer
): ICustomerBalanceSummaryCustomer => {
const closingBalance = this.ledger
.whereContactId(customer.id)
.getClosingBalance();
return {
id: customer.id,
customerName: customer.displayName,
total: this.getContactTotalFormat(closingBalance),
};
};
/**
* Mappes the customer model object to customer balance summary section.
* @param {ICustomer[]} customers - Customers.
* @returns {ICustomerBalanceSummaryCustomer[]}
*/
private customersMapper = (
customers: ICustomer[]
): ICustomerBalanceSummaryCustomer[] => {
return customers.map(this.customerMapper);
};
/**
* Detarmines whether the customers post filter is active.
* @returns {boolean}
*/
private isCustomersPostFilter = () => {
return isEmpty(this.filter.customersIds);
};
/**
* Retrieve the customers sections of the report.
* @param {ICustomer} customers
* @returns {ICustomerBalanceSummaryCustomer[]}
*/
private getCustomersSection = (
customers: ICustomer[]
): ICustomerBalanceSummaryCustomer[] => {
return R.compose(
R.when(this.isCustomersPostFilter, this.contactsFilter),
R.when(
R.always(this.filter.percentageColumn),
this.contactCamparsionPercentageOfColumn
),
this.customersMapper
)(customers);
};
/**
* Retrieve the report statement data.
* @returns {ICustomerBalanceSummaryData}
*/
public reportData = (): ICustomerBalanceSummaryData => {
const customersSections = this.getCustomersSection(this.customers);
const customersTotal = this.getContactsTotalSection(customersSections);
return {
customers: customersSections,
total: customersTotal,
};
};
}

View File

@@ -0,0 +1,69 @@
import { Inject, Service } from 'typedi';
import { map, isEmpty } from 'lodash';
import { ICustomer, IAccount } from '@/interfaces';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { ACCOUNT_TYPE } from '@/data/AccountTypes';
@Service()
export default class CustomerBalanceSummaryRepository {
@Inject()
tenancy: HasTenancyService;
/**
* Retrieve the report customers.
* @param {number} tenantId
* @param {number[]} customersIds
* @returns {ICustomer[]}
*/
public getCustomers(tenantId: number, customersIds: number[]): ICustomer[] {
const { Customer } = this.tenancy.models(tenantId);
return Customer.query()
.orderBy('displayName')
.onBuild((query) => {
if (!isEmpty(customersIds)) {
query.whereIn('id', customersIds);
}
});
}
/**
* Retrieve the A/R accounts.
* @param {number} tenantId
* @returns {Promise<IAccount[]>}
*/
public getReceivableAccounts(tenantId: number): Promise<IAccount> {
const { Account } = this.tenancy.models(tenantId);
return Account.query().where(
'accountType',
ACCOUNT_TYPE.ACCOUNTS_RECEIVABLE
);
}
/**
* Retrieve the customers credit/debit totals
* @param {number} tenantId
* @returns
*/
public async getCustomersTransactions(tenantId: number, asDate: any) {
const { AccountTransaction } = this.tenancy.models(tenantId);
// Retrieve the receivable accounts A/R.
const receivableAccounts = await this.getReceivableAccounts(tenantId);
const receivableAccountsIds = map(receivableAccounts, 'id');
// Retrieve the customers transactions of A/R accounts.
const customersTranasctions = await AccountTransaction.query().onBuild(
(query) => {
query.whereIn('accountId', receivableAccountsIds);
query.modify('filterDateRange', null, asDate);
query.groupBy('contactId');
query.sum('credit as credit');
query.sum('debit as debit');
query.select('contactId');
}
);
return customersTranasctions;
}
}

View File

@@ -0,0 +1,122 @@
import { Inject } from 'typedi';
import moment from 'moment';
import { isEmpty, map } from 'lodash';
import TenancyService from '@/services/Tenancy/TenancyService';
import * as R from 'ramda';
import {
ICustomerBalanceSummaryService,
ICustomerBalanceSummaryQuery,
ICustomerBalanceSummaryStatement,
ICustomer,
ILedgerEntry,
} from '@/interfaces';
import { CustomerBalanceSummaryReport } from './CustomerBalanceSummary';
import Ledger from '@/services/Accounting/Ledger';
import CustomerBalanceSummaryRepository from './CustomerBalanceSummaryRepository';
import { Tenant } from '@/system/models';
export default class CustomerBalanceSummaryService
implements ICustomerBalanceSummaryService
{
@Inject()
tenancy: TenancyService;
@Inject('logger')
logger: any;
@Inject()
reportRepository: CustomerBalanceSummaryRepository;
/**
* Defaults balance sheet filter query.
* @return {ICustomerBalanceSummaryQuery}
*/
get defaultQuery(): ICustomerBalanceSummaryQuery {
return {
asDate: moment().format('YYYY-MM-DD'),
numberFormat: {
precision: 2,
divideOn1000: false,
showZero: false,
formatMoney: 'total',
negativeFormat: 'mines',
},
percentageColumn: false,
noneZero: false,
noneTransactions: true,
};
}
/**
* Retrieve the customers ledger entries mapped from accounts transactions.
* @param {number} tenantId
* @param {Date|string} asDate
* @returns {Promise<ILedgerEntry[]>}
*/
private async getReportCustomersEntries(
tenantId: number,
asDate: Date | string
): Promise<ILedgerEntry[]> {
const transactions = await this.reportRepository.getCustomersTransactions(
tenantId,
asDate
);
const commonProps = { accountNormal: 'debit', date: asDate };
return R.map(R.merge(commonProps))(transactions);
}
/**
* Retrieve the statment of customer balance summary report.
* @param {number} tenantId
* @param {ICustomerBalanceSummaryQuery} query
* @return {Promise<ICustomerBalanceSummaryStatement>}
*/
async customerBalanceSummary(
tenantId: number,
query: ICustomerBalanceSummaryQuery
): Promise<ICustomerBalanceSummaryStatement> {
const tenant = await Tenant.query()
.findById(tenantId)
.withGraphFetched('metadata');
// Merges the default query and request query.
const filter = { ...this.defaultQuery, ...query };
this.logger.info(
'[customer_balance_summary] trying to calculate the report.',
{
filter,
tenantId,
}
);
// Retrieve the customers list ordered by the display name.
const customers = await this.reportRepository.getCustomers(
tenantId,
query.customersIds
);
// Retrieve the customers debit/credit totals.
const customersEntries = await this.getReportCustomersEntries(
tenantId,
filter.asDate
);
// Ledger query.
const ledger = new Ledger(customersEntries);
// Report instance.
const report = new CustomerBalanceSummaryReport(
ledger,
customers,
filter,
tenant.metadata.baseCurrency,
);
return {
data: report.reportData(),
query: filter,
};
}
}

View File

@@ -0,0 +1,150 @@
import * as R from 'ramda';
import {
ICustomerBalanceSummaryData,
ICustomerBalanceSummaryCustomer,
ICustomerBalanceSummaryTotal,
ITableRow,
IColumnMapperMeta,
ICustomerBalanceSummaryQuery,
ITableColumn,
} from '@/interfaces';
import { tableMapper, tableRowMapper } from 'utils';
enum TABLE_ROWS_TYPES {
CUSTOMER = 'CUSTOMER',
TOTAL = 'TOTAL',
}
export default class CustomerBalanceSummaryTable {
report: ICustomerBalanceSummaryData;
query: ICustomerBalanceSummaryQuery;
i18n: any;
/**
* Constructor method.
*/
constructor(
report: ICustomerBalanceSummaryData,
query: ICustomerBalanceSummaryQuery,
i18n
) {
this.report = report;
this.i18n = i18n;
this.query = query;
}
/**
* Retrieve percentage columns accessor.
* @returns {IColumnMapperMeta[]}
*/
private getPercentageColumnsAccessor = (): IColumnMapperMeta[] => {
return [
{
key: 'percentageOfColumn',
accessor: 'percentageOfColumn.formattedAmount',
},
];
};
/**
* Retrieve customer node columns accessor.
* @returns {IColumnMapperMeta[]}
*/
private getCustomerColumnsAccessor = (): IColumnMapperMeta[] => {
const columns = [
{ key: 'customerName', accessor: 'customerName' },
{ key: 'total', accessor: 'total.formattedAmount' },
];
return R.compose(
R.concat(columns),
R.when(
R.always(this.query.percentageColumn),
R.concat(this.getPercentageColumnsAccessor())
)
)([]);
};
/**
* Transformes the customers to table rows.
* @param {ICustomerBalanceSummaryCustomer[]} customers
* @returns {ITableRow[]}
*/
private customersTransformer(
customers: ICustomerBalanceSummaryCustomer[]
): ITableRow[] {
const columns = this.getCustomerColumnsAccessor();
return tableMapper(customers, columns, {
rowTypes: [TABLE_ROWS_TYPES.CUSTOMER],
});
}
/**
* Retrieve total node columns accessor.
* @returns {IColumnMapperMeta[]}
*/
private getTotalColumnsAccessor = (): IColumnMapperMeta[] => {
const columns = [
{ key: 'total', value: this.i18n.__('Total') },
{ key: 'total', accessor: 'total.formattedAmount' },
];
return R.compose(
R.concat(columns),
R.when(
R.always(this.query.percentageColumn),
R.concat(this.getPercentageColumnsAccessor())
)
)([]);
};
/**
* Transformes the total to table row.
* @param {ICustomerBalanceSummaryTotal} total
* @returns {ITableRow}
*/
private totalTransformer = (
total: ICustomerBalanceSummaryTotal
): ITableRow => {
const columns = this.getTotalColumnsAccessor();
return tableRowMapper(total, columns, {
rowTypes: [TABLE_ROWS_TYPES.TOTAL],
});
};
/**
* Transformes the customer balance summary to table rows.
* @param {ICustomerBalanceSummaryData} customerBalanceSummary
* @returns {ITableRow[]}
*/
public tableRows(): ITableRow[] {
const customers = this.customersTransformer(this.report.customers);
const total = this.totalTransformer(this.report.total);
return customers.length > 0 ? [...customers, total] : [];
}
/**
* Retrieve the report statement columns
* @returns {ITableColumn[]}
*/
public tableColumns = (): ITableColumn[] => {
const columns = [
{
key: 'name',
label: this.i18n.__('contact_summary_balance.account_name'),
},
{ key: 'total', label: this.i18n.__('contact_summary_balance.total') },
];
return R.compose(
R.when(
R.always(this.query.percentageColumn),
R.append({
key: 'percentage_of_column',
label: this.i18n.__('contact_summary_balance.percentage_column'),
})
),
R.concat(columns)
)([]);
};
}