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