mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-16 04:40:32 +00:00
fix: customer balance and transactions report.
This commit is contained in:
@@ -4,11 +4,13 @@ import {
|
||||
IContactBalanceSummaryQuery,
|
||||
IContactBalanceSummaryAmount,
|
||||
IContactBalanceSummaryPercentage,
|
||||
IContactBalanceSummaryTotal
|
||||
IContactBalanceSummaryTotal,
|
||||
} from './ContactBalanceSummary';
|
||||
|
||||
export interface ICustomerBalanceSummaryQuery
|
||||
extends IContactBalanceSummaryQuery {}
|
||||
extends IContactBalanceSummaryQuery {
|
||||
customersIds?: number[];
|
||||
}
|
||||
|
||||
export interface ICustomerBalanceSummaryAmount
|
||||
extends IContactBalanceSummaryAmount {}
|
||||
@@ -22,7 +24,8 @@ export interface ICustomerBalanceSummaryCustomer {
|
||||
percentageOfColumn?: ICustomerBalanceSummaryPercentage;
|
||||
}
|
||||
|
||||
export interface ICustomerBalanceSummaryTotal extends IContactBalanceSummaryTotal {
|
||||
export interface ICustomerBalanceSummaryTotal
|
||||
extends IContactBalanceSummaryTotal {
|
||||
total: ICustomerBalanceSummaryAmount;
|
||||
percentageOfColumn?: ICustomerBalanceSummaryPercentage;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ export interface ILedger {
|
||||
whereContactId(contactId: number): ILedger;
|
||||
whereFromDate(fromDate: Date | string): ILedger;
|
||||
whereToDate(toDate: Date | string): ILedger;
|
||||
getClosingBalance(): number;
|
||||
}
|
||||
|
||||
export interface ILedgerEntry {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { get } from 'lodash';
|
||||
import * as R from 'ramda';
|
||||
import {
|
||||
IJournalPoster,
|
||||
ILedger,
|
||||
ICustomer,
|
||||
ICustomerBalanceSummaryCustomer,
|
||||
ICustomerBalanceSummaryQuery,
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
import { ContactBalanceSummaryReport } from '../ContactBalanceSummary/ContactBalanceSummary';
|
||||
|
||||
export class CustomerBalanceSummaryReport extends ContactBalanceSummaryReport {
|
||||
readonly receivableLedger: IJournalPoster;
|
||||
readonly ledger: ILedger;
|
||||
readonly baseCurrency: string;
|
||||
readonly customers: ICustomer[];
|
||||
readonly filter: ICustomerBalanceSummaryQuery;
|
||||
@@ -25,14 +25,14 @@ export class CustomerBalanceSummaryReport extends ContactBalanceSummaryReport {
|
||||
* @param {string} baseCurrency
|
||||
*/
|
||||
constructor(
|
||||
receivableLedger: IJournalPoster,
|
||||
ledger: ILedger,
|
||||
customers: ICustomer[],
|
||||
filter: ICustomerBalanceSummaryQuery,
|
||||
baseCurrency: string
|
||||
) {
|
||||
super();
|
||||
|
||||
this.receivableLedger = receivableLedger;
|
||||
this.ledger = ledger;
|
||||
this.baseCurrency = baseCurrency;
|
||||
this.customers = customers;
|
||||
this.filter = filter;
|
||||
@@ -45,12 +45,13 @@ export class CustomerBalanceSummaryReport extends ContactBalanceSummaryReport {
|
||||
* @returns {ICustomerBalanceSummaryCustomer}
|
||||
*/
|
||||
private customerMapper(customer: ICustomer): ICustomerBalanceSummaryCustomer {
|
||||
const customerBalance = this.receivableLedger.get(customer.id);
|
||||
const balanceAmount = get(customerBalance, 'balance', 0);
|
||||
const closingBalance = this.ledger
|
||||
.whereContactId(customer.id)
|
||||
.getClosingBalance();
|
||||
|
||||
return {
|
||||
customerName: customer.displayName,
|
||||
total: this.getContactTotalFormat(balanceAmount),
|
||||
total: this.getContactTotalFormat(closingBalance),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -96,7 +97,11 @@ export class CustomerBalanceSummaryReport extends ContactBalanceSummaryReport {
|
||||
};
|
||||
}
|
||||
|
||||
reportColumns() {
|
||||
/**
|
||||
* Retrieve the report statement columns
|
||||
* @returns
|
||||
*/
|
||||
public reportColumns() {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import { Inject } from 'typedi';
|
||||
import moment from 'moment';
|
||||
import { map } from 'lodash';
|
||||
import { isEmpty, map } from 'lodash';
|
||||
import TenancyService from 'services/Tenancy/TenancyService';
|
||||
import * as R from 'ramda';
|
||||
import { transformToMap } from 'utils';
|
||||
import {
|
||||
ICustomerBalanceSummaryService,
|
||||
ICustomerBalanceSummaryQuery,
|
||||
ICustomerBalanceSummaryStatement,
|
||||
ICustomer
|
||||
} from 'interfaces';
|
||||
import { CustomerBalanceSummaryReport } from './CustomerBalanceSummary';
|
||||
import { ACCOUNT_TYPE } from 'data/AccountTypes';
|
||||
import Ledger from 'services/Accounting/Ledger';
|
||||
|
||||
export default class CustomerBalanceSummaryService
|
||||
implements ICustomerBalanceSummaryService {
|
||||
@@ -42,39 +43,64 @@ export default class CustomerBalanceSummaryService
|
||||
};
|
||||
}
|
||||
|
||||
customersBalancesQuery(query) {
|
||||
query.groupBy('contactId');
|
||||
query.sum('credit as credit');
|
||||
query.sum('debit as debit');
|
||||
query.select('contactId');
|
||||
/**
|
||||
* Retrieve the A/R accounts.
|
||||
* @param tenantId
|
||||
* @returns
|
||||
*/
|
||||
private getReceivableAccounts(tenantId: number) {
|
||||
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
|
||||
* @param {number} tenantId
|
||||
* @returns
|
||||
*/
|
||||
async getCustomersCreditDebitTotals(tenantId: number) {
|
||||
const { AccountTransaction, Account } = this.tenancy.models(tenantId);
|
||||
private async getReportCustomersTransactions(tenantId: number, asDate: any) {
|
||||
const { AccountTransaction } = this.tenancy.models(tenantId);
|
||||
|
||||
const receivableAccounts = await Account.query().where(
|
||||
'accountType',
|
||||
ACCOUNT_TYPE.ACCOUNTS_RECEIVABLE
|
||||
);
|
||||
// Retrieve the receivable accounts A/R.
|
||||
const receivableAccounts = await this.getReceivableAccounts(tenantId);
|
||||
const receivableAccountsIds = map(receivableAccounts, 'id');
|
||||
|
||||
const customersTotals = await AccountTransaction.query().onBuild((query) => {
|
||||
query.whereIn('accountId', receivableAccountsIds);
|
||||
this.customersBalancesQuery(query);
|
||||
});
|
||||
// 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');
|
||||
}
|
||||
);
|
||||
const commonProps = { accountNormal: 'debit', date: asDate };
|
||||
|
||||
return R.compose(
|
||||
(customers) => transformToMap(customers, 'contactId'),
|
||||
(customers) => customers.map((customer) => ({
|
||||
...customer,
|
||||
balance: customer.debit - customer.credit
|
||||
})),
|
||||
)(customersTotals);
|
||||
return R.map(R.merge(commonProps))(customersTranasctions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the report customers.
|
||||
* @param {number} tenantId
|
||||
* @param {number[]} customersIds
|
||||
* @returns {ICustomer[]}
|
||||
*/
|
||||
private getReportCustomers(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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,19 +113,15 @@ export default class CustomerBalanceSummaryService
|
||||
tenantId: number,
|
||||
query: ICustomerBalanceSummaryQuery
|
||||
): Promise<ICustomerBalanceSummaryStatement> {
|
||||
const { Customer } = this.tenancy.models(tenantId);
|
||||
|
||||
// Settings tenant service.
|
||||
const settings = this.tenancy.settings(tenantId);
|
||||
const baseCurrency = settings.get({
|
||||
group: 'organization',
|
||||
key: 'base_currency',
|
||||
});
|
||||
// Merges the default query and request query.
|
||||
const filter = { ...this.defaultQuery, ...query };
|
||||
|
||||
const filter = {
|
||||
...this.defaultQuery,
|
||||
...query,
|
||||
};
|
||||
this.logger.info(
|
||||
'[customer_balance_summary] trying to calculate the report.',
|
||||
{
|
||||
@@ -108,27 +130,30 @@ export default class CustomerBalanceSummaryService
|
||||
}
|
||||
);
|
||||
// Retrieve the customers list ordered by the display name.
|
||||
const customers = await Customer.query().orderBy('displayName');
|
||||
|
||||
const customers = await this.getReportCustomers(
|
||||
tenantId,
|
||||
query.customersIds
|
||||
);
|
||||
// Retrieve the customers debit/credit totals.
|
||||
const customersBalances = await this.getCustomersCreditDebitTotals(tenantId);
|
||||
const customersTransactions = await this.getReportCustomersTransactions(
|
||||
tenantId,
|
||||
filter.asDate
|
||||
);
|
||||
// Ledger query.
|
||||
const ledger = new Ledger(customersTransactions);
|
||||
|
||||
// Report instance.
|
||||
const reportInstance = new CustomerBalanceSummaryReport(
|
||||
customersBalances,
|
||||
const report = new CustomerBalanceSummaryReport(
|
||||
ledger,
|
||||
customers,
|
||||
filter,
|
||||
baseCurrency
|
||||
);
|
||||
// Retrieve the report statement.
|
||||
const reportData = reportInstance.reportData();
|
||||
|
||||
// Retrieve the report columns.
|
||||
const reportColumns = reportInstance.reportColumns();
|
||||
|
||||
return {
|
||||
data: reportData,
|
||||
columns: reportColumns,
|
||||
data: report.reportData(),
|
||||
columns: report.reportColumns(),
|
||||
query: filter
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,7 +142,6 @@ export default class TransactionsByCustomersService
|
||||
...this.defaultQuery,
|
||||
...query,
|
||||
};
|
||||
|
||||
const accountsGraph = await accountRepository.getDependencyGraph();
|
||||
const customers = await Customer.query().orderBy('displayName');
|
||||
|
||||
@@ -181,6 +180,7 @@ export default class TransactionsByCustomersService
|
||||
return {
|
||||
data: reportData,
|
||||
columns: reportColumns,
|
||||
query: filter,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,11 @@ export default class TransactionsByCustomersTableRows extends TransactionsByCont
|
||||
* @returns {ITableRow[]}
|
||||
*/
|
||||
private customerDetails(customer: ITransactionsByCustomersCustomer) {
|
||||
const columns = [{ key: 'customerName', accessor: 'customerName' }];
|
||||
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] }),
|
||||
|
||||
Reference in New Issue
Block a user