From 97d12d4294af484d2c20777ffd22bb9a65b91a2c Mon Sep 17 00:00:00 2001 From: "a.bouhuolia" Date: Wed, 5 May 2021 21:41:10 +0200 Subject: [PATCH] WIP: customer/vendor balance summary. --- .../api/controllers/FinancialStatements.ts | 9 +- .../CustomerBalanceSummary/index.ts | 2 +- .../VendorBalanceSummary/index.ts | 74 +++++++++++ .../src/interfaces/ContactBalanceSummary.ts | 49 +++++++ .../src/interfaces/CustomerBalanceSummary.ts | 36 +++-- server/src/interfaces/Table.ts | 15 +++ server/src/interfaces/VendorBalanceSummary.ts | 50 +++++++ server/src/interfaces/index.ts | 5 +- .../ContactBalanceSummary.ts | 125 ++++++++++++++++++ .../CustomerBalanceSummary.ts | 118 ++--------------- .../CustomerBalanceSummaryTableRows.ts | 54 ++------ .../FinancialStatements/FinancialSheet.ts | 1 + .../TransactionsByContact.ts | 7 + .../VendorBalanceSummary.ts | 98 ++++++++++++++ .../VendorBalanceSummaryService.ts | 109 +++++++++++++++ .../VendorBalanceSummaryTableRows.ts | 70 ++++++++++ server/src/utils/index.ts | 2 + server/src/utils/table.ts | 26 ++++ 18 files changed, 674 insertions(+), 176 deletions(-) create mode 100644 server/src/interfaces/ContactBalanceSummary.ts create mode 100644 server/src/interfaces/Table.ts create mode 100644 server/src/interfaces/VendorBalanceSummary.ts create mode 100644 server/src/services/FinancialStatements/ContactBalanceSummary/ContactBalanceSummary.ts create mode 100644 server/src/services/FinancialStatements/TransactionsByContact/TransactionsByContact.ts create mode 100644 server/src/services/FinancialStatements/VendorBalanceSummary/VendorBalanceSummary.ts create mode 100644 server/src/services/FinancialStatements/VendorBalanceSummary/VendorBalanceSummaryService.ts create mode 100644 server/src/services/FinancialStatements/VendorBalanceSummary/VendorBalanceSummaryTableRows.ts create mode 100644 server/src/utils/table.ts diff --git a/server/src/api/controllers/FinancialStatements.ts b/server/src/api/controllers/FinancialStatements.ts index 2e44fb697..5be1b3ed8 100644 --- a/server/src/api/controllers/FinancialStatements.ts +++ b/server/src/api/controllers/FinancialStatements.ts @@ -64,14 +64,15 @@ export default class FinancialStatementsService { '/customer-balance-summary', Container.get(CustomerBalanceSummaryController).router(), ); + router.use( + '/vendor-balance-summary', + Container.get(VendorBalanceSummaryController).router(), + ); router.use( '/transactions-by-customers', Container.get(TransactionsByCustomers).router(), ) - // router.use( - // '/vendor-balance-summary', - // Container.get(VendorBalanceSummaryController).router(), - // ) + return router; } } diff --git a/server/src/api/controllers/FinancialStatements/CustomerBalanceSummary/index.ts b/server/src/api/controllers/FinancialStatements/CustomerBalanceSummary/index.ts index f9214a9ab..646631f03 100644 --- a/server/src/api/controllers/FinancialStatements/CustomerBalanceSummary/index.ts +++ b/server/src/api/controllers/FinancialStatements/CustomerBalanceSummary/index.ts @@ -60,7 +60,7 @@ export default class CustomerBalanceSummaryReportController extends BaseFinancia const tableRows = this.customerBalanceSummaryTableRows.tableRowsTransformer( data ); - return res.status(200).send({ + return res.status( 200).send({ table: { rows: tableRows }, diff --git a/server/src/api/controllers/FinancialStatements/VendorBalanceSummary/index.ts b/server/src/api/controllers/FinancialStatements/VendorBalanceSummary/index.ts index e69de29bb..388ff1968 100644 --- a/server/src/api/controllers/FinancialStatements/VendorBalanceSummary/index.ts +++ b/server/src/api/controllers/FinancialStatements/VendorBalanceSummary/index.ts @@ -0,0 +1,74 @@ +import { Router, Request, Response, NextFunction } from 'express'; +import { query } from 'express-validator'; +import { Inject } from 'typedi'; +import asyncMiddleware from 'api/middleware/asyncMiddleware'; +import BaseFinancialReportController from '../BaseFinancialReportController'; +import VendorBalanceSummaryTableRows from 'services/FinancialStatements/VendorBalanceSummary/VendorBalanceSummaryTableRows'; +import VendorBalanceSummaryService from 'services/FinancialStatements/VendorBalanceSummary/VendorBalanceSummaryService'; + +export default class VendorBalanceSummaryReportController extends BaseFinancialReportController { + @Inject() + vendorBalanceSummaryService: VendorBalanceSummaryService; + + @Inject() + vendorBalanceSummaryTableRows: VendorBalanceSummaryTableRows; + + /** + * Router constructor. + */ + router() { + const router = Router(); + + router.get( + '/', + this.validationSchema, + asyncMiddleware(this.vendorBalanceSummary.bind(this)) + ); + return router; + } + + /** + * Validation schema. + */ + get validationSchema() { + return [ + ...this.sheetNumberFormatValidationSchema, + query('as_date').optional().isISO8601(), + ]; + } + + /** + * Retrieve vendors balance summary. + * @param {Request} req - + * @param {Response} res - + * @param {NextFunction} next - + */ + async vendorBalanceSummary(req: Request, res: Response, next: NextFunction) { + const { tenantId, settings } = req; + const filter = this.matchedQueryData(req); + + try { + const { + data, + columns, + query, + } = await this.vendorBalanceSummaryService.vendorBalanceSummary( + tenantId, + filter + ); + + const tableRows = this.vendorBalanceSummaryTableRows.tableRowsTransformer( + data + ); + return res.status( 200).send({ + table: { + rows: tableRows + }, + columns: this.transfromToResponse(columns), + query: this.transfromToResponse(query), + }); + } catch (error) { + next(error); + } + } +} diff --git a/server/src/interfaces/ContactBalanceSummary.ts b/server/src/interfaces/ContactBalanceSummary.ts new file mode 100644 index 000000000..9500d4f03 --- /dev/null +++ b/server/src/interfaces/ContactBalanceSummary.ts @@ -0,0 +1,49 @@ +import { INumberFormatQuery } from './FinancialStatements'; + +export interface IContactBalanceSummaryQuery { + asDate: Date; + numberFormat: INumberFormatQuery; + comparison: { + percentageOfColumn: boolean; + }; + noneTransactions: boolean; + noneZero: boolean; +} + +export interface IContactBalanceSummaryAmount { + amount: number; + formattedAmount: string; + currencyCode: string; +} +export interface IContactBalanceSummaryPercentage { + amount: number; + formattedAmount: string; +} + +export interface IContactBalanceSummaryContact { + total: IContactBalanceSummaryAmount; + percentageOfColumn?: IContactBalanceSummaryPercentage; +} + +export interface IContactBalanceSummaryTotal { + total: IContactBalanceSummaryAmount; + percentageOfColumn?: IContactBalanceSummaryPercentage; +} + +export interface ICustomerBalanceSummaryData { + customers: IContactBalanceSummaryContact[]; + total: IContactBalanceSummaryTotal; +} + +export interface ICustomerBalanceSummaryStatement { + data: ICustomerBalanceSummaryData; + columns: {}; + query: IContactBalanceSummaryQuery; +} + +export interface ICustomerBalanceSummaryService { + customerBalanceSummary( + tenantId: number, + query: IContactBalanceSummaryQuery, + ): Promise; +} diff --git a/server/src/interfaces/CustomerBalanceSummary.ts b/server/src/interfaces/CustomerBalanceSummary.ts index 856924bdf..bbc2b8aa4 100644 --- a/server/src/interfaces/CustomerBalanceSummary.ts +++ b/server/src/interfaces/CustomerBalanceSummary.ts @@ -1,24 +1,20 @@ import { INumberFormatQuery } from './FinancialStatements'; -export interface ICustomerBalanceSummaryQuery { - asDate: Date; - numberFormat: INumberFormatQuery; - comparison: { - percentageOfColumn: boolean; - }; - noneTransactions: boolean; - noneZero: boolean; -} +import { + IContactBalanceSummaryQuery, + IContactBalanceSummaryAmount, + IContactBalanceSummaryPercentage, + IContactBalanceSummaryTotal +} from './ContactBalanceSummary'; -export interface ICustomerBalanceSummaryAmount { - amount: number; - formattedAmount: string; - currencyCode: string; -} -export interface ICustomerBalanceSummaryPercentage { - amount: number; - formattedAmount: string; -} +export interface ICustomerBalanceSummaryQuery + extends IContactBalanceSummaryQuery {} + +export interface ICustomerBalanceSummaryAmount + extends IContactBalanceSummaryAmount {} + +export interface ICustomerBalanceSummaryPercentage + extends IContactBalanceSummaryPercentage {} export interface ICustomerBalanceSummaryCustomer { customerName: string; @@ -26,7 +22,7 @@ export interface ICustomerBalanceSummaryCustomer { percentageOfColumn?: ICustomerBalanceSummaryPercentage; } -export interface ICustomerBalanceSummaryTotal { +export interface ICustomerBalanceSummaryTotal extends IContactBalanceSummaryTotal { total: ICustomerBalanceSummaryAmount; percentageOfColumn?: ICustomerBalanceSummaryPercentage; } @@ -45,6 +41,6 @@ export interface ICustomerBalanceSummaryStatement { export interface ICustomerBalanceSummaryService { customerBalanceSummary( tenantId: number, - query: ICustomerBalanceSummaryQuery, + query: ICustomerBalanceSummaryQuery ): Promise; } diff --git a/server/src/interfaces/Table.ts b/server/src/interfaces/Table.ts new file mode 100644 index 000000000..f04d844e0 --- /dev/null +++ b/server/src/interfaces/Table.ts @@ -0,0 +1,15 @@ + +export interface IColumnMapperMeta { + key: string; + accessor?: string; + value?: string; +} + +export interface ITableCell { + value: string; + key: string; +} + +export type ITableRow = { + rows: ITableCell[]; +}; \ No newline at end of file diff --git a/server/src/interfaces/VendorBalanceSummary.ts b/server/src/interfaces/VendorBalanceSummary.ts new file mode 100644 index 000000000..b06cd26fd --- /dev/null +++ b/server/src/interfaces/VendorBalanceSummary.ts @@ -0,0 +1,50 @@ +import { INumberFormatQuery } from './FinancialStatements'; + +export interface IVendorBalanceSummaryQuery { + asDate: Date; + numberFormat: INumberFormatQuery; + comparison: { + percentageOfColumn: boolean; + }; + noneTransactions: boolean; + noneZero: boolean; +} + +export interface IVendorBalanceSummaryAmount { + amount: number; + formattedAmount: string; + currencyCode: string; +} +export interface IVendorBalanceSummaryPercentage { + amount: number; + formattedAmount: string; +} + +export interface IVendorBalanceSummaryVendor { + vendorName: string; + total: IVendorBalanceSummaryAmount; + percentageOfColumn?: IVendorBalanceSummaryPercentage; +} + +export interface IVendorBalanceSummaryTotal { + total: IVendorBalanceSummaryAmount; + percentageOfColumn?: IVendorBalanceSummaryPercentage; +} + +export interface IVendorBalanceSummaryData { + vendors: IVendorBalanceSummaryVendor[]; + total: IVendorBalanceSummaryTotal; +} + +export interface IVendorBalanceSummaryStatement { + data: IVendorBalanceSummaryData; + columns: {}; + query: IVendorBalanceSummaryQuery; +} + +export interface IVendorBalanceSummaryService { + vendorBalanceSummary( + tenantId: number, + query: IVendorBalanceSummaryQuery, + ): Promise; +} diff --git a/server/src/interfaces/index.ts b/server/src/interfaces/index.ts index aac33fdbd..f004523bb 100644 --- a/server/src/interfaces/index.ts +++ b/server/src/interfaces/index.ts @@ -44,4 +44,7 @@ export * from './Setup' export * from './IInventoryValuationSheet'; export * from './SalesByItemsSheet'; export * from './CustomerBalanceSummary'; -export * from './TransactionsByCustomers'; \ No newline at end of file +export * from './VendorBalanceSummary'; +export * from './ContactBalanceSummary'; +export * from './TransactionsByCustomers'; +export * from './Table'; \ No newline at end of file diff --git a/server/src/services/FinancialStatements/ContactBalanceSummary/ContactBalanceSummary.ts b/server/src/services/FinancialStatements/ContactBalanceSummary/ContactBalanceSummary.ts new file mode 100644 index 000000000..aa5474680 --- /dev/null +++ b/server/src/services/FinancialStatements/ContactBalanceSummary/ContactBalanceSummary.ts @@ -0,0 +1,125 @@ +import { sumBy } from 'lodash'; +import * as R from 'ramda'; +import FinancialSheet from '../FinancialSheet'; +import { + IContactBalanceSummaryContact, + IContactBalanceSummaryTotal, + IContactBalanceSummaryAmount, + IContactBalanceSummaryPercentage +} from 'interfaces'; + +export class ContactBalanceSummaryReport extends FinancialSheet { + readonly baseCurrency: string; + + /** + * Calculates the contact percentage of column. + * @param {number} customerBalance - Contact balance. + * @param {number} totalBalance - Total contacts balance. + * @returns {number} + */ + protected getContactPercentageOfColumn( + customerBalance: number, + totalBalance: number + ): number { + return customerBalance > 0 ? totalBalance / customerBalance : 0; + } + + /** + * Retrieve the contacts total. + * @param {IContactBalanceSummaryContact} contacts + * @returns {number} + */ + protected getContactsTotal( + contacts: IContactBalanceSummaryContact[] + ): number { + return sumBy( + contacts, + (contact: IContactBalanceSummaryContact) => contact.total.amount + ); + } + + /** + * Retrieve the contacts total section. + * @param {IContactBalanceSummaryContact[]} contacts + * @returns {IContactBalanceSummaryTotal} + */ + protected getContactsTotalSection( + contacts: IContactBalanceSummaryContact[] + ): IContactBalanceSummaryTotal { + const customersTotal = this.getContactsTotal(contacts); + + return { + total: this.getTotalFormat(customersTotal), + percentageOfColumn: this.getPercentageMeta(1), + }; + } + + /** + * Retrieve the contact summary section with percentage of column. + * @param {number} total + * @param {IContactBalanceSummaryContact} contact + * @returns {IContactBalanceSummaryContact} + */ + private contactCamparsionPercentageOfColumnMapper( + total: number, + contact: IContactBalanceSummaryContact + ): IContactBalanceSummaryContact { + const amount = this.getContactPercentageOfColumn( + total, + contact.total.amount + ); + return { + ...contact, + percentageOfColumn: this.getPercentageMeta(amount), + }; + } + + /** + * Mappes the contacts summary sections with percentage of column. + * @param {IContactBalanceSummaryContact[]} contacts - + * @return {IContactBalanceSummaryContact[]} + */ + protected contactCamparsionPercentageOfColumn( + contacts: IContactBalanceSummaryContact[] + ): IContactBalanceSummaryContact[] { + const customersTotal = this.getContactsTotal(contacts); + const camparsionPercentageOfColummn = R.curry( + this.contactCamparsionPercentageOfColumnMapper.bind(this) + )(customersTotal); + + return contacts.map(camparsionPercentageOfColummn); + } + + getContactTotalFormat(amount: number) { + return { + amount, + formattedAmount: this.formatNumber(amount), + currencyCode: this.baseCurrency, + }; + } + + /** + * Retrieve the total amount of contacts sections. + * @param {number} amount + * @returns {IContactBalanceSummaryAmount} + */ + getTotalFormat(amount: number): IContactBalanceSummaryAmount { + return { + amount, + formattedAmount: this.formatNumber(amount), + currencyCode: this.baseCurrency, + }; + } + + /** + * Retrieve the percentage amount object. + * @param {number} amount + * @returns {IContactBalanceSummaryPercentage} + */ + getPercentageMeta(amount: number): IContactBalanceSummaryPercentage { + return { + amount, + formattedAmount: this.formatPercentage(amount), + }; + } +} diff --git a/server/src/services/FinancialStatements/CustomerBalanceSummary/CustomerBalanceSummary.ts b/server/src/services/FinancialStatements/CustomerBalanceSummary/CustomerBalanceSummary.ts index 9158b3714..185392833 100644 --- a/server/src/services/FinancialStatements/CustomerBalanceSummary/CustomerBalanceSummary.ts +++ b/server/src/services/FinancialStatements/CustomerBalanceSummary/CustomerBalanceSummary.ts @@ -1,20 +1,21 @@ import { sumBy } from 'lodash'; import * as R from 'ramda'; -import FinancialSheet from '../FinancialSheet'; import { IJournalPoster, ICustomer, ICustomerBalanceSummaryCustomer, ICustomerBalanceSummaryQuery, ICustomerBalanceSummaryData, - ICustomerBalanceSummaryTotal, + INumberFormatQuery, } from 'interfaces'; +import { ContactBalanceSummaryReport } from '../ContactBalanceSummary/ContactBalanceSummary'; -export class CustomerBalanceSummaryReport extends FinancialSheet { - receivableLedger: IJournalPoster; - baseCurrency: string; - customers: ICustomer[]; - filter: ICustomerBalanceSummaryQuery; +export class CustomerBalanceSummaryReport extends ContactBalanceSummaryReport { + readonly receivableLedger: IJournalPoster; + readonly baseCurrency: string; + readonly customers: ICustomer[]; + readonly filter: ICustomerBalanceSummaryQuery; + readonly numberFormat: INumberFormatQuery; /** * Constructor method. @@ -35,21 +36,7 @@ export class CustomerBalanceSummaryReport extends FinancialSheet { this.baseCurrency = baseCurrency; this.customers = customers; this.filter = filter; - } - - getAmountMeta(amount: number) { - return { - amount, - formattedAmount: amount, - currencyCode: this.baseCurrency, - }; - } - - getPercentageMeta(amount: number) { - return { - amount, - formattedAmount: this.formatPercentage(amount), - }; + this.numberFormat = this.filter.numberFormat; } /** @@ -62,46 +49,10 @@ export class CustomerBalanceSummaryReport extends FinancialSheet { return { customerName: customer.displayName, - total: this.getAmountMeta(balance), + total: this.getContactTotalFormat(balance), }; } - - /** - * Retrieve the customer summary section with percentage of column. - * @param {number} total - * @param {ICustomerBalanceSummaryCustomer} customer - * @returns {ICustomerBalanceSummaryCustomer} - */ - private customerCamparsionPercentageOfColumnMapper( - total: number, - customer: ICustomerBalanceSummaryCustomer - ): ICustomerBalanceSummaryCustomer { - const amount = this.getCustomerPercentageOfColumn( - total, - customer.total.amount - ); - return { - ...customer, - percentageOfColumn: this.getPercentageMeta(amount), - }; - } - - /** - * Mappes the customers summary sections with percentage of column. - * @param {ICustomerBalanceSummaryCustomer[]} customers - - * @return {ICustomerBalanceSummaryCustomer[]} - */ - private customerCamparsionPercentageOfColumn( - customers: ICustomerBalanceSummaryCustomer[] - ): ICustomerBalanceSummaryCustomer[] { - const customersTotal = this.getCustomersTotal(customers); - const camparsionPercentageOfColummn = R.curry( - this.customerCamparsionPercentageOfColumnMapper.bind(this) - )(customersTotal); - - return customers.map(camparsionPercentageOfColummn); - } - + /** * Mappes the customer model object to customer balance summary section. * @param {ICustomer[]} customers - Customers. @@ -124,62 +75,19 @@ export class CustomerBalanceSummaryReport extends FinancialSheet { return R.compose( R.when( R.always(this.filter.comparison.percentageOfColumn), - this.customerCamparsionPercentageOfColumn.bind(this) + this.contactCamparsionPercentageOfColumn.bind(this) ), this.customersMapper.bind(this) ).bind(this)(customers); } - /** - * Retrieve the customers total. - * @param {ICustomerBalanceSummaryCustomer} customers - * @returns {number} - */ - private getCustomersTotal( - customers: ICustomerBalanceSummaryCustomer[] - ): number { - return sumBy( - customers, - (customer: ICustomerBalanceSummaryCustomer) => customer.total.amount - ); - } - - /** - * Calculates the customer percentage of column. - * @param {number} customerBalance - Customer balance. - * @param {number} totalBalance - Total customers balance. - * @returns {number} - */ - private getCustomerPercentageOfColumn( - customerBalance: number, - totalBalance: number - ) { - return customerBalance > 0 ? totalBalance / customerBalance : 0; - } - - /** - * Retrieve the customers total section. - * @param {ICustomer[]} customers - * @returns {ICustomerBalanceSummaryTotal} - */ - private customersTotalSection( - customers: ICustomerBalanceSummaryCustomer[] - ): ICustomerBalanceSummaryTotal { - const customersTotal = this.getCustomersTotal(customers); - - return { - total: this.getAmountMeta(customersTotal), - percentageOfColumn: this.getPercentageMeta(1), - }; - } - /** * Retrieve the report statement data. * @returns {ICustomerBalanceSummaryData} */ public reportData(): ICustomerBalanceSummaryData { const customersSections = this.getCustomersSection(this.customers); - const customersTotal = this.customersTotalSection(customersSections); + const customersTotal = this.getContactsTotalSection(customersSections); return { customers: customersSections, diff --git a/server/src/services/FinancialStatements/CustomerBalanceSummary/CustomerBalanceSummaryTableRows.ts b/server/src/services/FinancialStatements/CustomerBalanceSummary/CustomerBalanceSummaryTableRows.ts index 5dc8e5e76..7de74b716 100644 --- a/server/src/services/FinancialStatements/CustomerBalanceSummary/CustomerBalanceSummaryTableRows.ts +++ b/server/src/services/FinancialStatements/CustomerBalanceSummary/CustomerBalanceSummaryTableRows.ts @@ -1,55 +1,17 @@ -import { get } from 'lodash'; +import { Service } from 'typedi'; import { ICustomerBalanceSummaryData, ICustomerBalanceSummaryCustomer, ICustomerBalanceSummaryTotal, + ITableRow, } from 'interfaces'; -import { Service } from 'typedi'; - -interface IColumnMapperMeta { - key: string; - accessor?: string; - value?: string; -} - -interface ITableCell { - value: string; - key: string; -} - -type ITableRow = { - rows: ITableCell[]; -}; +import { tableMapper, tableRowMapper } from 'utils'; enum TABLE_ROWS_TYPES { CUSTOMER = 'CUSTOMER', TOTAL = 'TOTAL', } -function tableMapper( - data: Object[], - columns: IColumnMapperMeta[], - rowsMeta -): ITableRow[] { - return data.map((object) => tableRowMapper(object, columns, rowsMeta)); -} - -function tableRowMapper( - object: Object, - columns: IColumnMapperMeta[], - rowMeta -): ITableRow { - const cells = columns.map((column) => ({ - key: column.key, - value: column.value ? column.value : get(object, column.accessor), - })); - - return { - cells, - ...rowMeta, - }; -} - @Service() export default class CustomerBalanceSummaryTableRows { /** @@ -100,9 +62,11 @@ export default class CustomerBalanceSummaryTableRows { public tableRowsTransformer( customerBalanceSummary: ICustomerBalanceSummaryData ): ITableRow[] { - return [ - ...this.customersTransformer(customerBalanceSummary.customers), - this.totalTransformer(customerBalanceSummary.total), - ]; + const customers = this.customersTransformer( + customerBalanceSummary.customers + ); + const total = this.totalTransformer(customerBalanceSummary.total); + + return customers.length > 0 ? [...customers, total] : []; } } diff --git a/server/src/services/FinancialStatements/FinancialSheet.ts b/server/src/services/FinancialStatements/FinancialSheet.ts index e9d9af0de..083ecd7c3 100644 --- a/server/src/services/FinancialStatements/FinancialSheet.ts +++ b/server/src/services/FinancialStatements/FinancialSheet.ts @@ -9,6 +9,7 @@ export default class FinancialSheet { * Transformes the number format query to settings */ protected transfromFormatQueryToSettings(): IFormatNumberSettings { + console.log(this.numberFormat, 'XX'); const { numberFormat } = this; return { diff --git a/server/src/services/FinancialStatements/TransactionsByContact/TransactionsByContact.ts b/server/src/services/FinancialStatements/TransactionsByContact/TransactionsByContact.ts new file mode 100644 index 000000000..a7378c9a5 --- /dev/null +++ b/server/src/services/FinancialStatements/TransactionsByContact/TransactionsByContact.ts @@ -0,0 +1,7 @@ + + + +export default class TransactionsByContact extends ITransactionsByContacts { + + +} \ No newline at end of file diff --git a/server/src/services/FinancialStatements/VendorBalanceSummary/VendorBalanceSummary.ts b/server/src/services/FinancialStatements/VendorBalanceSummary/VendorBalanceSummary.ts new file mode 100644 index 000000000..f98a47972 --- /dev/null +++ b/server/src/services/FinancialStatements/VendorBalanceSummary/VendorBalanceSummary.ts @@ -0,0 +1,98 @@ +import * as R from 'ramda'; +import { + IJournalPoster, + IVendor, + IVendorBalanceSummaryVendor, + IVendorBalanceSummaryQuery, + IVendorBalanceSummaryData, + IVendorBalanceSummaryTotal, + INumberFormatQuery, +} from 'interfaces'; +import { ContactBalanceSummaryReport } from '../ContactBalanceSummary/ContactBalanceSummary'; + +export class VendorBalanceSummaryReport extends ContactBalanceSummaryReport { + readonly payableLedger: IJournalPoster; + readonly baseCurrency: string; + readonly vendors: IVendor[]; + readonly filter: IVendorBalanceSummaryQuery; + readonly numberFormat: INumberFormatQuery; + + /** + * Constructor method. + * @param {IJournalPoster} receivableLedger + * @param {IVendor[]} vendors + * @param {IVendorBalanceSummaryQuery} filter + * @param {string} baseCurrency + */ + constructor( + payableLedger: IJournalPoster, + vendors: IVendor[], + filter: IVendorBalanceSummaryQuery, + baseCurrency: string + ) { + super(); + + this.payableLedger = payableLedger; + this.baseCurrency = baseCurrency; + this.vendors = vendors; + this.filter = filter; + this.numberFormat = this.filter.numberFormat; + } + + /** + * Customer section mapper. + * @param {IVendor} vendor + * @returns {IVendorBalanceSummaryVendor} + */ + private vendorMapper(vendor: IVendor): IVendorBalanceSummaryVendor { + const balance = this.payableLedger.getContactBalance(null, vendor.id); + + return { + vendorName: vendor.displayName, + total: this.getContactTotalFormat(balance), + }; + } + + /** + * Mappes the vendor model object to vendor balance summary section. + * @param {IVendor[]} vendors - Customers. + * @returns {IVendorBalanceSummaryVendor[]} + */ + private vendorsMapper( + vendors: IVendor[] + ): IVendorBalanceSummaryVendor[] { + return vendors.map(this.vendorMapper.bind(this)); + } + + /** + * Retrieve the vendors sections of the report. + * @param {IVendor} vendors + * @returns {IVendorBalanceSummaryVendor[]} + */ + private getVendorsSection( + vendors: IVendor[] + ): IVendorBalanceSummaryVendor[] { + return R.compose( + R.when( + R.always(this.filter.comparison.percentageOfColumn), + this.contactCamparsionPercentageOfColumn.bind(this) + ), + this.vendorsMapper.bind(this) + ).bind(this)(vendors); + } + + /** + * Retrieve the report statement data. + * @returns {IVendorBalanceSummaryData} + */ + public reportData(): IVendorBalanceSummaryData { + const vendors = this.getVendorsSection(this.vendors); + const total = this.getContactsTotalSection(vendors); + + return { vendors, total }; + } + + reportColumns() { + return []; + } +} diff --git a/server/src/services/FinancialStatements/VendorBalanceSummary/VendorBalanceSummaryService.ts b/server/src/services/FinancialStatements/VendorBalanceSummary/VendorBalanceSummaryService.ts new file mode 100644 index 000000000..45c97ea0c --- /dev/null +++ b/server/src/services/FinancialStatements/VendorBalanceSummary/VendorBalanceSummaryService.ts @@ -0,0 +1,109 @@ +import { Inject } from 'typedi'; +import moment from 'moment'; +import TenancyService from 'services/Tenancy/TenancyService'; +import Journal from 'services/Accounting/JournalPoster'; +import { + IVendorBalanceSummaryService, + IVendorBalanceSummaryQuery, + IVendorBalanceSummaryStatement, +} from 'interfaces'; +import { VendorBalanceSummaryReport } from './VendorBalanceSummary'; + +export default class VendorBalanceSummaryService + implements IVendorBalanceSummaryService { + + @Inject() + tenancy: TenancyService; + + @Inject('logger') + logger: any; + + /** + * Defaults balance sheet filter query. + * @return {IVendorBalanceSummaryQuery} + */ + get defaultQuery(): IVendorBalanceSummaryQuery { + return { + asDate: moment().format('YYYY-MM-DD'), + numberFormat: { + precision: 2, + divideOn1000: false, + showZero: false, + formatMoney: 'total', + negativeFormat: 'mines', + }, + comparison: { + percentageOfColumn: true, + }, + noneZero: false, + noneTransactions: false, + }; + } + + /** + * Retrieve the statment of customer balance summary report. + * @param {number} tenantId - Tenant id. + * @param {IVendorBalanceSummaryQuery} query - + * @return {Promise} + */ + async vendorBalanceSummary( + tenantId: number, + query: IVendorBalanceSummaryQuery + ): Promise { + const { + accountRepository, + transactionsRepository, + } = this.tenancy.repositories(tenantId); + + const { Vendor } = this.tenancy.models(tenantId); + + // Settings tenant service. + const settings = this.tenancy.settings(tenantId); + const baseCurrency = settings.get({ + group: 'organization', key: 'base_currency', + }); + + const filter = { + ...this.defaultQuery, + ...query, + }; + this.logger.info('[customer_balance_summary] trying to calculate the report.', { + filter, + tenantId, + }); + // Retrieve all accounts on the storage. + const accounts = await accountRepository.all(); + const accountsGraph = await accountRepository.getDependencyGraph(); + + // Retrieve all journal transactions based on the given query. + const transactions = await transactionsRepository.journal({ + toDate: query.asDate, + }); + // Transform transactions to journal collection. + const transactionsJournal = Journal.fromTransactions( + transactions, + tenantId, + accountsGraph + ); + // Retrieve the customers list ordered by the display name. + const vendors = await Vendor.query().orderBy('displayName'); + + // Report instance. + const reportInstance = new VendorBalanceSummaryReport( + transactionsJournal, + vendors, + filter, + baseCurrency, + ); + // Retrieve the report statement. + const reportData = reportInstance.reportData(); + + // Retrieve the report columns. + const reportColumns = reportInstance.reportColumns(); + + return { + data: reportData, + columns: reportColumns, + }; + } +} diff --git a/server/src/services/FinancialStatements/VendorBalanceSummary/VendorBalanceSummaryTableRows.ts b/server/src/services/FinancialStatements/VendorBalanceSummary/VendorBalanceSummaryTableRows.ts new file mode 100644 index 000000000..a68f565e1 --- /dev/null +++ b/server/src/services/FinancialStatements/VendorBalanceSummary/VendorBalanceSummaryTableRows.ts @@ -0,0 +1,70 @@ +import { Service } from 'typedi'; +import { tableMapper, tableRowMapper } from 'utils'; +import { + IVendorBalanceSummaryData, + IVendorBalanceSummaryVendor, + IVendorBalanceSummaryTotal, + ITableRow, +} from 'interfaces'; + +enum TABLE_ROWS_TYPES { + VENDOR = 'VENDOR', + TOTAL = 'TOTAL', +} + +@Service() +export default class VendorBalanceSummaryTableRows { + /** + * Transformes the vendors to table rows. + * @param {IVendorBalanceSummaryVendor[]} vendors + * @returns {ITableRow[]} + */ + private vendorsTransformer( + vendors: IVendorBalanceSummaryVendor[] + ): ITableRow[] { + const columns = [ + { key: 'vendorName', accessor: 'vendorName' }, + { key: 'total', accessor: 'total.formattedAmount' }, + { + key: 'percentageOfColumn', + accessor: 'percentageOfColumn.formattedAmount', + }, + ]; + return tableMapper(vendors, columns, { + rowTypes: [TABLE_ROWS_TYPES.VENDOR], + }); + } + + /** + * Transformes the total to table row. + * @param {IVendorBalanceSummaryTotal} total + * @returns {ITableRow} + */ + private totalTransformer(total: IVendorBalanceSummaryTotal) { + const columns = [ + { key: 'total', value: 'Total' }, + { key: 'total', accessor: 'total.formattedAmount' }, + { + key: 'percentageOfColumn', + accessor: 'percentageOfColumn.formattedAmount', + }, + ]; + return tableRowMapper(total, columns, { + rowTypes: [TABLE_ROWS_TYPES.TOTAL], + }); + } + + /** + * Transformes the vendor balance summary to table rows. + * @param {IVendorBalanceSummaryData} vendorBalanceSummary + * @returns {ITableRow[]} + */ + public tableRowsTransformer( + vendorBalanceSummary: IVendorBalanceSummaryData + ): ITableRow[] { + const vendors = this.vendorsTransformer(vendorBalanceSummary.vendors); + const total = this.totalTransformer(vendorBalanceSummary.total); + + return vendors.length > 0 ? [...vendors, total] : []; + } +} diff --git a/server/src/utils/index.ts b/server/src/utils/index.ts index 0e1f83bd4..634340f64 100644 --- a/server/src/utils/index.ts +++ b/server/src/utils/index.ts @@ -5,6 +5,8 @@ import accounting from 'accounting'; import Currencies from 'js-money/lib/currency'; import definedOptions from 'data/options'; +export * from './table'; + const hashPassword = (password) => new Promise((resolve) => { bcrypt.genSalt(10, (error, salt) => { diff --git a/server/src/utils/table.ts b/server/src/utils/table.ts new file mode 100644 index 000000000..02514fbe8 --- /dev/null +++ b/server/src/utils/table.ts @@ -0,0 +1,26 @@ +import { get } from 'lodash'; +import { IColumnMapperMeta, ITableRow } from 'interfaces'; + +export function tableMapper( + data: Object[], + columns: IColumnMapperMeta[], + rowsMeta +): ITableRow[] { + return data.map((object) => tableRowMapper(object, columns, rowsMeta)); +} + +export function tableRowMapper( + object: Object, + columns: IColumnMapperMeta[], + rowMeta +): ITableRow { + const cells = columns.map((column) => ({ + key: column.key, + value: column.value ? column.value : get(object, column.accessor), + })); + + return { + cells, + ...rowMeta, + }; +}