diff --git a/server/src/interfaces/CustomerBalanceSummary.ts b/server/src/interfaces/CustomerBalanceSummary.ts index bbc2b8aa4..d89ffa33b 100644 --- a/server/src/interfaces/CustomerBalanceSummary.ts +++ b/server/src/interfaces/CustomerBalanceSummary.ts @@ -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; } diff --git a/server/src/interfaces/Ledger.ts b/server/src/interfaces/Ledger.ts index 89f829ae2..4df42ed07 100644 --- a/server/src/interfaces/Ledger.ts +++ b/server/src/interfaces/Ledger.ts @@ -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 { diff --git a/server/src/services/FinancialStatements/CustomerBalanceSummary/CustomerBalanceSummary.ts b/server/src/services/FinancialStatements/CustomerBalanceSummary/CustomerBalanceSummary.ts index af1142a1c..dba46d01e 100644 --- a/server/src/services/FinancialStatements/CustomerBalanceSummary/CustomerBalanceSummary.ts +++ b/server/src/services/FinancialStatements/CustomerBalanceSummary/CustomerBalanceSummary.ts @@ -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 []; } } diff --git a/server/src/services/FinancialStatements/CustomerBalanceSummary/CustomerBalanceSummaryService.ts b/server/src/services/FinancialStatements/CustomerBalanceSummary/CustomerBalanceSummaryService.ts index 35c46cad8..8be59fd37 100644 --- a/server/src/services/FinancialStatements/CustomerBalanceSummary/CustomerBalanceSummaryService.ts +++ b/server/src/services/FinancialStatements/CustomerBalanceSummary/CustomerBalanceSummaryService.ts @@ -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 { - 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 }; } } diff --git a/server/src/services/FinancialStatements/TransactionsByCustomer/TransactionsByCustomersService.ts b/server/src/services/FinancialStatements/TransactionsByCustomer/TransactionsByCustomersService.ts index 7293eea41..40a9c956c 100644 --- a/server/src/services/FinancialStatements/TransactionsByCustomer/TransactionsByCustomersService.ts +++ b/server/src/services/FinancialStatements/TransactionsByCustomer/TransactionsByCustomersService.ts @@ -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, }; } } diff --git a/server/src/services/FinancialStatements/TransactionsByCustomer/TransactionsByCustomersTableRows.ts b/server/src/services/FinancialStatements/TransactionsByCustomer/TransactionsByCustomersTableRows.ts index c354edf4f..505dd96b1 100644 --- a/server/src/services/FinancialStatements/TransactionsByCustomer/TransactionsByCustomersTableRows.ts +++ b/server/src/services/FinancialStatements/TransactionsByCustomer/TransactionsByCustomersTableRows.ts @@ -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] }),