mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-21 15:20:34 +00:00
add server to monorepo.
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
import * as R from 'ramda';
|
||||
import {
|
||||
ITransactionsByCustomersTransaction,
|
||||
ITransactionsByCustomersFilter,
|
||||
ITransactionsByCustomersCustomer,
|
||||
ITransactionsByCustomersData,
|
||||
INumberFormatQuery,
|
||||
ICustomer,
|
||||
} from '@/interfaces';
|
||||
import TransactionsByContact from '../TransactionsByContact/TransactionsByContact';
|
||||
import Ledger from '@/services/Accounting/Ledger';
|
||||
import { isEmpty } from 'lodash';
|
||||
|
||||
const CUSTOMER_NORMAL = 'debit';
|
||||
|
||||
export default class TransactionsByCustomers extends TransactionsByContact {
|
||||
readonly customers: ICustomer[];
|
||||
readonly ledger: Ledger;
|
||||
readonly filter: ITransactionsByCustomersFilter;
|
||||
readonly baseCurrency: string;
|
||||
readonly numberFormat: INumberFormatQuery;
|
||||
readonly accountsGraph: any;
|
||||
|
||||
/**
|
||||
* Constructor method.
|
||||
* @param {ICustomer} customers
|
||||
* @param {Map<number, IAccountTransaction[]>} transactionsLedger
|
||||
* @param {string} baseCurrency
|
||||
*/
|
||||
constructor(
|
||||
customers: ICustomer[],
|
||||
accountsGraph: any,
|
||||
ledger: Ledger,
|
||||
filter: ITransactionsByCustomersFilter,
|
||||
baseCurrency: string,
|
||||
i18n
|
||||
) {
|
||||
super();
|
||||
|
||||
this.customers = customers;
|
||||
this.accountsGraph = accountsGraph;
|
||||
this.ledger = ledger;
|
||||
this.baseCurrency = baseCurrency;
|
||||
this.filter = filter;
|
||||
this.numberFormat = this.filter.numberFormat;
|
||||
this.i18n = i18n;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the customer transactions from the given customer id and opening balance.
|
||||
* @param {number} customerId - Customer id.
|
||||
* @param {number} openingBalance - Opening balance amount.
|
||||
* @returns {ITransactionsByCustomersTransaction[]}
|
||||
*/
|
||||
private customerTransactions(
|
||||
customerId: number,
|
||||
openingBalance: number
|
||||
): ITransactionsByCustomersTransaction[] {
|
||||
const ledger = this.ledger
|
||||
.whereContactId(customerId)
|
||||
.whereFromDate(this.filter.fromDate)
|
||||
.whereToDate(this.filter.toDate);
|
||||
|
||||
const ledgerEntries = ledger.getEntries();
|
||||
|
||||
return R.compose(
|
||||
R.curry(this.contactTransactionRunningBalance)(openingBalance, 'debit'),
|
||||
R.map(this.contactTransactionMapper.bind(this))
|
||||
).bind(this)(ledgerEntries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Customer section mapper.
|
||||
* @param {ICustomer} customer
|
||||
* @returns {ITransactionsByCustomersCustomer}
|
||||
*/
|
||||
private customerMapper(
|
||||
customer: ICustomer
|
||||
): ITransactionsByCustomersCustomer {
|
||||
const openingBalance = this.getContactOpeningBalance(customer.id);
|
||||
const transactions = this.customerTransactions(customer.id, openingBalance);
|
||||
const closingBalance = this.getCustomerClosingBalance(
|
||||
transactions,
|
||||
openingBalance
|
||||
);
|
||||
const currencyCode = this.baseCurrency;
|
||||
|
||||
return {
|
||||
customerName: customer.displayName,
|
||||
openingBalance: this.getTotalAmountMeta(openingBalance, currencyCode),
|
||||
closingBalance: this.getTotalAmountMeta(closingBalance, currencyCode),
|
||||
transactions,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the vendor closing balance from the given customer transactions.
|
||||
* @param {ITransactionsByContactsTransaction[]} customerTransactions
|
||||
* @param {number} openingBalance
|
||||
* @returns
|
||||
*/
|
||||
private getCustomerClosingBalance(
|
||||
customerTransactions: ITransactionsByCustomersTransaction[],
|
||||
openingBalance: number
|
||||
): number {
|
||||
return this.getContactClosingBalance(
|
||||
customerTransactions,
|
||||
CUSTOMER_NORMAL,
|
||||
openingBalance
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {ITransactionsByCustomersCustomer[]}
|
||||
*/
|
||||
private customersMapper(
|
||||
customers: ICustomer[]
|
||||
): ITransactionsByCustomersCustomer[] {
|
||||
return R.compose(
|
||||
R.when(this.isCustomersPostFilter, this.contactsFilter),
|
||||
R.map(this.customerMapper.bind(this))
|
||||
).bind(this)(customers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the report data.
|
||||
* @returns {ITransactionsByCustomersData}
|
||||
*/
|
||||
public reportData(): ITransactionsByCustomersData {
|
||||
return this.customersMapper(this.customers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the report columns.
|
||||
*/
|
||||
public reportColumns() {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
import { isEmpty, map } from 'lodash';
|
||||
import { IAccount, IAccountTransaction } from '@/interfaces';
|
||||
import { ACCOUNT_TYPE } from '@/data/AccountTypes';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { Inject } from 'typedi';
|
||||
|
||||
export default class TransactionsByCustomersRepository {
|
||||
@Inject()
|
||||
tenancy: HasTenancyService;
|
||||
|
||||
/**
|
||||
* Retrieve the report customers.
|
||||
* @param {number} tenantId
|
||||
* @returns {Promise<ICustomer[]>}
|
||||
*/
|
||||
public async getCustomers(tenantId: number, customersIds?: number[]) {
|
||||
const { Customer } = this.tenancy.models(tenantId);
|
||||
|
||||
return Customer.query().onBuild((q) => {
|
||||
q.orderBy('displayName');
|
||||
|
||||
if (!isEmpty(customersIds)) {
|
||||
q.whereIn('id', customersIds);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the accounts receivable.
|
||||
* @param {number} tenantId
|
||||
* @returns {Promise<IAccount[]>}
|
||||
*/
|
||||
public async getReceivableAccounts(tenantId: number): Promise<IAccount[]> {
|
||||
const { Account } = this.tenancy.models(tenantId);
|
||||
|
||||
const accounts = await Account.query().where(
|
||||
'accountType',
|
||||
ACCOUNT_TYPE.ACCOUNTS_RECEIVABLE
|
||||
);
|
||||
return accounts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the customers opening balance transactions.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {number} openingDate - Opening date.
|
||||
* @param {number} customersIds - Customers ids.
|
||||
* @returns {Promise<IAccountTransaction[]>}
|
||||
*/
|
||||
public async getCustomersOpeningBalanceTransactions(
|
||||
tenantId: number,
|
||||
openingDate: Date,
|
||||
customersIds?: number[]
|
||||
): Promise<IAccountTransaction[]> {
|
||||
const { AccountTransaction } = this.tenancy.models(tenantId);
|
||||
|
||||
const receivableAccounts = await this.getReceivableAccounts(tenantId);
|
||||
const receivableAccountsIds = map(receivableAccounts, 'id');
|
||||
|
||||
const openingTransactions = await AccountTransaction.query().modify(
|
||||
'contactsOpeningBalance',
|
||||
openingDate,
|
||||
receivableAccountsIds,
|
||||
customersIds
|
||||
);
|
||||
return openingTransactions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the customers periods transactions.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {Date|string} openingDate - Opening date.
|
||||
* @param {number[]} customersIds - Customers ids.
|
||||
* @return {Promise<IAccountTransaction[]>}
|
||||
*/
|
||||
public async getCustomersPeriodTransactions(
|
||||
tenantId: number,
|
||||
fromDate: Date,
|
||||
toDate: Date
|
||||
): Promise<IAccountTransaction[]> {
|
||||
const { AccountTransaction } = this.tenancy.models(tenantId);
|
||||
|
||||
const receivableAccounts = await this.getReceivableAccounts(tenantId);
|
||||
const receivableAccountsIds = map(receivableAccounts, 'id');
|
||||
|
||||
const transactions = await AccountTransaction.query().onBuild((query) => {
|
||||
// Filter by date.
|
||||
query.modify('filterDateRange', fromDate, toDate);
|
||||
|
||||
// Filter by customers.
|
||||
query.whereNot('contactId', null);
|
||||
|
||||
// Filter by accounts.
|
||||
query.whereIn('accountId', receivableAccountsIds);
|
||||
});
|
||||
return transactions;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
import { Inject } from 'typedi';
|
||||
import * as R from 'ramda';
|
||||
import moment from 'moment';
|
||||
import TenancyService from '@/services/Tenancy/TenancyService';
|
||||
import {
|
||||
ITransactionsByCustomersService,
|
||||
ITransactionsByCustomersFilter,
|
||||
ITransactionsByCustomersStatement,
|
||||
ILedgerEntry,
|
||||
} from '@/interfaces';
|
||||
import TransactionsByCustomers from './TransactionsByCustomers';
|
||||
import Ledger from '@/services/Accounting/Ledger';
|
||||
import TransactionsByCustomersRepository from './TransactionsByCustomersRepository';
|
||||
import { Tenant } from '@/system/models';
|
||||
|
||||
export default class TransactionsByCustomersService
|
||||
implements ITransactionsByCustomersService
|
||||
{
|
||||
@Inject()
|
||||
tenancy: TenancyService;
|
||||
|
||||
@Inject('logger')
|
||||
logger: any;
|
||||
|
||||
@Inject()
|
||||
reportRepository: TransactionsByCustomersRepository;
|
||||
|
||||
/**
|
||||
* Defaults balance sheet filter query.
|
||||
* @return {ICustomerBalanceSummaryQuery}
|
||||
*/
|
||||
get defaultQuery(): ITransactionsByCustomersFilter {
|
||||
return {
|
||||
fromDate: moment().startOf('year').format('YYYY-MM-DD'),
|
||||
toDate: moment().endOf('year').format('YYYY-MM-DD'),
|
||||
numberFormat: {
|
||||
precision: 2,
|
||||
divideOn1000: false,
|
||||
showZero: false,
|
||||
formatMoney: 'total',
|
||||
negativeFormat: 'mines',
|
||||
},
|
||||
comparison: {
|
||||
percentageOfColumn: true,
|
||||
},
|
||||
noneZero: false,
|
||||
noneTransactions: true,
|
||||
customersIds: [],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the customers opening balance ledger entries.
|
||||
* @param {number} tenantId
|
||||
* @param {Date} openingDate
|
||||
* @param {number[]} customersIds
|
||||
* @returns {Promise<ILedgerEntry[]>}
|
||||
*/
|
||||
private async getCustomersOpeningBalanceEntries(
|
||||
tenantId: number,
|
||||
openingDate: Date,
|
||||
customersIds?: number[]
|
||||
): Promise<ILedgerEntry[]> {
|
||||
const openingTransactions =
|
||||
await this.reportRepository.getCustomersOpeningBalanceTransactions(
|
||||
tenantId,
|
||||
openingDate,
|
||||
customersIds
|
||||
);
|
||||
|
||||
return R.compose(
|
||||
R.map(R.assoc('date', openingDate)),
|
||||
R.map(R.assoc('accountNormal', 'debit'))
|
||||
)(openingTransactions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the customers periods ledger entries.
|
||||
* @param {number} tenantId
|
||||
* @param {Date} fromDate
|
||||
* @param {Date} toDate
|
||||
* @returns {Promise<ILedgerEntry[]>}
|
||||
*/
|
||||
private async getCustomersPeriodsEntries(
|
||||
tenantId: number,
|
||||
fromDate: Date | string,
|
||||
toDate: Date | string
|
||||
): Promise<ILedgerEntry[]> {
|
||||
const transactions =
|
||||
await this.reportRepository.getCustomersPeriodTransactions(
|
||||
tenantId,
|
||||
fromDate,
|
||||
toDate
|
||||
);
|
||||
return R.compose(
|
||||
R.map(R.assoc('accountNormal', 'debit')),
|
||||
R.map((trans) => ({
|
||||
...trans,
|
||||
referenceTypeFormatted: trans.referenceTypeFormatted,
|
||||
}))
|
||||
)(transactions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve transactions by by the customers.
|
||||
* @param {number} tenantId
|
||||
* @param {ITransactionsByCustomersFilter} query
|
||||
* @return {Promise<ITransactionsByCustomersStatement>}
|
||||
*/
|
||||
public async transactionsByCustomers(
|
||||
tenantId: number,
|
||||
query: ITransactionsByCustomersFilter
|
||||
): Promise<ITransactionsByCustomersStatement> {
|
||||
const { accountRepository } = this.tenancy.repositories(tenantId);
|
||||
const i18n = this.tenancy.i18n(tenantId);
|
||||
|
||||
// Retrieve tenant information.
|
||||
const tenant = await Tenant.query()
|
||||
.findById(tenantId)
|
||||
.withGraphFetched('metadata');
|
||||
|
||||
const filter = {
|
||||
...this.defaultQuery,
|
||||
...query,
|
||||
};
|
||||
const accountsGraph = await accountRepository.getDependencyGraph();
|
||||
|
||||
// Retrieve the report customers.
|
||||
const customers = await this.reportRepository.getCustomers(
|
||||
tenantId,
|
||||
filter.customersIds
|
||||
);
|
||||
|
||||
const openingBalanceDate = moment(filter.fromDate)
|
||||
.subtract(1, 'days')
|
||||
.toDate();
|
||||
|
||||
// Retrieve all ledger transactions of the opening balance of.
|
||||
const openingBalanceEntries = await this.getCustomersOpeningBalanceEntries(
|
||||
tenantId,
|
||||
openingBalanceDate
|
||||
);
|
||||
// Retrieve all ledger transactions between opeing and closing period.
|
||||
const customersTransactions = await this.getCustomersPeriodsEntries(
|
||||
tenantId,
|
||||
query.fromDate,
|
||||
query.toDate
|
||||
);
|
||||
// Concats the opening balance and period customer ledger transactions.
|
||||
const journalTransactions = [
|
||||
...openingBalanceEntries,
|
||||
...customersTransactions,
|
||||
];
|
||||
const journal = new Ledger(journalTransactions);
|
||||
|
||||
// Transactions by customers data mapper.
|
||||
const reportInstance = new TransactionsByCustomers(
|
||||
customers,
|
||||
accountsGraph,
|
||||
journal,
|
||||
filter,
|
||||
tenant.metadata.baseCurrency,
|
||||
i18n
|
||||
);
|
||||
|
||||
return {
|
||||
data: reportInstance.reportData(),
|
||||
columns: reportInstance.reportColumns(),
|
||||
query: filter,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
import * as R from 'ramda';
|
||||
import { tableRowMapper, tableMapper } from 'utils';
|
||||
import { ITransactionsByCustomersCustomer, ITableRow } from '@/interfaces';
|
||||
import TransactionsByContactsTableRows from '../TransactionsByContact/TransactionsByContactTableRows';
|
||||
|
||||
enum ROW_TYPE {
|
||||
OPENING_BALANCE = 'OPENING_BALANCE',
|
||||
CLOSING_BALANCE = 'CLOSING_BALANCE',
|
||||
TRANSACTION = 'TRANSACTION',
|
||||
CUSTOMER = 'CUSTOMER',
|
||||
}
|
||||
|
||||
export default class TransactionsByCustomersTableRows extends TransactionsByContactsTableRows {
|
||||
private customersTransactions: ITransactionsByCustomersCustomer[];
|
||||
|
||||
/**
|
||||
* Constructor method.
|
||||
* @param {ITransactionsByCustomersCustomer[]} customersTransactions - Customers transactions.
|
||||
*/
|
||||
constructor(
|
||||
customersTransactions: ITransactionsByCustomersCustomer[],
|
||||
i18n
|
||||
) {
|
||||
super();
|
||||
this.customersTransactions = customersTransactions;
|
||||
this.i18n = i18n;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the table row of customer details.
|
||||
* @param {ITransactionsByCustomersCustomer} customer -
|
||||
* @returns {ITableRow[]}
|
||||
*/
|
||||
private customerDetails = (customer: ITransactionsByCustomersCustomer) => {
|
||||
const columns = [
|
||||
{ key: 'customerName', accessor: 'customerName' },
|
||||
...R.repeat({ key: 'empty', value: '' }, 5),
|
||||
{
|
||||
key: 'closingBalanceValue',
|
||||
accessor: 'closingBalance.formattedAmount',
|
||||
},
|
||||
];
|
||||
|
||||
return {
|
||||
...tableRowMapper(customer, columns, { rowTypes: [ROW_TYPE.CUSTOMER] }),
|
||||
children: R.pipe(
|
||||
R.when(
|
||||
R.always(customer.transactions.length > 0),
|
||||
R.pipe(
|
||||
R.concat(this.contactTransactions(customer)),
|
||||
R.prepend(this.contactOpeningBalance(customer))
|
||||
)
|
||||
),
|
||||
R.append(this.contactClosingBalance(customer))
|
||||
)([]),
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the table rows of the customer section.
|
||||
* @param {ITransactionsByCustomersCustomer} customer
|
||||
* @returns {ITableRow[]}
|
||||
*/
|
||||
private customerRowsMapper = (customer: ITransactionsByCustomersCustomer) => {
|
||||
return R.pipe(this.customerDetails)(customer);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the table rows of transactions by customers report.
|
||||
* @param {ITransactionsByCustomersCustomer[]} customers
|
||||
* @returns {ITableRow[]}
|
||||
*/
|
||||
public tableRows = (): ITableRow[] => {
|
||||
return R.map(this.customerRowsMapper.bind(this))(
|
||||
this.customersTransactions
|
||||
);
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user