From 7680150a31d41746e0c81bce4c7f312c816d8516 Mon Sep 17 00:00:00 2001 From: "a.bouhuolia" Date: Tue, 12 Jan 2021 11:45:36 +0200 Subject: [PATCH] feat: format amount rows of financial statemnets. --- server/package.json | 1 + .../FinancialStatements/APAgingSummary.ts | 7 +- .../FinancialStatements/ARAgingSummary.ts | 12 +- .../FinancialStatements/BalanceSheet.ts | 8 +- .../BaseFinancialReportController.ts | 26 +++++ .../FinancialStatements/GeneralLedger.ts | 6 +- .../FinancialStatements/JournalSheet.ts | 4 +- .../FinancialStatements/ProfitLossSheet.ts | 7 +- .../FinancialStatements/TrialBalanceSheet.ts | 47 +++++--- server/src/interfaces/APAgingSummaryReport.ts | 8 +- server/src/interfaces/ARAgingSummaryReport.ts | 8 +- server/src/interfaces/BalanceSheet.ts | 107 +++++++++--------- server/src/interfaces/FinancialStatements.ts | 19 +++- server/src/interfaces/Journal.ts | 2 + server/src/interfaces/ProfitLossSheet.ts | 13 +-- server/src/interfaces/TrialBalanceSheet.ts | 61 +++++----- .../AgingSummary/APAgingSummaryService.ts | 7 +- .../AgingSummary/ARAgingSummaryService.ts | 10 +- .../AgingSummary/ARAgingSummarySheet.ts | 2 +- .../AgingSummary/AgingSummary.ts | 44 ++++--- .../BalanceSheet/BalanceSheet.ts | 39 ++++--- .../BalanceSheet/BalanceSheetService.ts | 5 +- .../ProfitLossSheet/ProfitLossSheet.ts | 16 +-- .../ProfitLossSheet/ProfitLossSheetService.ts | 34 ++++-- .../TrialBalanceSheet/TrialBalanceSheet.ts | 36 +++++- .../TrialBalanceSheetService.ts | 14 ++- server/src/utils/index.js | 57 +++++++--- 27 files changed, 392 insertions(+), 208 deletions(-) create mode 100644 server/src/api/controllers/FinancialStatements/BaseFinancialReportController.ts diff --git a/server/package.json b/server/package.json index da28bd13f..4fba092e0 100644 --- a/server/package.json +++ b/server/package.json @@ -16,6 +16,7 @@ "dependencies": { "@hapi/boom": "^7.4.3", "@types/i18n": "^0.8.7", + "accounting": "^0.4.1", "agenda": "^3.1.0", "agendash": "^1.0.0", "app-root-path": "^3.0.0", diff --git a/server/src/api/controllers/FinancialStatements/APAgingSummary.ts b/server/src/api/controllers/FinancialStatements/APAgingSummary.ts index 76555235e..5f18f2555 100644 --- a/server/src/api/controllers/FinancialStatements/APAgingSummary.ts +++ b/server/src/api/controllers/FinancialStatements/APAgingSummary.ts @@ -1,11 +1,11 @@ import { Router, Request, Response, NextFunction } from 'express'; import { query } from 'express-validator'; import { Inject } from 'typedi'; -import BaseController from 'api/controllers/BaseController'; import asyncMiddleware from 'api/middleware/asyncMiddleware'; import APAgingSummaryReportService from 'services/FinancialStatements/AgingSummary/APAgingSummaryService'; +import BaseFinancialReportController from './BaseFinancialReportController'; -export default class APAgingSummaryReportController extends BaseController { +export default class APAgingSummaryReportController extends BaseFinancialReportController { @Inject() APAgingSummaryService: APAgingSummaryReportService; @@ -28,11 +28,10 @@ export default class APAgingSummaryReportController extends BaseController { */ get validationSchema() { return [ + ...this.sheetNumberFormatValidationSchema, query('as_date').optional().isISO8601(), query('aging_days_before').optional().isNumeric().toInt(), query('aging_periods').optional().isNumeric().toInt(), - query('number_format.no_cents').optional().isBoolean().toBoolean(), - query('number_format.1000_divide').optional().isBoolean().toBoolean(), query('vendors_ids').optional().isArray({ min: 1 }), query('vendors_ids.*').isInt({ min: 1 }).toInt(), query('none_zero').default(true).isBoolean().toBoolean(), diff --git a/server/src/api/controllers/FinancialStatements/ARAgingSummary.ts b/server/src/api/controllers/FinancialStatements/ARAgingSummary.ts index 1feb7741a..979eea0c5 100644 --- a/server/src/api/controllers/FinancialStatements/ARAgingSummary.ts +++ b/server/src/api/controllers/FinancialStatements/ARAgingSummary.ts @@ -1,13 +1,11 @@ import { Service, Inject } from 'typedi'; import { Router, Request, Response } from 'express'; -import { castArray } from 'lodash'; -import { query, oneOf } from 'express-validator'; -import { IARAgingSummaryQuery } from 'interfaces'; -import BaseController from '../BaseController'; +import { query } from 'express-validator'; import ARAgingSummaryService from 'services/FinancialStatements/AgingSummary/ARAgingSummaryService'; +import BaseFinancialReportController from './BaseFinancialReportController'; @Service() -export default class ARAgingSummaryReportController extends BaseController { +export default class ARAgingSummaryReportController extends BaseFinancialReportController { @Inject() ARAgingSummaryService: ARAgingSummaryService; @@ -31,11 +29,11 @@ export default class ARAgingSummaryReportController extends BaseController { */ get validationSchema() { return [ + ...this.sheetNumberFormatValidationSchema, + query('as_date').optional().isISO8601(), query('aging_days_before').optional().isInt({ max: 500 }).toInt(), query('aging_periods').optional().isInt({ max: 12 }).toInt(), - query('number_format.no_cents').optional().isBoolean().toBoolean(), - query('number_format.1000_divide').optional().isBoolean().toBoolean(), query('customers_ids').optional().isArray({ min: 1 }), query('customers_ids.*').isInt({ min: 1 }).toInt(), query('none_zero').default(true).isBoolean().toBoolean(), diff --git a/server/src/api/controllers/FinancialStatements/BalanceSheet.ts b/server/src/api/controllers/FinancialStatements/BalanceSheet.ts index fe1562e27..46163c04b 100644 --- a/server/src/api/controllers/FinancialStatements/BalanceSheet.ts +++ b/server/src/api/controllers/FinancialStatements/BalanceSheet.ts @@ -3,11 +3,11 @@ import { Router, Request, Response, NextFunction } from 'express'; import { query, ValidationChain } from 'express-validator'; import { castArray } from 'lodash'; import asyncMiddleware from 'api/middleware/asyncMiddleware'; -import BaseController from '../BaseController'; import BalanceSheetStatementService from 'services/FinancialStatements/BalanceSheet/BalanceSheetService'; +import BaseFinancialReportController from './BaseFinancialReportController'; @Service() -export default class BalanceSheetStatementController extends BaseController { +export default class BalanceSheetStatementController extends BaseFinancialReportController { @Inject() balanceSheetService: BalanceSheetStatementService; @@ -32,6 +32,8 @@ export default class BalanceSheetStatementController extends BaseController { */ get balanceSheetValidationSchema(): ValidationChain[] { return [ + ...this.sheetNumberFormatValidationSchema, + query('accounting_method').optional().isIn(['cash', 'accural']), query('from_date').optional(), query('to_date').optional(), @@ -39,8 +41,6 @@ export default class BalanceSheetStatementController extends BaseController { query('display_columns_by') .optional({ nullable: true, checkFalsy: true }) .isIn(['year', 'month', 'week', 'day', 'quarter']), - query('number_format.no_cents').optional().isBoolean().toBoolean(), - query('number_format.divide_1000').optional().isBoolean().toBoolean(), query('account_ids').isArray().optional(), query('account_ids.*').isNumeric().toInt(), query('none_zero').optional().isBoolean().toBoolean(), diff --git a/server/src/api/controllers/FinancialStatements/BaseFinancialReportController.ts b/server/src/api/controllers/FinancialStatements/BaseFinancialReportController.ts new file mode 100644 index 000000000..7fccd76f1 --- /dev/null +++ b/server/src/api/controllers/FinancialStatements/BaseFinancialReportController.ts @@ -0,0 +1,26 @@ +import { query } from 'express-validator'; +import BaseController from "../BaseController"; + +export default class BaseFinancialReportController extends BaseController { + + + get sheetNumberFormatValidationSchema() { + return [ + query('number_format.precision') + .optional() + .isInt({ min: 0, max: 5 }) + .toInt(), + query('number_format.divide_on_1000').optional().isBoolean().toBoolean(), + query('number_format.show_zero').optional().isBoolean().toBoolean(), + query('number_format.format_money') + .optional() + .isIn(['total', 'always', 'none']) + .trim(), + query('number_format.negative_format') + .optional() + .isIn(['parentheses', 'mines']) + .trim() + .escape(), + ]; + } +} \ No newline at end of file diff --git a/server/src/api/controllers/FinancialStatements/GeneralLedger.ts b/server/src/api/controllers/FinancialStatements/GeneralLedger.ts index b0357373d..071921a43 100644 --- a/server/src/api/controllers/FinancialStatements/GeneralLedger.ts +++ b/server/src/api/controllers/FinancialStatements/GeneralLedger.ts @@ -1,12 +1,12 @@ import { Router, Request, Response, NextFunction } from 'express'; import { query, ValidationChain } from 'express-validator'; -import asyncMiddleware from 'api/middleware/asyncMiddleware'; -import BaseController from '../BaseController'; import { Inject, Service } from 'typedi'; +import asyncMiddleware from 'api/middleware/asyncMiddleware'; import GeneralLedgerService from 'services/FinancialStatements/GeneralLedger/GeneralLedgerService'; +import BaseFinancialReportController from './BaseFinancialReportController'; @Service() -export default class GeneralLedgerReportController extends BaseController { +export default class GeneralLedgerReportController extends BaseFinancialReportController { @Inject() generalLedgetService: GeneralLedgerService; diff --git a/server/src/api/controllers/FinancialStatements/JournalSheet.ts b/server/src/api/controllers/FinancialStatements/JournalSheet.ts index 6a4214da4..f832fedf6 100644 --- a/server/src/api/controllers/FinancialStatements/JournalSheet.ts +++ b/server/src/api/controllers/FinancialStatements/JournalSheet.ts @@ -2,11 +2,11 @@ import { Inject, Service } from 'typedi'; import { Request, Response, Router, NextFunction } from 'express'; import { castArray } from 'lodash'; import { query, oneOf } from 'express-validator'; +import BaseFinancialReportController from './BaseFinancialReportController'; import JournalSheetService from 'services/FinancialStatements/JournalSheet/JournalSheetService'; -import BaseController from '../BaseController'; @Service() -export default class JournalSheetController extends BaseController { +export default class JournalSheetController extends BaseFinancialReportController { @Inject() journalService: JournalSheetService; diff --git a/server/src/api/controllers/FinancialStatements/ProfitLossSheet.ts b/server/src/api/controllers/FinancialStatements/ProfitLossSheet.ts index be6720666..e5b5125fb 100644 --- a/server/src/api/controllers/FinancialStatements/ProfitLossSheet.ts +++ b/server/src/api/controllers/FinancialStatements/ProfitLossSheet.ts @@ -1,11 +1,11 @@ import { Service, Inject } from 'typedi'; import { Router, Request, Response, NextFunction } from 'express'; import { query, ValidationChain } from 'express-validator'; -import BaseController from '../BaseController'; import ProfitLossSheetService from 'services/FinancialStatements/ProfitLossSheet/ProfitLossSheetService'; +import BaseFinancialReportController from './BaseFinancialReportController'; @Service() -export default class ProfitLossSheetController extends BaseController { +export default class ProfitLossSheetController extends BaseFinancialReportController { @Inject() profitLossSheetService: ProfitLossSheetService; @@ -29,11 +29,10 @@ export default class ProfitLossSheetController extends BaseController { */ get validationSchema(): ValidationChain[] { return [ + ...this.sheetNumberFormatValidationSchema, query('basis').optional(), query('from_date').optional().isISO8601(), query('to_date').optional().isISO8601(), - query('number_format.no_cents').optional().isBoolean(), - query('number_format.divide_1000').optional().isBoolean(), query('basis').optional(), query('none_zero').optional().isBoolean().toBoolean(), query('none_transactions').optional().isBoolean().toBoolean(), diff --git a/server/src/api/controllers/FinancialStatements/TrialBalanceSheet.ts b/server/src/api/controllers/FinancialStatements/TrialBalanceSheet.ts index a9baf0997..9ea0f4113 100644 --- a/server/src/api/controllers/FinancialStatements/TrialBalanceSheet.ts +++ b/server/src/api/controllers/FinancialStatements/TrialBalanceSheet.ts @@ -1,13 +1,13 @@ import { Inject, Service } from 'typedi'; -import { Request, Response, Router, NextFunction } from 'express'; +import { Request, Response, Router, NextFunction } from 'express'; import { query, ValidationChain } from 'express-validator'; -import asyncMiddleware from 'api/middleware/asyncMiddleware'; -import BaseController from '../BaseController'; -import TrialBalanceSheetService from 'services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetService'; import { castArray } from 'lodash'; +import asyncMiddleware from 'api/middleware/asyncMiddleware'; +import TrialBalanceSheetService from 'services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetService'; +import BaseFinancialReportController from './BaseFinancialReportController'; @Service() -export default class TrialBalanceSheetController extends BaseController { +export default class TrialBalanceSheetController extends BaseFinancialReportController { @Inject() trialBalanceSheetService: TrialBalanceSheetService; @@ -17,7 +17,8 @@ export default class TrialBalanceSheetController extends BaseController { router() { const router = Router(); - router.get('/', + router.get( + '/', this.trialBalanceSheetValidationSchema, this.validationResult, asyncMiddleware(this.trialBalanceSheet.bind(this)) @@ -31,11 +32,10 @@ export default class TrialBalanceSheetController extends BaseController { */ get trialBalanceSheetValidationSchema(): ValidationChain[] { return [ + ...this.sheetNumberFormatValidationSchema, query('basis').optional(), query('from_date').optional().isISO8601(), query('to_date').optional().isISO8601(), - query('number_format.no_cents').optional().isBoolean().toBoolean(), - query('number_format.1000_divide').optional().isBoolean().toBoolean(), query('account_ids').isArray().optional(), query('account_ids.*').isNumeric().toInt(), query('basis').optional(), @@ -46,7 +46,11 @@ export default class TrialBalanceSheetController extends BaseController { /** * Retrieve the trial balance sheet. */ - public async trialBalanceSheet(req: Request, res: Response, next: NextFunction) { + public async trialBalanceSheet( + req: Request, + res: Response, + next: NextFunction + ) { const { tenantId, settings } = req; let filter = this.matchedQueryData(req); @@ -54,21 +58,32 @@ export default class TrialBalanceSheetController extends BaseController { ...filter, accountsIds: castArray(filter.accountsIds), }; - const organizationName = settings.get({ group: 'organization', key: 'name' }); - const baseCurrency = settings.get({ group: 'organization', key: 'base_currency' }); + const organizationName = settings.get({ + group: 'organization', + key: 'name', + }); + const baseCurrency = settings.get({ + group: 'organization', + key: 'base_currency', + }); try { - const { data, query } = await this.trialBalanceSheetService - .trialBalanceSheet(tenantId, filter); - + const { + data, + query, + } = await this.trialBalanceSheetService.trialBalanceSheet( + tenantId, + filter + ); + return res.status(200).send({ organization_name: organizationName, base_currency: baseCurrency, data: this.transfromToResponse(data), - query: this.transfromToResponse(query) + query: this.transfromToResponse(query), }); } catch (error) { next(error); } } -} \ No newline at end of file +} diff --git a/server/src/interfaces/APAgingSummaryReport.ts b/server/src/interfaces/APAgingSummaryReport.ts index 490403338..2c352fd46 100644 --- a/server/src/interfaces/APAgingSummaryReport.ts +++ b/server/src/interfaces/APAgingSummaryReport.ts @@ -2,15 +2,15 @@ import { IAgingPeriod, IAgingPeriodTotal } from './AgingReport'; +import { + INumberFormatQuery +} from './FinancialStatements'; export interface IAPAgingSummaryQuery { asDate: Date | string; agingDaysBefore: number; agingPeriods: number; - numberFormat: { - noCents: boolean; - divideOn1000: boolean; - }; + numberFormat: INumberFormatQuery; vendorsIds: number[]; noneZero: boolean; } diff --git a/server/src/interfaces/ARAgingSummaryReport.ts b/server/src/interfaces/ARAgingSummaryReport.ts index 10c09063d..9f7670ae9 100644 --- a/server/src/interfaces/ARAgingSummaryReport.ts +++ b/server/src/interfaces/ARAgingSummaryReport.ts @@ -2,15 +2,15 @@ import { IAgingPeriod, IAgingPeriodTotal } from './AgingReport'; +import { + INumberFormatQuery +} from './FinancialStatements'; export interface IARAgingSummaryQuery { asDate: Date | string; agingDaysBefore: number; agingPeriods: number; - numberFormat: { - noCents: boolean; - divideOn1000: boolean; - }; + numberFormat: INumberFormatQuery; customersIds: number[]; noneZero: boolean; } diff --git a/server/src/interfaces/BalanceSheet.ts b/server/src/interfaces/BalanceSheet.ts index a680fe13c..f9a8e631d 100644 --- a/server/src/interfaces/BalanceSheet.ts +++ b/server/src/interfaces/BalanceSheet.ts @@ -1,74 +1,79 @@ +import { + INumberFormatQuery, + IFormatNumberSettings, +} from './FinancialStatements'; -export interface IBalanceSheetQuery{ - displayColumnsType: 'total' | 'date_periods', - displayColumnsBy: string, - fromDate: Date|string, - toDate: Date|string, - numberFormat: { - noCents: boolean, - divideOn1000: boolean, - }, - noneZero: boolean, - noneTransactions: boolean, - basis: 'cash' | 'accural', - accountIds: number[], +export interface IBalanceSheetQuery { + displayColumnsType: 'total' | 'date_periods'; + displayColumnsBy: string; + fromDate: Date | string; + toDate: Date | string; + numberFormat: INumberFormatQuery; + noneZero: boolean; + noneTransactions: boolean; + basis: 'cash' | 'accural'; + accountIds: number[]; +} + +export interface IBalanceSheetFormatNumberSettings + extends IFormatNumberSettings { + type: string; } export interface IBalanceSheetStatementService { - balanceSheet(tenantId: number, query: IBalanceSheetQuery): Promise; + balanceSheet( + tenantId: number, + query: IBalanceSheetQuery + ): Promise; } -export interface IBalanceSheetStatementColumns { +export interface IBalanceSheetStatementColumns {} -} - -export interface IBalanceSheetStatementData { - -} +export interface IBalanceSheetStatementData {} export interface IBalanceSheetStatement { - query: IBalanceSheetQuery, - columns: IBalanceSheetStatementColumns, - data: IBalanceSheetStatementData, + query: IBalanceSheetQuery; + columns: IBalanceSheetStatementColumns; + data: IBalanceSheetStatementData; } export interface IBalanceSheetStructureSection { - name: string, - sectionType?: string, - type: 'section' | 'accounts_section', - children?: IBalanceSheetStructureSection[], - accountsTypesRelated?: string[], - alwaysShow?: boolean, + name: string; + sectionType?: string; + type: 'section' | 'accounts_section'; + children?: IBalanceSheetStructureSection[]; + accountsTypesRelated?: string[]; + alwaysShow?: boolean; } export interface IBalanceSheetAccountTotal { - amount: number, - formattedAmount: string, - currencyCode: string, - date?: string|Date, + amount: number; + formattedAmount: string; + currencyCode: string; + date?: string | Date; } export interface IBalanceSheetAccount { - id: number, - index: number, - name: string, - code: string, - parentAccountId: number, - type: 'account', - hasTransactions: boolean, - children?: IBalanceSheetAccount[], - total: IBalanceSheetAccountTotal, - totalPeriods?: IBalanceSheetAccountTotal[], + id: number; + index: number; + name: string; + code: string; + parentAccountId: number; + type: 'account'; + hasTransactions: boolean; + children?: IBalanceSheetAccount[]; + total: IBalanceSheetAccountTotal; + totalPeriods?: IBalanceSheetAccountTotal[]; } export interface IBalanceSheetSection { - name: string, - sectionType?: string, - type: 'section' | 'accounts_section', - children: IBalanceSheetAccount[] | IBalanceSheetSection[], - total: IBalanceSheetAccountTotal, + name: string; + sectionType?: string; + type: 'section' | 'accounts_section'; + children: IBalanceSheetAccount[] | IBalanceSheetSection[]; + total: IBalanceSheetAccountTotal; totalPeriods?: IBalanceSheetAccountTotal[]; - accountsTypesRelated?: string[], - _forceShow?: boolean, -} \ No newline at end of file + accountsTypesRelated?: string[]; + _forceShow?: boolean; +} diff --git a/server/src/interfaces/FinancialStatements.ts b/server/src/interfaces/FinancialStatements.ts index 139597f9c..52ae49478 100644 --- a/server/src/interfaces/FinancialStatements.ts +++ b/server/src/interfaces/FinancialStatements.ts @@ -1,2 +1,19 @@ +export interface INumberFormatQuery { + precision: number; + divideOn1000: boolean; + showZero: boolean; + formatMoney: 'total' | 'always' | 'none'; + negativeFormat: 'parentheses' | 'mines'; +} - +export interface IFormatNumberSettings { + precision?: number; + divideOn1000?: boolean; + excerptZero?: boolean; + negativeFormat?: 'parentheses' | 'mines'; + thousand?: string; + decimal?: string; + zeroSign?: string; + symbol?: string; + money?: boolean, +} diff --git a/server/src/interfaces/Journal.ts b/server/src/interfaces/Journal.ts index 147ee663f..3a13642e1 100644 --- a/server/src/interfaces/Journal.ts +++ b/server/src/interfaces/Journal.ts @@ -11,6 +11,8 @@ export interface IJournalEntry { referenceType: string, referenceId: number, + referenceTypeFormatted: string, + transactionType?: string, note?: string, userId?: number, diff --git a/server/src/interfaces/ProfitLossSheet.ts b/server/src/interfaces/ProfitLossSheet.ts index be4d2f16a..da0b81059 100644 --- a/server/src/interfaces/ProfitLossSheet.ts +++ b/server/src/interfaces/ProfitLossSheet.ts @@ -1,13 +1,12 @@ - +import { + INumberFormatQuery, +} from './FinancialStatements'; export interface IProfitLossSheetQuery { basis: string, fromDate: Date | string, toDate: Date | string, - numberFormat: { - noCents: boolean, - divideOn1000: boolean, - }, + numberFormat: INumberFormatQuery, noneZero: boolean, noneTransactions: boolean, accountsIds: number[], @@ -34,8 +33,8 @@ export interface IProfitLossSheetAccount { }; export interface IProfitLossSheetAccountsSection { - sectionTitle: string, - entryNormal: 'credit', + name: string, + entryNormal: 'credit' | 'debit', accounts: IProfitLossSheetAccount[], total: IProfitLossSheetTotal, totalPeriods?: IProfitLossSheetTotal[], diff --git a/server/src/interfaces/TrialBalanceSheet.ts b/server/src/interfaces/TrialBalanceSheet.ts index fcaa4fd6d..abd42e5a6 100644 --- a/server/src/interfaces/TrialBalanceSheet.ts +++ b/server/src/interfaces/TrialBalanceSheet.ts @@ -1,38 +1,41 @@ +import { INumberFormatQuery } from './FinancialStatements'; export interface ITrialBalanceSheetQuery { - fromDate: Date|string, - toDate: Date|string, - numberFormat: { - noCents: boolean, - divideOn1000: boolean, - }, - basis: 'cash' | 'accural', - noneZero: boolean, - noneTransactions: boolean, - accountIds: number[], + fromDate: Date | string; + toDate: Date | string; + numberFormat: INumberFormatQuery; + basis: 'cash' | 'accural'; + noneZero: boolean; + noneTransactions: boolean; + accountIds: number[]; } -export interface ITrialBalanceAccount { - id: number, - parentAccountId: number, - name: string, - code: string, - accountNormal: string, - hasTransactions: boolean, +export interface ITrialBalanceTotal { + credit: number; + debit: number; + balance: number; + currencyCode: string; - credit: number, - debit: number, - balance: number, - currencyCode: string, - - formattedCredit: string, - formattedDebit: string, - formattedBalance: string, + formattedCredit: string; + formattedDebit: string; + formattedBalance: string; } -export type ITrialBalanceSheetData = IBalanceSheetSection[]; +export interface ITrialBalanceAccount extends ITrialBalanceTotal { + id: number; + parentAccountId: number; + name: string; + code: string; + accountNormal: string; + hasTransactions: boolean; +} + +export type ITrialBalanceSheetData = { + accounts: ITrialBalanceAccount[]; + total: ITrialBalanceTotal; +}; export interface ITrialBalanceStatement { - data: ITrialBalanceSheetData, - query: ITrialBalanceSheetQuery, -} \ No newline at end of file + data: ITrialBalanceSheetData; + query: ITrialBalanceSheetQuery; +} diff --git a/server/src/services/FinancialStatements/AgingSummary/APAgingSummaryService.ts b/server/src/services/FinancialStatements/AgingSummary/APAgingSummaryService.ts index eebd8241a..ba1916b29 100644 --- a/server/src/services/FinancialStatements/AgingSummary/APAgingSummaryService.ts +++ b/server/src/services/FinancialStatements/AgingSummary/APAgingSummaryService.ts @@ -15,14 +15,17 @@ export default class PayableAgingSummaryService { /** * Default report query. */ - get defaultQuery() { + get defaultQuery(): IAPAgingSummaryQuery { return { asDate: moment().format('YYYY-MM-DD'), agingDaysBefore: 30, agingPeriods: 3, numberFormat: { - noCents: false, + precision: 2, divideOn1000: false, + showZero: false, + formatMoney: 'total', + negativeFormat: 'mines' }, vendorsIds: [], noneZero: false, diff --git a/server/src/services/FinancialStatements/AgingSummary/ARAgingSummaryService.ts b/server/src/services/FinancialStatements/AgingSummary/ARAgingSummaryService.ts index 37960a540..c1d715216 100644 --- a/server/src/services/FinancialStatements/AgingSummary/ARAgingSummaryService.ts +++ b/server/src/services/FinancialStatements/AgingSummary/ARAgingSummaryService.ts @@ -15,14 +15,17 @@ export default class ARAgingSummaryService { /** * Default report query. */ - get defaultQuery() { + get defaultQuery(): IARAgingSummaryQuery { return { asDate: moment().format('YYYY-MM-DD'), agingDaysBefore: 30, agingPeriods: 3, numberFormat: { - no_cents: false, - divide_1000: false, + divideOn1000: false, + negativeFormat: 'mines', + showZero: false, + formatMoney: 'total', + precision: 2, }, customersIds: [], noneZero: false, @@ -50,6 +53,7 @@ export default class ARAgingSummaryService { }); // Settings tenant service. const settings = this.tenancy.settings(tenantId); + const baseCurrency = settings.get({ group: 'organization', key: 'base_currency', diff --git a/server/src/services/FinancialStatements/AgingSummary/ARAgingSummarySheet.ts b/server/src/services/FinancialStatements/AgingSummary/ARAgingSummarySheet.ts index 39f0d18fa..d75d74245 100644 --- a/server/src/services/FinancialStatements/AgingSummary/ARAgingSummarySheet.ts +++ b/server/src/services/FinancialStatements/AgingSummary/ARAgingSummarySheet.ts @@ -68,7 +68,7 @@ export default class ARAgingSummarySheet extends AgingSummaryReport { return { customerName: customer.displayName, - current: this.formatTotalAmount(currentTotal), + current: this.formatAmount(currentTotal), aging: agingPeriods, total: this.formatTotalAmount(amount), }; diff --git a/server/src/services/FinancialStatements/AgingSummary/AgingSummary.ts b/server/src/services/FinancialStatements/AgingSummary/AgingSummary.ts index b13ecaaa2..81fc3d393 100644 --- a/server/src/services/FinancialStatements/AgingSummary/AgingSummary.ts +++ b/server/src/services/FinancialStatements/AgingSummary/AgingSummary.ts @@ -7,6 +7,7 @@ import { IARAgingSummaryCustomer, IContact, IARAgingSummaryQuery, + IFormatNumberSettings, } from 'interfaces'; import AgingReport from './AgingReport'; import { Dictionary } from 'tsyringe/dist/typings/types'; @@ -30,7 +31,7 @@ export default abstract class AgingSummaryReport extends AgingReport { protected getInitialAgingPeriodsTotal() { return this.agingPeriods.map((agingPeriod) => ({ ...agingPeriod, - ...this.formatTotalAmount(0), + ...this.formatAmount(0), })); } @@ -70,12 +71,15 @@ export default abstract class AgingSummaryReport extends AgingReport { const isInAgingPeriod = agingPeriod.beforeDays <= overdueDays && (agingPeriod.toDays > overdueDays || !agingPeriod.toDays); + + const total = isInAgingPeriod + ? agingPeriod.total + dueAmount + : agingPeriod.total; return { ...agingPeriod, - total: isInAgingPeriod - ? agingPeriod.total + dueAmount - : agingPeriod.total, + total, + formattedAmount: this.formatAmount(total), }; }); return newAgingPeriods; @@ -86,14 +90,28 @@ export default abstract class AgingSummaryReport extends AgingReport { * @param {number} amount * @return {IAgingPeriodTotal} */ - protected formatTotalAmount(amount: number): IAgingPeriodTotal { + protected formatAmount( + amount: number, + settings: IFormatNumberSettings = {} + ): IAgingPeriodTotal { return { total: amount, - formattedTotal: this.formatNumber(amount), + formattedTotal: this.formatNumber(amount, settings), currencyCode: this.baseCurrency, }; } + protected formatTotalAmount( + amount: number, + settings: IFormatNumberSettings = {} + ): IAgingPeriodTotal { + return this.formatAmount(amount, { + money: true, + excerptZero: false, + ...settings + }); + } + /** * Calculates the total of the aging period by the given index. * @param {number} index @@ -142,7 +160,7 @@ export default abstract class AgingSummaryReport extends AgingReport { /** * Retrieve the current invoices by the given contact id. - * @param {number} contactId + * @param {number} contactId - Specific contact id. * @return {(ISaleInvoice | IBill)[]} */ protected getCurrentInvoicesByContactId( @@ -153,23 +171,23 @@ export default abstract class AgingSummaryReport extends AgingReport { /** * Retrieve the contact total due amount. - * @param {number} contactId + * @param {number} contactId - Specific contact id. * @return {number} */ protected getContactCurrentTotal(contactId: number): number { const currentInvoices = this.getCurrentInvoicesByContactId(contactId); - return sumBy(currentInvoices, invoice => invoice.dueAmount); + return sumBy(currentInvoices, (invoice) => invoice.dueAmount); } /** * Retrieve to total sumation of the given customers sections. - * @param {IARAgingSummaryCustomer[]} contactsSections - + * @param {IARAgingSummaryCustomer[]} contactsSections - * @return {number} */ protected getTotalCurrent( customersSummary: IARAgingSummaryCustomer[] ): number { - return sumBy(customersSummary, summary => summary.current.total); + return sumBy(customersSummary, (summary) => summary.current.total); } /** @@ -177,9 +195,7 @@ export default abstract class AgingSummaryReport extends AgingReport { * @param {IAgingPeriodTotal[]} agingPeriods * @return {number} */ - protected getAgingPeriodsTotal( - agingPeriods: IAgingPeriodTotal[], - ): number { + protected getAgingPeriodsTotal(agingPeriods: IAgingPeriodTotal[]): number { return sumBy(agingPeriods, 'total'); } } diff --git a/server/src/services/FinancialStatements/BalanceSheet/BalanceSheet.ts b/server/src/services/FinancialStatements/BalanceSheet/BalanceSheet.ts index 87dcbdaf5..6c36c3c31 100644 --- a/server/src/services/FinancialStatements/BalanceSheet/BalanceSheet.ts +++ b/server/src/services/FinancialStatements/BalanceSheet/BalanceSheet.ts @@ -73,7 +73,7 @@ export default class BalanceSheetStatement extends FinancialSheet { sections: IBalanceSheetSection[] ): IBalanceSheetAccountTotal { const amount = sumBy(sections, 'total.amount'); - const formattedAmount = this.formatNumber(amount); + const formattedAmount = this.formatTotalNumber(amount); const currencyCode = this.baseCurrency; return { amount, formattedAmount, currencyCode }; @@ -89,7 +89,25 @@ export default class BalanceSheetStatement extends FinancialSheet { ): IBalanceSheetAccountTotal[] { return this.dateRangeSet.map((date, index) => { const amount = sumBy(sections, `totalPeriods[${index}].amount`); - const formattedAmount = this.formatNumber(amount); + + const formattedAmount = this.formatTotalNumber(amount); + const currencyCode = this.baseCurrency; + + return { date, amount, formattedAmount, currencyCode }; + }); + } + + /** + * Retrieve accounts total periods. + * @param {Array} accounts - + * @return {IBalanceSheetAccountTotal[]} + */ + private getAccountsTotalPeriods( + accounts: Array + ): IBalanceSheetAccountTotal[] { + return this.dateRangeSet.map((date, index) => { + const amount = sumBy(accounts, `totalPeriods[${index}].amount`); + const formattedAmount = this.formatNumber(amount) const currencyCode = this.baseCurrency; return { date, amount, formattedAmount, currencyCode }; @@ -190,12 +208,12 @@ export default class BalanceSheetStatement extends FinancialSheet { }), total: { amount: totalAmount, - formattedAmount: this.formatNumber(totalAmount), + formattedAmount: this.formatTotalNumber(totalAmount), currencyCode: this.baseCurrency, }, ...(this.query.displayColumnsType === 'date_periods' ? { - totalPeriods: this.getSectionTotalPeriods(filteredAccounts), + totalPeriods: this.getAccountsTotalPeriods(filteredAccounts), } : {}), }; @@ -232,7 +250,7 @@ export default class BalanceSheetStatement extends FinancialSheet { */ private balanceSheetStructureMapper( structure: IBalanceSheetStructureSection, - accounts: IAccount & { type: IAccountType }[] + accounts: IAccount & { type: IAccountType }[], ): IBalanceSheetSection { const result = { name: structure.name, @@ -276,14 +294,9 @@ export default class BalanceSheetStatement extends FinancialSheet { } ) // Mappes the balance sheet scetions only - .map( - ([sheetSection, structure]: [ - IBalanceSheetSection, - IBalanceSheetStructureSection - ]) => { - return sheetSection; - } - ) + .map(([sheetSection]: [IBalanceSheetSection]) => { + return sheetSection; + }) ); } diff --git a/server/src/services/FinancialStatements/BalanceSheet/BalanceSheetService.ts b/server/src/services/FinancialStatements/BalanceSheet/BalanceSheetService.ts index fc1e96212..8af288074 100644 --- a/server/src/services/FinancialStatements/BalanceSheet/BalanceSheetService.ts +++ b/server/src/services/FinancialStatements/BalanceSheet/BalanceSheetService.ts @@ -29,8 +29,11 @@ export default class BalanceSheetStatementService fromDate: moment().startOf('year').format('YYYY-MM-DD'), toDate: moment().endOf('year').format('YYYY-MM-DD'), numberFormat: { - noCents: false, + precision: 2, divideOn1000: false, + showZero: false, + formatMoney: 'total', + negativeFormat: 'mines' }, noneZero: false, noneTransactions: false, diff --git a/server/src/services/FinancialStatements/ProfitLossSheet/ProfitLossSheet.ts b/server/src/services/FinancialStatements/ProfitLossSheet/ProfitLossSheet.ts index 1ab120beb..9562acdf6 100644 --- a/server/src/services/FinancialStatements/ProfitLossSheet/ProfitLossSheet.ts +++ b/server/src/services/FinancialStatements/ProfitLossSheet/ProfitLossSheet.ts @@ -187,7 +187,7 @@ export default class ProfitLossSheet extends FinancialSheet { accounts: IProfitLossSheetAccount[] ): IProfitLossSheetTotal { const amount = sumBy(accounts, 'total.amount'); - const formattedAmount = this.formatNumber(amount); + const formattedAmount = this.formatTotalNumber(amount); const currencyCode = this.baseCurrency; return { amount, formattedAmount, currencyCode }; @@ -203,7 +203,7 @@ export default class ProfitLossSheet extends FinancialSheet { ): IProfitLossSheetTotal[] { return this.dateRangeSet.map((date, index) => { const amount = sumBy(accounts, `totalPeriods[${index}].amount`); - const formattedAmount = this.formatNumber(amount); + const formattedAmount = this.formatTotalNumber(amount); const currencyCode = this.baseCurrency; return { amount, formattedAmount, currencyCode }; @@ -229,7 +229,7 @@ export default class ProfitLossSheet extends FinancialSheet { */ private get incomeSection(): IProfitLossSheetAccountsSection { return { - sectionTitle: 'Income accounts', + name: 'Income accounts', entryNormal: 'credit', ...this.sectionMapper(this.incomeAccounts), }; @@ -241,7 +241,7 @@ export default class ProfitLossSheet extends FinancialSheet { */ private get expensesSection(): IProfitLossSheetAccountsSection { return { - sectionTitle: 'Expense accounts', + name: 'Expense accounts', entryNormal: 'debit', ...this.sectionMapper(this.expensesAccounts), }; @@ -253,7 +253,7 @@ export default class ProfitLossSheet extends FinancialSheet { */ private get otherExpensesSection(): IProfitLossSheetAccountsSection { return { - sectionTitle: 'Other expenses accounts', + name: 'Other expenses accounts', entryNormal: 'debit', ...this.sectionMapper(this.otherExpensesAccounts), }; @@ -265,7 +265,7 @@ export default class ProfitLossSheet extends FinancialSheet { */ private get costOfSalesSection(): IProfitLossSheetAccountsSection { return { - sectionTitle: 'Cost of sales', + name: 'Cost of sales', entryNormal: 'debit', ...this.sectionMapper(this.costOfSalesAccounts), }; @@ -283,7 +283,7 @@ export default class ProfitLossSheet extends FinancialSheet { const totalMines = sumBy(minesSections, `totalPeriods[${index}].amount`); const amount = totalPositive - totalMines; - const formattedAmount = this.formatNumber(amount); + const formattedAmount = this.formatTotalNumber(amount); const currencyCode = this.baseCurrency; return { date, amount, formattedAmount, currencyCode }; @@ -298,7 +298,7 @@ export default class ProfitLossSheet extends FinancialSheet { const totalMinesSections = sumBy(minesSections, 'total.amount'); const amount = totalPositiveSections - totalMinesSections; - const formattedAmount = this.formatNumber(amount); + const formattedAmount = this.formatTotalNumber(amount); const currencyCode = this.baseCurrency; return { amount, formattedAmount, currencyCode }; diff --git a/server/src/services/FinancialStatements/ProfitLossSheet/ProfitLossSheetService.ts b/server/src/services/FinancialStatements/ProfitLossSheet/ProfitLossSheetService.ts index af021cdda..fac09cc68 100644 --- a/server/src/services/FinancialStatements/ProfitLossSheet/ProfitLossSheetService.ts +++ b/server/src/services/FinancialStatements/ProfitLossSheet/ProfitLossSheetService.ts @@ -27,8 +27,11 @@ export default class ProfitLossSheetService { fromDate: moment().startOf('year').format('YYYY-MM-DD'), toDate: moment().endOf('year').format('YYYY-MM-DD'), numberFormat: { - noCents: false, divideOn1000: false, + negativeFormat: 'mines', + showZero: false, + formatMoney: 'total', + precision: 2, }, basis: 'accural', noneZero: false, @@ -41,8 +44,8 @@ export default class ProfitLossSheetService { /** * Retrieve profit/loss sheet statement. - * @param {number} tenantId - * @param {IProfitLossSheetQuery} query + * @param {number} tenantId + * @param {IProfitLossSheetQuery} query * @return { } */ async profitLossSheet(tenantId: number, query: IProfitLossSheetQuery) { @@ -55,16 +58,24 @@ export default class ProfitLossSheetService { ...this.defaultQuery, ...query, }; - this.logger.info('[profit_loss_sheet] trying to calculate the report.', { tenantId, filter }); + this.logger.info('[profit_loss_sheet] trying to calculate the report.', { + tenantId, + filter, + }); // Get the given accounts or throw not found service error. if (filter.accountsIds.length > 0) { - await this.accountsService.getAccountsOrThrowError(tenantId, filter.accountsIds); + await this.accountsService.getAccountsOrThrowError( + tenantId, + filter.accountsIds + ); } // Settings tenant service. const settings = this.tenancy.settings(tenantId); - const baseCurrency = settings.get({ group: 'organization', key: 'base_currency' }); - + const baseCurrency = settings.get({ + group: 'organization', + key: 'base_currency', + }); // Retrieve all accounts on the storage. const accounts = await accountRepository.all('type'); const accountsGraph = await accountRepository.getDependencyGraph(); @@ -75,8 +86,11 @@ export default class ProfitLossSheetService { toDate: query.toDate, }); // Transform transactions to journal collection. - const transactionsJournal = Journal.fromTransactions(transactions, tenantId, accountsGraph); - + const transactionsJournal = Journal.fromTransactions( + transactions, + tenantId, + accountsGraph + ); // Profit/Loss report instance. const profitLossInstance = new ProfitLossSheet( tenantId, @@ -95,4 +109,4 @@ export default class ProfitLossSheetService { query: filter, }; } -} \ No newline at end of file +} diff --git a/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheet.ts b/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheet.ts index 0fe38c8a1..9c13915b1 100644 --- a/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheet.ts +++ b/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheet.ts @@ -1,12 +1,19 @@ +import { sumBy } from 'lodash'; import { ITrialBalanceSheetQuery, ITrialBalanceAccount, IAccount, + ITrialBalanceTotal, IAccountType, } from 'interfaces'; import FinancialSheet from '../FinancialSheet'; import { flatToNestedArray } from 'utils'; +const AMOUNT_TYPE = { + TOTAL: 'TOTAL', + SECTION_TOTAL: 'SECTION_TOTAL', +}; + export default class TrialBalanceSheet extends FinancialSheet { tenantId: number; query: ITrialBalanceSheetQuery; @@ -103,10 +110,37 @@ export default class TrialBalanceSheet extends FinancialSheet { }); } + /** + * Retrieve trial balance total section. + * @param {ITrialBalanceAccount[]} accountsBalances + * @return {ITrialBalanceTotal} + */ + private tatalSection( + accountsBalances: ITrialBalanceAccount[] + ): ITrialBalanceTotal { + const credit = sumBy(accountsBalances, 'credit'); + const debit = sumBy(accountsBalances, 'debit'); + const balance = sumBy(accountsBalances, 'balance'); + const currencyCode = this.baseCurrency; + + return { + credit, + debit, + balance, + currencyCode, + formattedCredit: this.formatTotalNumber(credit), + formattedDebit: this.formatTotalNumber(debit), + formattedBalance: this.formatTotalNumber(balance), + }; + } + /** * Retrieve trial balance sheet statement data. */ public reportData() { - return this.accountsWalker(this.accounts); + const accounts = this.accountsWalker(this.accounts); + const total = this.tatalSection(accounts); + + return { accounts, total }; } } diff --git a/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetService.ts b/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetService.ts index d82b848f1..3fab2408e 100644 --- a/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetService.ts +++ b/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetService.ts @@ -1,12 +1,13 @@ import { Service, Inject } from 'typedi'; import moment from 'moment'; import TenancyService from 'services/Tenancy/TenancyService'; -import { ITrialBalanceSheetQuery, ITrialBalanceStatement } from 'interfaces'; -import TrialBalanceSheet from './TrialBalanceSheet'; import Journal from 'services/Accounting/JournalPoster'; +import { INumberFormatQuery, ITrialBalanceSheetQuery, ITrialBalanceStatement } from 'interfaces'; +import TrialBalanceSheet from './TrialBalanceSheet'; +import FinancialSheet from '../FinancialSheet'; @Service() -export default class TrialBalanceSheetService { +export default class TrialBalanceSheetService extends FinancialSheet { @Inject() tenancy: TenancyService; @@ -22,8 +23,11 @@ export default class TrialBalanceSheetService { fromDate: moment().startOf('year').format('YYYY-MM-DD'), toDate: moment().endOf('year').format('YYYY-MM-DD'), numberFormat: { - noCents: false, divideOn1000: false, + negativeFormat: 'mines', + showZero: false, + formatMoney: 'total', + precision: 2, }, basis: 'accural', noneZero: false, @@ -42,7 +46,7 @@ export default class TrialBalanceSheetService { */ public async trialBalanceSheet( tenantId: number, - query: ITrialBalanceSheetQuery + query: ITrialBalanceSheetQuery, ): Promise { const filter = { ...this.defaultQuery, diff --git a/server/src/utils/index.js b/server/src/utils/index.js index 8995bc7b6..688dc4b18 100644 --- a/server/src/utils/index.js +++ b/server/src/utils/index.js @@ -1,6 +1,7 @@ import bcrypt from 'bcryptjs'; import moment from 'moment'; import _ from 'lodash'; +import accounting from 'accounting'; import definedOptions from 'data/options'; const hashPassword = (password) => @@ -192,7 +193,7 @@ const entriesAmountDiff = ( .groupBy(idAttribute) .mapValues((group) => _.sumBy(group, amountAttribute) || 0) .mergeWith(oldEntriesTable, (objValue, srcValue) => { - return (_.isNumber(objValue) ? objValue - srcValue : srcValue * -1); + return _.isNumber(objValue) ? objValue - srcValue : srcValue * -1; }) .value(); @@ -214,27 +215,56 @@ const convertEmptyStringToNull = (value) => { : value; }; -const formatNumber = (balance, { noCents = false, divideOn1000 = false }) => { +const getNegativeFormat = (formatName) => { + switch (formatName) { + case 'parentheses': + return '(%s%v)'; + case 'mines': + return '-%s%v'; + } +}; + +const formatNumber = ( + balance, + { + precision = 2, + divideOn1000 = false, + excerptZero = false, + negativeFormat = 'mines', + thousand = ',', + decimal = '.', + zeroSign = '', + symbol = '$', + money = true, + } +) => { + const negForamt = getNegativeFormat(negativeFormat); + const format = '%s%v'; + let formattedBalance = parseFloat(balance); - if (noCents) { - formattedBalance = parseInt(formattedBalance, 10); - } if (divideOn1000) { formattedBalance /= 1000; } - return formattedBalance + ''; + return accounting.formatMoney( + formattedBalance, + money ? symbol : '', + precision, + thousand, + decimal, + { + pos: format, + neg: negForamt, + zero: excerptZero ? zeroSign : format, + } + ); }; const isBlank = (value) => { - return _.isEmpty(value) && !_.isNumber(value) || _.isNaN(value); -} + return (_.isEmpty(value) && !_.isNumber(value)) || _.isNaN(value); +}; -function defaultToTransform( - value, - defaultOrTransformedValue, - defaultValue, -) { +function defaultToTransform(value, defaultOrTransformedValue, defaultValue) { const _defaultValue = typeof defaultValue === 'undefined' ? defaultOrTransformedValue @@ -248,7 +278,6 @@ function defaultToTransform( : _transfromedValue; } - export { hashPassword, origin,