From c4425d5f64967889efaabfff3ff0d58fd4d14eb3 Mon Sep 17 00:00:00 2001 From: "a.bouhuolia" Date: Thu, 6 May 2021 22:06:37 +0200 Subject: [PATCH] feat: WIP transactions by customers/vendors. --- server/package.json | 22 +----- server/src/api/controllers/BaseController.ts | 48 ++++++++---- .../CustomerBalanceSummary/index.ts | 78 ++++++++++++++----- .../TransactionsByCustomers/index.ts | 58 ++++++++++---- .../TransactionsByVendors/index.ts | 66 ++++++++++------ .../VendorBalanceSummary/index.ts | 63 ++++++++++----- .../ContactBalanceSummary.ts | 23 ++++-- .../FinancialStatements/FinancialSheet.ts | 1 - .../TransactionsByContactTableRows.ts | 12 ++- .../TransactionsByCustomersTableRows.ts | 4 +- server/src/utils/table.ts | 8 +- 11 files changed, 256 insertions(+), 127 deletions(-) diff --git a/server/package.json b/server/package.json index 527bbc6b9..9667985c8 100644 --- a/server/package.json +++ b/server/package.json @@ -19,6 +19,7 @@ "dependencies": { "@hapi/boom": "^7.4.3", "@types/i18n": "^0.8.7", + "accepts": "^1.3.7", "accounting": "^0.4.1", "agenda": "^3.1.0", "agendash": "^1.0.0", @@ -69,6 +70,7 @@ "objection-filter": "^4.0.1", "objection-soft-delete": "^1.0.7", "pluralize": "^8.0.0", + "ramda": "^0.27.1", "rate-limiter-flexible": "^2.1.14", "reflect-metadata": "^0.1.13", "ts-transformer-keys": "^0.4.2", @@ -105,25 +107,5 @@ "webpack-cli": "^4.6.0", "rimraf": "^3.0.2" }, - "_moduleAliases": { - "loaders": "build/loaders", - "collection": "build/collection", - "config": "build/config", - "api": "build/api", - "data": "build/data", - "database": "build/database", - "decorators": "build/decorators", - "exceptions": "build/exceptions", - "interfaces": "build/interfaces", - "jobs": "build/jobs", - "lib": "build/lib", - "utils": "build/utils", - "locales": "build/locales", - "models": "build/models", - "repositories": "build/repositories", - "services": "build/services", - "subscribers": "build/subscribers", - "system": "build/system" - }, "_moduleAliases": {} } diff --git a/server/src/api/controllers/BaseController.ts b/server/src/api/controllers/BaseController.ts index e891ea893..5627d57cb 100644 --- a/server/src/api/controllers/BaseController.ts +++ b/server/src/api/controllers/BaseController.ts @@ -1,13 +1,14 @@ import { Response, Request, NextFunction } from 'express'; -import { matchedData, validationResult } from "express-validator"; -import { camelCase, snakeCase, omit, set, get } from "lodash"; -import { mapKeysDeep } from 'utils' +import { matchedData, validationResult } from 'express-validator'; +import accepts from 'accepts'; +import { camelCase, snakeCase, omit, set, get } from 'lodash'; +import { mapKeysDeep } from 'utils'; import asyncMiddleware from 'api/middleware/asyncMiddleware'; export default class BaseController { /** * Converts plain object keys to cameCase style. - * @param {Object} data + * @param {Object} data */ private dataToCamelCase(data) { return mapKeysDeep(data, (v, k) => camelCase(k)); @@ -15,8 +16,8 @@ export default class BaseController { /** * Matches the body data from validation schema. - * @param {Request} req - * @param options + * @param {Request} req + * @param options */ matchedBodyData(req: Request, options: any = {}) { const data = matchedData(req, { @@ -29,7 +30,7 @@ export default class BaseController { /** * Matches the query data from validation schema. - * @param {Request} req + * @param {Request} req */ matchedQueryData(req: Request) { const data = matchedData(req, { @@ -40,13 +41,13 @@ export default class BaseController { /** * Validate validation schema middleware. - * @param {Request} req - * @param {Response} res - * @param {NextFunction} next + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next */ validationResult(req: Request, res: Response, next: NextFunction) { const validationErrors = validationResult(req); - + if (!validationErrors.isEmpty()) { return res.boom.badData(null, { code: 'validation_error', @@ -58,13 +59,19 @@ export default class BaseController { /** * Transform the given data to response. - * @param {any} data + * @param {any} data */ - transfromToResponse(data: any, translatable?: string | string[], req?: Request) { + transfromToResponse( + data: any, + translatable?: string | string[], + req?: Request + ) { const response = mapKeysDeep(data, (v, k) => snakeCase(k)); if (translatable) { - const translatables = Array.isArray(translatable) ? translatable : [translatable]; + const translatables = Array.isArray(translatable) + ? translatable + : [translatable]; translatables.forEach((path) => { const value = get(response, path); @@ -76,9 +83,18 @@ export default class BaseController { /** * Async middleware. - * @param {function} callback + * @param {function} callback */ asyncMiddleware(callback) { return asyncMiddleware(callback); } -} \ No newline at end of file + + /** + * + * @param {Request} req + * @returns + */ + accepts(req) { + return accepts(req); + } +} diff --git a/server/src/api/controllers/FinancialStatements/CustomerBalanceSummary/index.ts b/server/src/api/controllers/FinancialStatements/CustomerBalanceSummary/index.ts index 646631f03..a0e2e4c2c 100644 --- a/server/src/api/controllers/FinancialStatements/CustomerBalanceSummary/index.ts +++ b/server/src/api/controllers/FinancialStatements/CustomerBalanceSummary/index.ts @@ -1,6 +1,7 @@ import { Router, Request, Response, NextFunction } from 'express'; import { query } from 'express-validator'; import { Inject } from 'typedi'; +import { ICustomerBalanceSummaryStatement } from 'interfaces'; import asyncMiddleware from 'api/middleware/asyncMiddleware'; import CustomerBalanceSummary from 'services/FinancialStatements/CustomerBalanceSummary/CustomerBalanceSummaryService'; import BaseFinancialReportController from '../BaseFinancialReportController'; @@ -38,35 +39,72 @@ export default class CustomerBalanceSummaryReportController extends BaseFinancia } /** - * Retrieve payable aging summary report. - * @param {Request} req - - * @param {Response} res - - * @param {NextFunction} next - + * Transformes the balance summary statement to table rows. + * @param {ICustomerBalanceSummaryStatement} statement - */ - async customerBalanceSummary(req: Request, res: Response, next: NextFunction) { + private transformToTableRows({ + data, + columns, + }: ICustomerBalanceSummaryStatement) { + return { + table: { + rows: this.customerBalanceSummaryTableRows.tableRowsTransformer(data), + columns: this.transfromToResponse(columns), + }, + query: this.transfromToResponse(query), + }; + } + + /** + * Transformes the balance summary statement to raw json. + * @param {ICustomerBalanceSummaryStatement} customerBalance - + */ + private transformToJsonResponse({ + data, + columns, + query, + }: ICustomerBalanceSummaryStatement) { + return { + data: this.transfromToResponse(data), + columns: this.transfromToResponse(columns), + query: this.transfromToResponse(query), + }; + } + + /** + * Retrieve payable aging summary report. + * @param {Request} req - + * @param {Response} res - + * @param {NextFunction} next - + */ + async customerBalanceSummary( + req: Request, + res: Response, + next: NextFunction + ) { const { tenantId, settings } = req; const filter = this.matchedQueryData(req); try { - const { - data, - columns, - query, - } = await this.customerBalanceSummaryService.customerBalanceSummary( + const customerBalanceSummary = await this.customerBalanceSummaryService.customerBalanceSummary( tenantId, filter ); - const tableRows = this.customerBalanceSummaryTableRows.tableRowsTransformer( - data - ); - return res.status( 200).send({ - table: { - rows: tableRows - }, - columns: this.transfromToResponse(columns), - query: this.transfromToResponse(query), - }); + const accept = this.accepts(req); + const acceptType = accept.types(['json', 'application/json+table']); + + switch (acceptType) { + case 'application/json+table': + return res + .status(200) + .send(this.transformToTableRows(customerBalanceSummary)); + case 'application/json': + default: + return res + .status(200) + .send(this.transformToJsonResponse(customerBalanceSummary)); + } } catch (error) { next(error); } diff --git a/server/src/api/controllers/FinancialStatements/TransactionsByCustomers/index.ts b/server/src/api/controllers/FinancialStatements/TransactionsByCustomers/index.ts index 092d81df6..30ed4d41e 100644 --- a/server/src/api/controllers/FinancialStatements/TransactionsByCustomers/index.ts +++ b/server/src/api/controllers/FinancialStatements/TransactionsByCustomers/index.ts @@ -1,6 +1,7 @@ import { Router, Request, Response, NextFunction } from 'express'; import { query } from 'express-validator'; import { Inject } from 'typedi'; +import { ITransactionsByCustomersStatement } from 'interfaces'; import asyncMiddleware from 'api/middleware/asyncMiddleware'; import BaseFinancialReportController from '../BaseFinancialReportController'; import TransactionsByCustomersService from 'services/FinancialStatements/TransactionsByCustomer/TransactionsByCustomersService'; @@ -40,6 +41,33 @@ export default class TransactionsByCustomersReportController extends BaseFinanci ]; } + /** + * Transformes the statement to table rows response. + * @param {ITransactionsByCustomersStatement} statement - + */ + transformToTableResponse({ data }: ITransactionsByCustomersStatement) { + return { + table: { + rows: this.transactionsByCustomersTableRows.tableRows(data), + }, + }; + } + + /** + * Transformes the statement to json response. + * @param {ITransactionsByCustomersStatement} statement - + */ + transfromToJsonResponse({ + data, + columns, + }: ITransactionsByCustomersStatement) { + return { + data: this.transfromToResponse(data), + columns: this.transfromToResponse(columns), + query: this.transfromToResponse(query), + }; + } + /** * Retrieve payable aging summary report. * @param {Request} req - @@ -55,26 +83,24 @@ export default class TransactionsByCustomersReportController extends BaseFinanci const filter = this.matchedQueryData(req); try { - const { - data, - columns, - query, - } = await this.transactionsByCustomersService.transactionsByCustomers( + const transactionsByCustomers = await this.transactionsByCustomersService.transactionsByCustomers( tenantId, filter ); + const accept = this.accepts(req); + const acceptType = accept.types(['json', 'application/json+table']); - return res.status(200).send({ - table: { - rows: this.transactionsByCustomersTableRows.tableRows(data), - }, - }); - - return res.status(200).send({ - data: this.transfromToResponse(data), - columns: this.transfromToResponse(columns), - query: this.transfromToResponse(query), - }); + switch (acceptType) { + case 'json': + return res + .status(200) + .send(this.transfromToJsonResponse(transactionsByCustomers)); + case 'application/json+table': + default: + return res + .status(200) + .send(this.transformToTableResponse(transactionsByCustomers)); + } } catch (error) { next(error); } diff --git a/server/src/api/controllers/FinancialStatements/TransactionsByVendors/index.ts b/server/src/api/controllers/FinancialStatements/TransactionsByVendors/index.ts index 7ede03ee5..4b3dc5f12 100644 --- a/server/src/api/controllers/FinancialStatements/TransactionsByVendors/index.ts +++ b/server/src/api/controllers/FinancialStatements/TransactionsByVendors/index.ts @@ -5,7 +5,7 @@ import asyncMiddleware from 'api/middleware/asyncMiddleware'; import BaseFinancialReportController from '../BaseFinancialReportController'; import TransactionsByVendorsTableRows from 'services/FinancialStatements/TransactionsByVendor/TransactionsByVendorTableRows'; import TransactionsByVendorsService from 'services/FinancialStatements/TransactionsByVendor/TransactionsByVendorService'; - +import { ITransactionsByVendorsStatement } from 'interfaces'; export default class TransactionsByVendorsReportController extends BaseFinancialReportController { @Inject() transactionsByVendorsService: TransactionsByVendorsService; @@ -40,41 +40,63 @@ export default class TransactionsByVendorsReportController extends BaseFinancial ]; } + /** + * Transformes the report statement to table rows. + * @param {ITransactionsByVendorsStatement} statement - + */ + transformToTableRows({ data }: ITransactionsByVendorsStatement) { + return { + table: { + data: this.transactionsByVendorsTableRows.tableRows(data), + }, + }; + } + + /** + * Transformes the report statement to json response. + * @param {ITransactionsByVendorsStatement} statement - + */ + transformToJsonResponse({ + data, + columns, + query, + }: ITransactionsByVendorsStatement) { + return { + data: this.transfromToResponse(data), + columns: this.transfromToResponse(columns), + query: this.transfromToResponse(query), + }; + } + /** * Retrieve payable aging summary report. * @param {Request} req - * @param {Response} res - * @param {NextFunction} next - */ - async transactionsByVendors( - req: Request, - res: Response, - next: NextFunction - ) { + async transactionsByVendors(req: Request, res: Response, next: NextFunction) { const { tenantId } = req; const filter = this.matchedQueryData(req); try { - const { - data, - columns, - query, - } = await this.transactionsByVendorsService.transactionsByVendors( + const transactionsByVendors = await this.transactionsByVendorsService.transactionsByVendors( tenantId, filter ); + const accept = this.accepts(req); + const acceptType = accept.types(['json', 'application/json+table']); - return res.status(200).send({ - table: { - rows: this.transactionsByVendorsTableRows.tableRows(data), - }, - }); - - return res.status(200).send({ - data: this.transfromToResponse(data), - columns: this.transfromToResponse(columns), - query: this.transfromToResponse(query), - }); + switch (acceptType) { + case 'application/json+table': + return res + .status(200) + .send(this.transformToTableRows(transactionsByVendors)); + case 'json': + default: + return res + .status(200) + .send(this.transformToJsonResponse(transactionsByVendors)); + } } catch (error) { next(error); } diff --git a/server/src/api/controllers/FinancialStatements/VendorBalanceSummary/index.ts b/server/src/api/controllers/FinancialStatements/VendorBalanceSummary/index.ts index 388ff1968..c94f77319 100644 --- a/server/src/api/controllers/FinancialStatements/VendorBalanceSummary/index.ts +++ b/server/src/api/controllers/FinancialStatements/VendorBalanceSummary/index.ts @@ -5,7 +5,7 @@ import asyncMiddleware from 'api/middleware/asyncMiddleware'; import BaseFinancialReportController from '../BaseFinancialReportController'; import VendorBalanceSummaryTableRows from 'services/FinancialStatements/VendorBalanceSummary/VendorBalanceSummaryTableRows'; import VendorBalanceSummaryService from 'services/FinancialStatements/VendorBalanceSummary/VendorBalanceSummaryService'; - +import { IVendorBalanceSummaryStatement } from 'interfaces'; export default class VendorBalanceSummaryReportController extends BaseFinancialReportController { @Inject() vendorBalanceSummaryService: VendorBalanceSummaryService; @@ -37,36 +37,61 @@ export default class VendorBalanceSummaryReportController extends BaseFinancialR ]; } + /** + * Transformes the report statement to table rows. + * @param {IVendorBalanceSummaryStatement} statement - + */ + transformToTableRows({ data }: IVendorBalanceSummaryStatement) { + return { + table: { + data: this.vendorBalanceSummaryTableRows.tableRowsTransformer(data), + }, + }; + } + + /** + * Transformes the report statement to raw json. + * @param {IVendorBalanceSummaryStatement} statement - + */ + transformToJsonResponse({ data, columns }: IVendorBalanceSummaryStatement) { + return { + data: this.transfromToResponse(data), + columns: this.transfromToResponse(columns), + query: this.transfromToResponse(query), + }; + } + /** * Retrieve vendors balance summary. - * @param {Request} req - - * @param {Response} res - - * @param {NextFunction} next - + * @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( + const vendorBalanceSummary = await this.vendorBalanceSummaryService.vendorBalanceSummary( tenantId, filter ); + const accept = this.accepts(req); + const acceptType = accept.types(['json', 'application/json+table']); - const tableRows = this.vendorBalanceSummaryTableRows.tableRowsTransformer( - data - ); - return res.status( 200).send({ - table: { - rows: tableRows - }, - columns: this.transfromToResponse(columns), - query: this.transfromToResponse(query), - }); + switch (acceptType) { + case 'application/json+table': + return res + .status(200) + .send(this.transformToTableRows(vendorBalanceSummary)); + case 'json': + default: + return res + .status(200) + .send(this.transformToJsonResponse(vendorBalanceSummary)); + } + + return res.status(200).send({}); } catch (error) { next(error); } diff --git a/server/src/services/FinancialStatements/ContactBalanceSummary/ContactBalanceSummary.ts b/server/src/services/FinancialStatements/ContactBalanceSummary/ContactBalanceSummary.ts index aa5474680..45912d97f 100644 --- a/server/src/services/FinancialStatements/ContactBalanceSummary/ContactBalanceSummary.ts +++ b/server/src/services/FinancialStatements/ContactBalanceSummary/ContactBalanceSummary.ts @@ -5,7 +5,7 @@ import { IContactBalanceSummaryContact, IContactBalanceSummaryTotal, IContactBalanceSummaryAmount, - IContactBalanceSummaryPercentage + IContactBalanceSummaryPercentage, } from 'interfaces'; export class ContactBalanceSummaryReport extends FinancialSheet { @@ -60,7 +60,7 @@ export class ContactBalanceSummaryReport extends FinancialSheet { * @param {IContactBalanceSummaryContact} contact * @returns {IContactBalanceSummaryContact} */ - private contactCamparsionPercentageOfColumnMapper( + private contactCamparsionPercentageOfColumnMapper( total: number, contact: IContactBalanceSummaryContact ): IContactBalanceSummaryContact { @@ -90,7 +90,14 @@ export class ContactBalanceSummaryReport extends FinancialSheet { return contacts.map(camparsionPercentageOfColummn); } - getContactTotalFormat(amount: number) { + /** + * Retrieve the contact total format. + * @param {number} amount - + * @return {IContactBalanceSummaryAmount} + */ + protected getContactTotalFormat( + amount: number + ): IContactBalanceSummaryAmount { return { amount, formattedAmount: this.formatNumber(amount), @@ -100,10 +107,10 @@ export class ContactBalanceSummaryReport extends FinancialSheet { /** * Retrieve the total amount of contacts sections. - * @param {number} amount + * @param {number} amount * @returns {IContactBalanceSummaryAmount} */ - getTotalFormat(amount: number): IContactBalanceSummaryAmount { + protected getTotalFormat(amount: number): IContactBalanceSummaryAmount { return { amount, formattedAmount: this.formatNumber(amount), @@ -113,10 +120,12 @@ export class ContactBalanceSummaryReport extends FinancialSheet { /** * Retrieve the percentage amount object. - * @param {number} amount + * @param {number} amount * @returns {IContactBalanceSummaryPercentage} */ - getPercentageMeta(amount: number): IContactBalanceSummaryPercentage { + protected getPercentageMeta( + amount: number + ): IContactBalanceSummaryPercentage { return { amount, formattedAmount: this.formatPercentage(amount), diff --git a/server/src/services/FinancialStatements/FinancialSheet.ts b/server/src/services/FinancialStatements/FinancialSheet.ts index 083ecd7c3..e9d9af0de 100644 --- a/server/src/services/FinancialStatements/FinancialSheet.ts +++ b/server/src/services/FinancialStatements/FinancialSheet.ts @@ -9,7 +9,6 @@ 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/TransactionsByContactTableRows.ts b/server/src/services/FinancialStatements/TransactionsByContact/TransactionsByContactTableRows.ts index 053ceba97..481bcee18 100644 --- a/server/src/services/FinancialStatements/TransactionsByContact/TransactionsByContactTableRows.ts +++ b/server/src/services/FinancialStatements/TransactionsByContact/TransactionsByContactTableRows.ts @@ -1,3 +1,4 @@ +import moment from 'moment'; import { tableMapper, tableRowMapper } from 'utils'; import { ITransactionsByContactsContact, @@ -12,6 +13,11 @@ enum ROW_TYPE { } export default class TransactionsByContactsTableRows { + + private dateAccessor(value) { + return moment(value.date).format('YYYY MMM DD'); + } + /** * Retrieve the table rows of contact transactions. * @param {ITransactionsByCustomersCustomer} contact @@ -21,7 +27,7 @@ export default class TransactionsByContactsTableRows { contact: ITransactionsByContactsContact ): ITableRow[] { const columns = [ - { key: 'date', accessor: 'date' }, + { key: 'date', accessor: this.dateAccessor }, { key: 'account', accessor: 'account.name' }, { key: 'referenceType', accessor: 'referenceType' }, { key: 'transactionType', accessor: 'transactionType' }, @@ -62,9 +68,9 @@ export default class TransactionsByContactsTableRows { contact: ITransactionsByContactsContact ): ITableRow { const columns = [ - { key: 'openingBalanceLabel', value: 'Closing balance' }, + { key: 'closingBalanceLabel', value: 'Closing balance' }, { - key: 'openingBalanceValue', + key: 'closingBalanceValue', accessor: 'closingBalance.formattedAmount', }, ]; diff --git a/server/src/services/FinancialStatements/TransactionsByCustomer/TransactionsByCustomersTableRows.ts b/server/src/services/FinancialStatements/TransactionsByCustomer/TransactionsByCustomersTableRows.ts index be779f998..c354edf4f 100644 --- a/server/src/services/FinancialStatements/TransactionsByCustomer/TransactionsByCustomersTableRows.ts +++ b/server/src/services/FinancialStatements/TransactionsByCustomer/TransactionsByCustomersTableRows.ts @@ -22,8 +22,8 @@ export default class TransactionsByCustomersTableRows extends TransactionsByCont return { ...tableRowMapper(customer, columns, { rowTypes: [ROW_TYPE.CUSTOMER] }), children: R.pipe( - R.append(this.contactOpeningBalance(customer)), R.concat(this.contactTransactions(customer)), + R.prepend(this.contactOpeningBalance(customer)), R.append(this.contactClosingBalance(customer)) )([]), }; @@ -35,7 +35,7 @@ export default class TransactionsByCustomersTableRows extends TransactionsByCont * @returns {ITableRow[]} */ private customerRowsMapper(customer: ITransactionsByCustomersCustomer) { - return R.pipe(R.append(this.customerDetails(customer))).bind(this)([]); + return R.pipe(this.customerDetails).bind(this)(customer); } /** diff --git a/server/src/utils/table.ts b/server/src/utils/table.ts index 02514fbe8..579f6e944 100644 --- a/server/src/utils/table.ts +++ b/server/src/utils/table.ts @@ -9,6 +9,12 @@ export function tableMapper( return data.map((object) => tableRowMapper(object, columns, rowsMeta)); } +function getAccessor(object, accessor) { + return typeof accessor === 'function' + ? accessor(object) + : get(object, accessor); +} + export function tableRowMapper( object: Object, columns: IColumnMapperMeta[], @@ -16,7 +22,7 @@ export function tableRowMapper( ): ITableRow { const cells = columns.map((column) => ({ key: column.key, - value: column.value ? column.value : get(object, column.accessor), + value: column.value ? column.value : getAccessor(object, column.accessor), })); return {