From 21eb88ef53a1c0b52cde47beb8b3ad8fa12cf05b Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Wed, 24 Jan 2024 00:26:56 +0200 Subject: [PATCH 01/39] chore: dump CHANGELOG.md file --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca8948cf1..4c2938ad4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,19 @@ All notable changes to Bigcapital server-side will be in this file. +## [0.13.3] - 22-01-2024 + +* hotfix(server): Unhandled thrown errors of services by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/329 + +## [0.13.2] - 21-01-2024 + +* feat: show customer / vendor balance. by @asenawritescode in https://github.com/bigcapitalhq/bigcapital/pull/311 +* feat: inventory valuation csv and xlsx export by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/308 +* feat: sales by items export csv & xlsx by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/310 +* fix(server): the invoice and payment receipt printing by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/315 +* fix: get cashflow transaction broken cause transaction type by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/318 +* fix: `AccountActivateAlert` import by @xprnio in https://github.com/bigcapitalhq/bigcapital/pull/322 + ## [0.13.1] - 15-01-2024 * feat(webapp): add approve/reject to action bar of estimate details dr… by @ANasouf in https://github.com/bigcapitalhq/bigcapital/pull/304 From 475c4e996717060717f49681b6179f0493e213c4 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Fri, 26 Jan 2024 00:05:24 +0200 Subject: [PATCH 02/39] fix(webapp): inconsistency in currency of universal search items --- .../src/containers/Sales/Invoices/InvoiceUniversalSearch.tsx | 4 ++-- .../src/containers/Sales/Receipts/ReceiptUniversalSearch.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceUniversalSearch.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceUniversalSearch.tsx index 84a8858e4..1656b9bc7 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceUniversalSearch.tsx +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceUniversalSearch.tsx @@ -83,13 +83,13 @@ export function InvoiceUniversalSearchItem( {highlightText(item.reference.invoice_no, query)}{' '} - {item.reference.formatted_invoice_date} + {item.reference.invoice_date_formatted} } label={ <> -
${item.reference.balance}
+
${item.reference.total_formatted}
} diff --git a/packages/webapp/src/containers/Sales/Receipts/ReceiptUniversalSearch.tsx b/packages/webapp/src/containers/Sales/Receipts/ReceiptUniversalSearch.tsx index ec050da8f..2eb9405ac 100644 --- a/packages/webapp/src/containers/Sales/Receipts/ReceiptUniversalSearch.tsx +++ b/packages/webapp/src/containers/Sales/Receipts/ReceiptUniversalSearch.tsx @@ -75,7 +75,7 @@ export function ReceiptUniversalSearchItem( } label={ <> -
${item.reference.amount}
+
${item.reference.formatted_amount}
} From de5920f9109ef90c7cdf890712041b889d210cd2 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Fri, 26 Jan 2024 23:46:45 +0200 Subject: [PATCH 03/39] fix: Expense amounts should not be rounded --- packages/server/src/models/ExpenseCategory.ts | 2 ++ .../CRUD/ExpenseCategoryTransformer.ts | 25 +++++++++++++++++++ .../Expenses/CRUD/ExpenseTransformer.ts | 17 +++++++++++-- .../ExpenseDrawer/ExpenseDrawerFooter.tsx | 6 ++--- .../Drawers/ExpenseDrawer/utils.tsx | 3 +-- 5 files changed, 46 insertions(+), 7 deletions(-) create mode 100644 packages/server/src/services/Expenses/CRUD/ExpenseCategoryTransformer.ts diff --git a/packages/server/src/models/ExpenseCategory.ts b/packages/server/src/models/ExpenseCategory.ts index 50416805e..21d61f7e8 100644 --- a/packages/server/src/models/ExpenseCategory.ts +++ b/packages/server/src/models/ExpenseCategory.ts @@ -2,6 +2,8 @@ import { Model } from 'objection'; import TenantModel from 'models/TenantModel'; export default class ExpenseCategory extends TenantModel { + amount: number; + /** * Table name */ diff --git a/packages/server/src/services/Expenses/CRUD/ExpenseCategoryTransformer.ts b/packages/server/src/services/Expenses/CRUD/ExpenseCategoryTransformer.ts new file mode 100644 index 000000000..3f8383c03 --- /dev/null +++ b/packages/server/src/services/Expenses/CRUD/ExpenseCategoryTransformer.ts @@ -0,0 +1,25 @@ +import { Transformer } from '@/lib/Transformer/Transformer'; +import { ExpenseCategory } from '@/models'; +import { formatNumber } from '@/utils'; + +export class ExpenseCategoryTransformer extends Transformer { + /** + * Include these attributes to expense object. + * @returns {Array} + */ + public includeAttributes = (): string[] => { + return ['amountFormatted']; + }; + + /** + * Retrieves the formatted amount. + * @param {ExpenseCategory} category + * @returns {string} + */ + protected amountFormatted(category: ExpenseCategory) { + return formatNumber(category.amount, { + currencyCode: this.context.currencyCode, + money: false, + }); + } +} diff --git a/packages/server/src/services/Expenses/CRUD/ExpenseTransformer.ts b/packages/server/src/services/Expenses/CRUD/ExpenseTransformer.ts index 2812a9261..89f461934 100644 --- a/packages/server/src/services/Expenses/CRUD/ExpenseTransformer.ts +++ b/packages/server/src/services/Expenses/CRUD/ExpenseTransformer.ts @@ -1,6 +1,7 @@ import { Transformer } from '@/lib/Transformer/Transformer'; import { formatNumber } from 'utils'; import { IExpense } from '@/interfaces'; +import { ExpenseCategoryTransformer } from './ExpenseCategoryTransformer'; export class ExpenseTransfromer extends Transformer { /** @@ -12,7 +13,8 @@ export class ExpenseTransfromer extends Transformer { 'formattedAmount', 'formattedLandedCostAmount', 'formattedAllocatedCostAmount', - 'formattedDate' + 'formattedDate', + 'categories', ]; }; @@ -56,5 +58,16 @@ export class ExpenseTransfromer extends Transformer { */ protected formattedDate = (expense: IExpense): string => { return this.formatDate(expense.paymentDate); - } + }; + + /** + * Retrieves the transformed expense categories. + * @param {IExpense} expense + * @returns {} + */ + protected categories = (expense: IExpense) => { + return this.item(expense.categories, new ExpenseCategoryTransformer(), { + currencyCode: expense.currencyCode, + }); + }; } diff --git a/packages/webapp/src/containers/Drawers/ExpenseDrawer/ExpenseDrawerFooter.tsx b/packages/webapp/src/containers/Drawers/ExpenseDrawer/ExpenseDrawerFooter.tsx index c1acea3f2..48b3b2514 100644 --- a/packages/webapp/src/containers/Drawers/ExpenseDrawer/ExpenseDrawerFooter.tsx +++ b/packages/webapp/src/containers/Drawers/ExpenseDrawer/ExpenseDrawerFooter.tsx @@ -9,7 +9,7 @@ import { TotalLineTextStyle, } from '@/components'; import { useExpenseDrawerContext } from './ExpenseDrawerProvider'; -import { FormatNumber, TotalLine } from '@/components'; +import { TotalLine } from '@/components'; /** * Footer details of expense readonly details. @@ -22,12 +22,12 @@ export default function ExpenseDrawerFooter() { } - value={} + value={expense.formatted_amount} borderStyle={TotalLineBorderStyle.SingleDark} /> } - value={} + value={expense.formatted_amount} borderStyle={TotalLineBorderStyle.DoubleDark} textStyle={TotalLineTextStyle.Bold} /> diff --git a/packages/webapp/src/containers/Drawers/ExpenseDrawer/utils.tsx b/packages/webapp/src/containers/Drawers/ExpenseDrawer/utils.tsx index 276923cce..377f6f9a4 100644 --- a/packages/webapp/src/containers/Drawers/ExpenseDrawer/utils.tsx +++ b/packages/webapp/src/containers/Drawers/ExpenseDrawer/utils.tsx @@ -36,8 +36,7 @@ export const useExpenseReadEntriesColumns = () => { }, { Header: intl.get('amount'), - accessor: 'amount', - Cell: FormatNumberCell, + accessor: 'amount_formatted', width: getColumnWidth(categories, 'amount', { minWidth: 60, magicSpacing: 5, From ac7175d83b7d0109c39d89826a4b7f5dff32262b Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Sun, 28 Jan 2024 15:52:54 +0200 Subject: [PATCH 04/39] feat: get latest exchange rate from third party services --- .env.example | 8 +- .../src/api/controllers/ExchangeRates.ts | 214 +++++------------- packages/server/src/config/index.ts | 10 + .../src/lib/ExchangeRate/ExchangeRate.ts | 45 ++++ .../src/lib/ExchangeRate/OpenExchangeRate.ts | 62 +++++ packages/server/src/lib/ExchangeRate/types.ts | 17 ++ .../ExchangeRates/ExchangeRatesService.ts | 208 +++-------------- 7 files changed, 224 insertions(+), 340 deletions(-) create mode 100644 packages/server/src/lib/ExchangeRate/ExchangeRate.ts create mode 100644 packages/server/src/lib/ExchangeRate/OpenExchangeRate.ts create mode 100644 packages/server/src/lib/ExchangeRate/types.ts diff --git a/.env.example b/.env.example index 78945ab91..7af1c6061 100644 --- a/.env.example +++ b/.env.example @@ -57,4 +57,10 @@ GOTENBERG_DOCS_URL=http://server:3000/public/ # Gotenberg API - (development) # GOTENBERG_URL=http://localhost:9000 -# GOTENBERG_DOCS_URL=http://host.docker.internal:3000/public/ \ No newline at end of file +# GOTENBERG_DOCS_URL=http://host.docker.internal:3000/public/ + +# Exchange Rate Service +EXCHANGE_RATE_SERVICE=open-exchange-rate + +# Open Exchange Rate +OPEN_EXCHANGE_RATE_APP_I= \ No newline at end of file diff --git a/packages/server/src/api/controllers/ExchangeRates.ts b/packages/server/src/api/controllers/ExchangeRates.ts index 4b808e921..4ea59ad1b 100644 --- a/packages/server/src/api/controllers/ExchangeRates.ts +++ b/packages/server/src/api/controllers/ExchangeRates.ts @@ -4,16 +4,13 @@ import { check, param, query } from 'express-validator'; import asyncMiddleware from '@/api/middleware/asyncMiddleware'; import BaseController from './BaseController'; import { ServiceError } from '@/exceptions'; -import ExchangeRatesService from '@/services/ExchangeRates/ExchangeRatesService'; -import DynamicListingService from '@/services/DynamicListing/DynamicListService'; +import { ExchangeRatesService } from '@/services/ExchangeRates/ExchangeRatesService'; +import { EchangeRateErrors } from '@/lib/ExchangeRate/types'; @Service() export default class ExchangeRatesController extends BaseController { @Inject() - exchangeRatesService: ExchangeRatesService; - - @Inject() - dynamicListService: DynamicListingService; + private exchangeRatesService: ExchangeRatesService; /** * Constructor method. @@ -22,164 +19,35 @@ export default class ExchangeRatesController extends BaseController { const router = Router(); router.get( - '/', - [...this.exchangeRatesListSchema], + '/latest', + [query('to_currency').exists().isString()], this.validationResult, - asyncMiddleware(this.exchangeRates.bind(this)), - this.dynamicListService.handlerErrorsToResponse, - this.handleServiceError, - ); - router.post( - '/', - [...this.exchangeRateDTOSchema], - this.validationResult, - asyncMiddleware(this.addExchangeRate.bind(this)), - this.handleServiceError - ); - router.post( - '/:id', - [...this.exchangeRateEditDTOSchema, ...this.exchangeRateIdSchema], - this.validationResult, - asyncMiddleware(this.editExchangeRate.bind(this)), - this.handleServiceError - ); - router.delete( - '/:id', - [...this.exchangeRateIdSchema], - this.validationResult, - asyncMiddleware(this.deleteExchangeRate.bind(this)), + asyncMiddleware(this.latestExchangeRate.bind(this)), this.handleServiceError ); return router; } - get exchangeRatesListSchema() { - return [ - query('page').optional().isNumeric().toInt(), - query('page_size').optional().isNumeric().toInt(), - - query('column_sort_by').optional(), - query('sort_order').optional().isIn(['desc', 'asc']), - ]; - } - - get exchangeRateDTOSchema() { - return [ - check('exchange_rate').exists().isNumeric().toFloat(), - check('currency_code').exists().trim().escape(), - check('date').exists().isISO8601(), - ]; - } - - get exchangeRateEditDTOSchema() { - return [check('exchange_rate').exists().isNumeric().toFloat()]; - } - - get exchangeRateIdSchema() { - return [param('id').isNumeric().toInt()]; - } - - get exchangeRatesIdsSchema() { - return [ - query('ids').isArray({ min: 2 }), - query('ids.*').isNumeric().toInt(), - ]; - } - /** * Retrieve exchange rates. * @param {Request} req * @param {Response} res * @param {NextFunction} next */ - async exchangeRates(req: Request, res: Response, next: NextFunction) { + private async latestExchangeRate( + req: Request, + res: Response, + next: NextFunction + ) { const { tenantId } = req; - const filter = { - page: 1, - pageSize: 12, - filterRoles: [], - columnSortBy: 'created_at', - sortOrder: 'asc', - ...this.matchedQueryData(req), - }; - if (filter.stringifiedFilterRoles) { - filter.filterRoles = JSON.parse(filter.stringifiedFilterRoles); - } - try { - const exchangeRates = await this.exchangeRatesService.listExchangeRates( - tenantId, - filter - ); - return res.status(200).send({ exchange_rates: exchangeRates }); - } catch (error) { - next(error); - } - } - - /** - * Adds a new exchange rate on the given date. - * @param {Request} req - * @param {Response} res - * @param {NextFunction} next - */ - async addExchangeRate(req: Request, res: Response, next: NextFunction) { - const { tenantId } = req; - const exchangeRateDTO = this.matchedBodyData(req); + const exchangeRateQuery = this.matchedQueryData(req); try { - const exchangeRate = await this.exchangeRatesService.newExchangeRate( + const exchangeRate = await this.exchangeRatesService.latest( tenantId, - exchangeRateDTO + exchangeRateQuery ); - return res.status(200).send({ id: exchangeRate.id }); - } catch (error) { - next(error); - } - } - - /** - * Edit the given exchange rate. - * @param {Request} req - * @param {Response} res - * @param {NextFunction} next - */ - async editExchangeRate(req: Request, res: Response, next: NextFunction) { - const { tenantId } = req; - const { id: exchangeRateId } = req.params; - const exchangeRateDTO = this.matchedBodyData(req); - - try { - const exchangeRate = await this.exchangeRatesService.editExchangeRate( - tenantId, - exchangeRateId, - exchangeRateDTO - ); - - return res.status(200).send({ - id: exchangeRateId, - message: 'The exchange rate has been edited successfully.', - }); - } catch (error) { - next(error); - } - } - - /** - * Delete the given exchange rate from the storage. - * @param {Request} req - * @param {Response} res - * @param {NextFunction} next - */ - async deleteExchangeRate(req: Request, res: Response, next: NextFunction) { - const { tenantId } = req; - const { id: exchangeRateId } = req.params; - - try { - await this.exchangeRatesService.deleteExchangeRate( - tenantId, - exchangeRateId - ); - return res.status(200).send({ id: exchangeRateId }); + return res.status(200).send(exchangeRate); } catch (error) { next(error); } @@ -192,26 +60,56 @@ export default class ExchangeRatesController extends BaseController { * @param {Response} res * @param {NextFunction} next */ - handleServiceError( + private handleServiceError( error: Error, req: Request, res: Response, next: NextFunction ) { if (error instanceof ServiceError) { - if (error.errorType === 'EXCHANGE_RATE_NOT_FOUND') { - return res.status(404).send({ - errors: [{ type: 'EXCHANGE.RATE.NOT.FOUND', code: 200 }], - }); - } - if (error.errorType === 'NOT_FOUND_EXCHANGE_RATES') { + if (EchangeRateErrors.EX_RATE_INVALID_BASE_CURRENCY === error.errorType) { return res.status(400).send({ - errors: [{ type: 'EXCHANGE.RATES.IS.NOT.FOUND', code: 100 }], + errors: [ + { + type: EchangeRateErrors.EX_RATE_INVALID_BASE_CURRENCY, + code: 100, + message: 'The given base currency is invalid.', + }, + ], }); - } - if (error.errorType === 'EXCHANGE_RATE_PERIOD_EXISTS') { + } else if ( + EchangeRateErrors.EX_RATE_SERVICE_NOT_ALLOWED === error.errorType + ) { return res.status(400).send({ - errors: [{ type: 'EXCHANGE.RATE.PERIOD.EXISTS', code: 300 }], + errors: [ + { + type: EchangeRateErrors.EX_RATE_SERVICE_NOT_ALLOWED, + code: 200, + message: 'The service is not allowed', + }, + ], + }); + } else if ( + EchangeRateErrors.EX_RATE_SERVICE_API_KEY_REQUIRED === error.errorType + ) { + return res.status(400).send({ + errors: [ + { + type: EchangeRateErrors.EX_RATE_SERVICE_API_KEY_REQUIRED, + code: 300, + message: 'The API key is required', + }, + ], + }); + } else if (EchangeRateErrors.EX_RATE_LIMIT_EXCEEDED === error.errorType) { + return res.status(400).send({ + errors: [ + { + type: EchangeRateErrors.EX_RATE_LIMIT_EXCEEDED, + code: 400, + message: 'The API rate limit has been exceeded', + }, + ], }); } } diff --git a/packages/server/src/config/index.ts b/packages/server/src/config/index.ts index 0dc9d9676..4d096875a 100644 --- a/packages/server/src/config/index.ts +++ b/packages/server/src/config/index.ts @@ -169,4 +169,14 @@ module.exports = { * to application detarmines to upgrade. */ databaseBatch: 4, + + /** + * Exchange rate. + */ + exchangeRate: { + service: 'open-exchange-rate', + openExchangeRate: { + appId: process.env.OPEN_EXCHANGE_RATE_APP_ID, + } + } }; diff --git a/packages/server/src/lib/ExchangeRate/ExchangeRate.ts b/packages/server/src/lib/ExchangeRate/ExchangeRate.ts new file mode 100644 index 000000000..5bb84cff3 --- /dev/null +++ b/packages/server/src/lib/ExchangeRate/ExchangeRate.ts @@ -0,0 +1,45 @@ +import { OpenExchangeRate } from './OpenExchangeRate'; +import { ExchangeRateServiceType, IExchangeRateService } from './types'; + +export class ExchangeRate { + private exchangeRateService: IExchangeRateService; + private exchangeRateServiceType: ExchangeRateServiceType; + + /** + * Constructor method. + * @param {ExchangeRateServiceType} service + */ + constructor(service: ExchangeRateServiceType) { + this.exchangeRateServiceType = service; + this.initService(); + } + + /** + * Initialize the exchange rate service based on the service type. + */ + private initService() { + if ( + this.exchangeRateServiceType === ExchangeRateServiceType.OpenExchangeRate + ) { + this.setExchangeRateService(new OpenExchangeRate()); + } + } + + /** + * Sets the exchange rate service. + * @param {IExchangeRateService} service + */ + private setExchangeRateService(service: IExchangeRateService) { + this.exchangeRateService = service; + } + + /** + * Gets the latest exchange rate. + * @param {string} baseCurrency + * @param {string} toCurrency + * @returns {number} + */ + public latest(baseCurrency: string, toCurrency: string): Promise { + return this.exchangeRateService.latest(baseCurrency, toCurrency); + } +} diff --git a/packages/server/src/lib/ExchangeRate/OpenExchangeRate.ts b/packages/server/src/lib/ExchangeRate/OpenExchangeRate.ts new file mode 100644 index 000000000..5f0a9b15b --- /dev/null +++ b/packages/server/src/lib/ExchangeRate/OpenExchangeRate.ts @@ -0,0 +1,62 @@ +import Axios, { AxiosError } from 'axios'; +import { + EchangeRateErrors, + IExchangeRateService, + OPEN_EXCHANGE_RATE_LATEST_URL, +} from './types'; +import config from '@/config'; +import { ServiceError } from '@/exceptions'; + +export class OpenExchangeRate implements IExchangeRateService { + /** + * Gets the latest exchange rate. + * @param {string} baseCurrency + * @param {string} toCurrency + * @returns {Promise { + try { + const result = await Axios.get(OPEN_EXCHANGE_RATE_LATEST_URL, { + params: { + app_id: config.exchangeRate.openExchangeRate.appId, + base: baseCurrency, + symbols: toCurrency, + }, + }); + return result.data.rates[toCurrency] || (1 as number); + } catch (error) { + this.handleLatestErrors(error); + } + } + + /** + * Handles the latest errors. + * @param {any} error + */ + private handleLatestErrors(error: any) { + if (error.response.data?.message === 'missing_app_id') { + throw new ServiceError( + EchangeRateErrors.EX_RATE_SERVICE_API_KEY_REQUIRED, + 'Invalid App ID provided. Please sign up at https://openexchangerates.org/signup, or contact support@openexchangerates.org.' + ); + } else if (error.response.data?.message === 'invalid_app_id') { + throw new ServiceError( + EchangeRateErrors.EX_RATE_SERVICE_API_KEY_REQUIRED, + 'Invalid App ID provided. Please sign up at https://openexchangerates.org/signup, or contact support@openexchangerates.org.' + ); + } else if (error.response.data?.message === 'not_allowed') { + throw new ServiceError( + EchangeRateErrors.EX_RATE_SERVICE_NOT_ALLOWED, + 'Getting the exchange rate from the given base currency to the given currency is not allowed.' + ); + } else if (error.response.data?.message === 'invalid_base') { + throw new ServiceError( + EchangeRateErrors.EX_RATE_INVALID_BASE_CURRENCY, + 'The given base currency is invalid.' + ); + } + } +} diff --git a/packages/server/src/lib/ExchangeRate/types.ts b/packages/server/src/lib/ExchangeRate/types.ts new file mode 100644 index 000000000..8b40125cd --- /dev/null +++ b/packages/server/src/lib/ExchangeRate/types.ts @@ -0,0 +1,17 @@ +export interface IExchangeRateService { + latest(baseCurrency: string, toCurrency: string): Promise; +} + +export enum ExchangeRateServiceType { + OpenExchangeRate = 'OpenExchangeRate', +} + +export enum EchangeRateErrors { + EX_RATE_SERVICE_NOT_ALLOWED = 'EX_RATE_SERVICE_NOT_ALLOWED', + EX_RATE_LIMIT_EXCEEDED = 'EX_RATE_LIMIT_EXCEEDED', + EX_RATE_SERVICE_API_KEY_REQUIRED = 'EX_RATE_SERVICE_API_KEY_REQUIRED', + EX_RATE_INVALID_BASE_CURRENCY = 'EX_RATE_INVALID_BASE_CURRENCY', +} + +export const OPEN_EXCHANGE_RATE_LATEST_URL = + 'https://openexchangerates.org/api/latest.json'; diff --git a/packages/server/src/services/ExchangeRates/ExchangeRatesService.ts b/packages/server/src/services/ExchangeRates/ExchangeRatesService.ts index 9bc63fbfd..bb2437abc 100644 --- a/packages/server/src/services/ExchangeRates/ExchangeRatesService.ts +++ b/packages/server/src/services/ExchangeRates/ExchangeRatesService.ts @@ -1,193 +1,39 @@ -import moment from 'moment'; -import { difference } from 'lodash'; import { Service, Inject } from 'typedi'; -import { ServiceError } from '@/exceptions'; -import DynamicListingService from '@/services/DynamicListing/DynamicListService'; -import { - EventDispatcher, - EventDispatcherInterface, -} from 'decorators/eventDispatcher'; -import { - IExchangeRateDTO, - IExchangeRate, - IExchangeRatesService, - IExchangeRateEditDTO, - IExchangeRateFilter, -} from '@/interfaces'; -import TenancyService from '@/services/Tenancy/TenancyService'; +import { IExchangeRatesService } from '@/interfaces'; +import { ExchangeRate } from '@/lib/ExchangeRate/ExchangeRate'; +import { ExchangeRateServiceType } from '@/lib/ExchangeRate/types'; +interface ExchangeRateLatestDTO { + toCurrency: string; +} -const ERRORS = { - NOT_FOUND_EXCHANGE_RATES: 'NOT_FOUND_EXCHANGE_RATES', - EXCHANGE_RATE_PERIOD_EXISTS: 'EXCHANGE_RATE_PERIOD_EXISTS', - EXCHANGE_RATE_NOT_FOUND: 'EXCHANGE_RATE_NOT_FOUND', -}; +interface EchangeRateLatestPOJO { + baseCurrency: string; + toCurrency: string; + exchangeRate: number; +} @Service() -export default class ExchangeRatesService implements IExchangeRatesService { - @Inject('logger') - logger: any; - - @EventDispatcher() - eventDispatcher: EventDispatcherInterface; - - @Inject() - tenancy: TenancyService; - - @Inject() - dynamicListService: DynamicListingService; - +export class ExchangeRatesService { /** - * Creates a new exchange rate. + * Gets the latest exchange rate. * @param {number} tenantId - * @param {IExchangeRateDTO} exchangeRateDTO - * @returns {Promise} + * @param {number} exchangeRateLatestDTO + * @returns {EchangeRateLatestPOJO} */ - public async newExchangeRate( + public async latest( tenantId: number, - exchangeRateDTO: IExchangeRateDTO - ): Promise { - const { ExchangeRate } = this.tenancy.models(tenantId); - - this.logger.info('[exchange_rates] trying to insert new exchange rate.', { - tenantId, - exchangeRateDTO, - }); - await this.validateExchangeRatePeriodExistance(tenantId, exchangeRateDTO); - - const exchangeRate = await ExchangeRate.query().insertAndFetch({ - ...exchangeRateDTO, - date: moment(exchangeRateDTO.date).format('YYYY-MM-DD'), - }); - this.logger.info('[exchange_rates] inserted successfully.', { - tenantId, - exchangeRateDTO, - }); - return exchangeRate; - } - - /** - * Edits the exchange rate details. - * @param {number} tenantId - Tenant id. - * @param {number} exchangeRateId - Exchange rate id. - * @param {IExchangeRateEditDTO} editExRateDTO - Edit exchange rate DTO. - */ - public async editExchangeRate( - tenantId: number, - exchangeRateId: number, - editExRateDTO: IExchangeRateEditDTO - ): Promise { - const { ExchangeRate } = this.tenancy.models(tenantId); - - this.logger.info('[exchange_rates] trying to edit exchange rate.', { - tenantId, - exchangeRateId, - editExRateDTO, - }); - await this.validateExchangeRateExistance(tenantId, exchangeRateId); - - await ExchangeRate.query() - .where('id', exchangeRateId) - .update({ ...editExRateDTO }); - this.logger.info('[exchange_rates] exchange rate edited successfully.', { - tenantId, - exchangeRateId, - editExRateDTO, - }); - } - - /** - * Deletes the given exchange rate. - * @param {number} tenantId - Tenant id. - * @param {number} exchangeRateId - Exchange rate id. - */ - public async deleteExchangeRate( - tenantId: number, - exchangeRateId: number - ): Promise { - const { ExchangeRate } = this.tenancy.models(tenantId); - await this.validateExchangeRateExistance(tenantId, exchangeRateId); - - await ExchangeRate.query().findById(exchangeRateId).delete(); - } - - /** - * Listing exchange rates details. - * @param {number} tenantId - Tenant id. - * @param {IExchangeRateFilter} exchangeRateFilter - Exchange rates list filter. - */ - public async listExchangeRates( - tenantId: number, - exchangeRateFilter: IExchangeRateFilter - ): Promise { - const { ExchangeRate } = this.tenancy.models(tenantId); - const dynamicFilter = await this.dynamicListService.dynamicList( - tenantId, - ExchangeRate, - exchangeRateFilter - ); - // Retrieve exchange rates by the given query. - const exchangeRates = await ExchangeRate.query() - .onBuild((query) => { - dynamicFilter.buildQuery()(query); - }) - .pagination(exchangeRateFilter.page - 1, exchangeRateFilter.pageSize); - - return exchangeRates; - } - - /** - * Validates period of the exchange rate existance. - * @param {number} tenantId - Tenant id. - * @param {IExchangeRateDTO} exchangeRateDTO - Exchange rate DTO. - * @return {Promise} - */ - private async validateExchangeRatePeriodExistance( - tenantId: number, - exchangeRateDTO: IExchangeRateDTO - ): Promise { - const { ExchangeRate } = this.tenancy.models(tenantId); - - this.logger.info('[exchange_rates] trying to validate period existance.', { - tenantId, - }); - const foundExchangeRate = await ExchangeRate.query() - .where('currency_code', exchangeRateDTO.currencyCode) - .where('date', exchangeRateDTO.date); - - if (foundExchangeRate.length > 0) { - this.logger.info('[exchange_rates] given exchange rate period exists.', { - tenantId, - }); - throw new ServiceError(ERRORS.EXCHANGE_RATE_PERIOD_EXISTS); - } - } - - /** - * Validate the given echange rate id existance. - * @param {number} tenantId - Tenant id. - * @param {number} exchangeRateId - Exchange rate id. - * @returns {Promise} - */ - private async validateExchangeRateExistance( - tenantId: number, - exchangeRateId: number - ) { - const { ExchangeRate } = this.tenancy.models(tenantId); - - this.logger.info( - '[exchange_rates] trying to validate exchange rate id existance.', - { tenantId, exchangeRateId } - ); - const foundExchangeRate = await ExchangeRate.query().findById( - exchangeRateId + exchangeRateLatestDTO: ExchangeRateLatestDTO + ): Promise { + const exchange = new ExchangeRate(ExchangeRateServiceType.OpenExchangeRate); + const exchangeRate = await exchange.latest( + 'USD', + exchangeRateLatestDTO.toCurrency ); - if (!foundExchangeRate) { - this.logger.info('[exchange_rates] exchange rate not found.', { - tenantId, - exchangeRateId, - }); - throw new ServiceError(ERRORS.EXCHANGE_RATE_NOT_FOUND); - } + return { + baseCurrency: 'USD', + toCurrency: exchangeRateLatestDTO.toCurrency, + exchangeRate, + }; } } From 1b20d1b073319e2814c56a96a15e92786771aaaf Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Sun, 28 Jan 2024 18:47:49 +0200 Subject: [PATCH 05/39] feat(server): add application layer to exchange rate service --- .../src/api/controllers/ExchangeRates.ts | 8 ++-- .../server/src/interfaces/ExchangeRate.ts | 43 ++++--------------- .../src/lib/ExchangeRate/OpenExchangeRate.ts | 19 ++++++++ .../ExchangeRates/ExchangeRateApplication.ts | 21 +++++++++ .../ExchangeRates/ExchangeRatesService.ts | 13 +----- 5 files changed, 54 insertions(+), 50 deletions(-) create mode 100644 packages/server/src/services/ExchangeRates/ExchangeRateApplication.ts diff --git a/packages/server/src/api/controllers/ExchangeRates.ts b/packages/server/src/api/controllers/ExchangeRates.ts index 4ea59ad1b..5a935a173 100644 --- a/packages/server/src/api/controllers/ExchangeRates.ts +++ b/packages/server/src/api/controllers/ExchangeRates.ts @@ -1,16 +1,16 @@ import { Service, Inject } from 'typedi'; import { Router, Request, Response, NextFunction } from 'express'; -import { check, param, query } from 'express-validator'; +import { query } from 'express-validator'; import asyncMiddleware from '@/api/middleware/asyncMiddleware'; import BaseController from './BaseController'; import { ServiceError } from '@/exceptions'; -import { ExchangeRatesService } from '@/services/ExchangeRates/ExchangeRatesService'; import { EchangeRateErrors } from '@/lib/ExchangeRate/types'; +import { ExchangeRateApplication } from '@/services/ExchangeRates/ExchangeRateApplication'; @Service() export default class ExchangeRatesController extends BaseController { @Inject() - private exchangeRatesService: ExchangeRatesService; + private exchangeRatesApp: ExchangeRateApplication; /** * Constructor method. @@ -43,7 +43,7 @@ export default class ExchangeRatesController extends BaseController { const exchangeRateQuery = this.matchedQueryData(req); try { - const exchangeRate = await this.exchangeRatesService.latest( + const exchangeRate = await this.exchangeRatesApp.latest( tenantId, exchangeRateQuery ); diff --git a/packages/server/src/interfaces/ExchangeRate.ts b/packages/server/src/interfaces/ExchangeRate.ts index fc3bd33e4..757ca5f3d 100644 --- a/packages/server/src/interfaces/ExchangeRate.ts +++ b/packages/server/src/interfaces/ExchangeRate.ts @@ -1,36 +1,9 @@ -import { IFilterRole } from './DynamicFilter'; +export interface ExchangeRateLatestDTO { + toCurrency: string; +} -export interface IExchangeRate { - id: number, - currencyCode: string, - exchangeRate: number, - date: Date, - createdAt: Date, - updatedAt: Date, -}; - -export interface IExchangeRateDTO { - currencyCode: string, - exchangeRate: number, - date: Date, -}; - -export interface IExchangeRateEditDTO { - exchangeRate: number, -}; - -export interface IExchangeRateFilter { - page: number, - pageSize: number, - filterRoles?: IFilterRole[]; - columnSortBy: string; - sortOrder: string; -}; - -export interface IExchangeRatesService { - newExchangeRate(tenantId: number, exchangeRateDTO: IExchangeRateDTO): Promise; - editExchangeRate(tenantId: number, exchangeRateId: number, editExRateDTO: IExchangeRateEditDTO): Promise; - - deleteExchangeRate(tenantId: number, exchangeRateId: number): Promise; - listExchangeRates(tenantId: number, exchangeRateFilter: IExchangeRateFilter): Promise; -}; \ No newline at end of file +export interface EchangeRateLatestPOJO { + baseCurrency: string; + toCurrency: string; + exchangeRate: number; +} diff --git a/packages/server/src/lib/ExchangeRate/OpenExchangeRate.ts b/packages/server/src/lib/ExchangeRate/OpenExchangeRate.ts index 5f0a9b15b..221c5c5c5 100644 --- a/packages/server/src/lib/ExchangeRate/OpenExchangeRate.ts +++ b/packages/server/src/lib/ExchangeRate/OpenExchangeRate.ts @@ -18,6 +18,9 @@ export class OpenExchangeRate implements IExchangeRateService { baseCurrency: string, toCurrency: string ): Promise { + // Vaclidates the Open Exchange Rate api id early. + this.validateApiIdExistance(); + try { const result = await Axios.get(OPEN_EXCHANGE_RATE_LATEST_URL, { params: { @@ -32,9 +35,25 @@ export class OpenExchangeRate implements IExchangeRateService { } } + /** + * Validates the Open Exchange Rate api id. + * @throws {ServiceError} + */ + private validateApiIdExistance() { + const apiId = config.exchangeRate.openExchangeRate.appId; + + if (!apiId) { + throw new ServiceError( + EchangeRateErrors.EX_RATE_SERVICE_API_KEY_REQUIRED, + 'Invalid App ID provided. Please sign up at https://openexchangerates.org/signup, or contact support@openexchangerates.org.' + ); + } + } + /** * Handles the latest errors. * @param {any} error + * @throws {ServiceError} */ private handleLatestErrors(error: any) { if (error.response.data?.message === 'missing_app_id') { diff --git a/packages/server/src/services/ExchangeRates/ExchangeRateApplication.ts b/packages/server/src/services/ExchangeRates/ExchangeRateApplication.ts new file mode 100644 index 000000000..9e51c5d72 --- /dev/null +++ b/packages/server/src/services/ExchangeRates/ExchangeRateApplication.ts @@ -0,0 +1,21 @@ +import { Inject } from 'typedi'; +import { ExchangeRatesService } from './ExchangeRatesService'; +import { EchangeRateLatestPOJO, ExchangeRateLatestDTO } from '@/interfaces'; + +export class ExchangeRateApplication { + @Inject() + private exchangeRateService: ExchangeRatesService; + + /** + * Gets the latest exchange rate. + * @param {number} tenantId + * @param {ExchangeRateLatestDTO} exchangeRateLatestDTO + * @returns {Promise} + */ + public latest( + tenantId: number, + exchangeRateLatestDTO: ExchangeRateLatestDTO + ): Promise { + return this.exchangeRateService.latest(tenantId, exchangeRateLatestDTO); + } +} diff --git a/packages/server/src/services/ExchangeRates/ExchangeRatesService.ts b/packages/server/src/services/ExchangeRates/ExchangeRatesService.ts index bb2437abc..ae322cdff 100644 --- a/packages/server/src/services/ExchangeRates/ExchangeRatesService.ts +++ b/packages/server/src/services/ExchangeRates/ExchangeRatesService.ts @@ -1,16 +1,7 @@ -import { Service, Inject } from 'typedi'; -import { IExchangeRatesService } from '@/interfaces'; +import { Service } from 'typedi'; import { ExchangeRate } from '@/lib/ExchangeRate/ExchangeRate'; import { ExchangeRateServiceType } from '@/lib/ExchangeRate/types'; -interface ExchangeRateLatestDTO { - toCurrency: string; -} - -interface EchangeRateLatestPOJO { - baseCurrency: string; - toCurrency: string; - exchangeRate: number; -} +import { EchangeRateLatestPOJO, ExchangeRateLatestDTO } from '@/interfaces'; @Service() export class ExchangeRatesService { From 1740226294f2289666565ecaf6796094bef7a3fc Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Sun, 28 Jan 2024 18:48:35 +0200 Subject: [PATCH 06/39] feat(webapp): hook up latest exchange rate api --- .../Entries/AutoExchangeProvider.tsx | 9 ++--- .../webapp/src/hooks/query/exchangeRates.tsx | 39 ++++++++----------- 2 files changed, 20 insertions(+), 28 deletions(-) diff --git a/packages/webapp/src/containers/Entries/AutoExchangeProvider.tsx b/packages/webapp/src/containers/Entries/AutoExchangeProvider.tsx index 6554b85a1..dcaf86283 100644 --- a/packages/webapp/src/containers/Entries/AutoExchangeProvider.tsx +++ b/packages/webapp/src/containers/Entries/AutoExchangeProvider.tsx @@ -1,6 +1,5 @@ -import { useExchangeRate } from '@/hooks/query'; -import { useCurrentOrganization } from '@/hooks/state'; import React from 'react'; +import { useLatestExchangeRate } from '@/hooks/query'; interface AutoExchangeRateProviderProps { children: React.ReactNode; @@ -18,15 +17,15 @@ const AutoExchangeRateContext = React.createContext( function AutoExchangeRateProvider({ children }: AutoExchangeRateProviderProps) { const [autoExRateCurrency, setAutoExRateCurrency] = React.useState(''); - const currentOrganization = useCurrentOrganization(); // Retrieves the exchange rate. const { data: autoExchangeRate, isLoading: isAutoExchangeRateLoading } = - useExchangeRate(autoExRateCurrency, currentOrganization.base_currency, { - enabled: Boolean(currentOrganization.base_currency && autoExRateCurrency), + useLatestExchangeRate(autoExRateCurrency, { + enabled: Boolean(autoExRateCurrency), refetchOnWindowFocus: false, staleTime: 0, cacheTime: 0, + retry: 0, }); const value = { diff --git a/packages/webapp/src/hooks/query/exchangeRates.tsx b/packages/webapp/src/hooks/query/exchangeRates.tsx index f56958040..a0f58f7ac 100644 --- a/packages/webapp/src/hooks/query/exchangeRates.tsx +++ b/packages/webapp/src/hooks/query/exchangeRates.tsx @@ -1,34 +1,27 @@ // @ts-nocheck import { useQuery } from 'react-query'; import QUERY_TYPES from './types'; +import useApiRequest from '../useRequest'; -function getRandomItemFromArray(arr) { - const randomIndex = Math.floor(Math.random() * arr.length); - return arr[randomIndex]; -} -function delay(t, val) { - return new Promise((resolve) => setTimeout(resolve, t, val)); -} /** - * Retrieves tax rates. + * Retrieves latest exchange rate. * @param {number} customerId - Customer id. */ -export function useExchangeRate( - fromCurrency: string, - toCurrency: string, - props, -) { - return useQuery( - [QUERY_TYPES.EXCHANGE_RATE, fromCurrency, toCurrency], - async () => { - await delay(100); +export function useLatestExchangeRate(toCurrency: string, props) { + const apiRequest = useApiRequest(); - return { - from_currency: fromCurrency, - to_currency: toCurrency, - exchange_rate: 1.00, - }; - }, + return useQuery( + [QUERY_TYPES.EXCHANGE_RATE, toCurrency], + () => + apiRequest + .http({ + url: `/api/exchange_rates/latest`, + method: 'get', + params: { + to_currency: toCurrency, + }, + }) + .then((res) => res.data), props, ); } From 74a07847a4ce8fb1983a18beaadb320918f77dff Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Sun, 28 Jan 2024 20:10:23 +0200 Subject: [PATCH 07/39] feat(server): ability to assign the base currency as api query when getting latest ex. rate --- .../server/src/api/controllers/ExchangeRates.ts | 9 +++++++-- packages/server/src/interfaces/ExchangeRate.ts | 1 + .../ExchangeRates/ExchangeRatesService.ts | 17 ++++++++++++----- .../server/src/system/models/TenantMetadata.ts | 2 ++ .../containers/Entries/AutoExchangeProvider.tsx | 17 ++++++++++------- .../webapp/src/hooks/query/exchangeRates.tsx | 13 +++++++++++-- 6 files changed, 43 insertions(+), 16 deletions(-) diff --git a/packages/server/src/api/controllers/ExchangeRates.ts b/packages/server/src/api/controllers/ExchangeRates.ts index 5a935a173..63c476bf9 100644 --- a/packages/server/src/api/controllers/ExchangeRates.ts +++ b/packages/server/src/api/controllers/ExchangeRates.ts @@ -1,6 +1,6 @@ import { Service, Inject } from 'typedi'; import { Router, Request, Response, NextFunction } from 'express'; -import { query } from 'express-validator'; +import { query, oneOf } from 'express-validator'; import asyncMiddleware from '@/api/middleware/asyncMiddleware'; import BaseController from './BaseController'; import { ServiceError } from '@/exceptions'; @@ -20,7 +20,12 @@ export default class ExchangeRatesController extends BaseController { router.get( '/latest', - [query('to_currency').exists().isString()], + [ + oneOf([ + query('to_currency').exists().isString().isISO4217(), + query('from_currency').exists().isString().isISO4217(), + ]), + ], this.validationResult, asyncMiddleware(this.latestExchangeRate.bind(this)), this.handleServiceError diff --git a/packages/server/src/interfaces/ExchangeRate.ts b/packages/server/src/interfaces/ExchangeRate.ts index 757ca5f3d..45080fc0f 100644 --- a/packages/server/src/interfaces/ExchangeRate.ts +++ b/packages/server/src/interfaces/ExchangeRate.ts @@ -1,5 +1,6 @@ export interface ExchangeRateLatestDTO { toCurrency: string; + fromCurrency: string; } export interface EchangeRateLatestPOJO { diff --git a/packages/server/src/services/ExchangeRates/ExchangeRatesService.ts b/packages/server/src/services/ExchangeRates/ExchangeRatesService.ts index ae322cdff..4cde544ad 100644 --- a/packages/server/src/services/ExchangeRates/ExchangeRatesService.ts +++ b/packages/server/src/services/ExchangeRates/ExchangeRatesService.ts @@ -2,6 +2,7 @@ import { Service } from 'typedi'; import { ExchangeRate } from '@/lib/ExchangeRate/ExchangeRate'; import { ExchangeRateServiceType } from '@/lib/ExchangeRate/types'; import { EchangeRateLatestPOJO, ExchangeRateLatestDTO } from '@/interfaces'; +import { TenantMetadata } from '@/system/models'; @Service() export class ExchangeRatesService { @@ -15,14 +16,20 @@ export class ExchangeRatesService { tenantId: number, exchangeRateLatestDTO: ExchangeRateLatestDTO ): Promise { + const organization = await TenantMetadata.query().findOne({ tenantId }); + + // Assign the organization base currency as a default currency + // if no currency is provided + const fromCurrency = + exchangeRateLatestDTO.fromCurrency || organization.baseCurrency; + const toCurrency = + exchangeRateLatestDTO.toCurrency || organization.baseCurrency; + const exchange = new ExchangeRate(ExchangeRateServiceType.OpenExchangeRate); - const exchangeRate = await exchange.latest( - 'USD', - exchangeRateLatestDTO.toCurrency - ); + const exchangeRate = await exchange.latest(fromCurrency, toCurrency); return { - baseCurrency: 'USD', + baseCurrency: fromCurrency, toCurrency: exchangeRateLatestDTO.toCurrency, exchangeRate, }; diff --git a/packages/server/src/system/models/TenantMetadata.ts b/packages/server/src/system/models/TenantMetadata.ts index 4664cfd6d..7040a6a68 100644 --- a/packages/server/src/system/models/TenantMetadata.ts +++ b/packages/server/src/system/models/TenantMetadata.ts @@ -1,6 +1,8 @@ import BaseModel from 'models/Model'; export default class TenantMetadata extends BaseModel { + baseCurrency: string; + /** * Table name. */ diff --git a/packages/webapp/src/containers/Entries/AutoExchangeProvider.tsx b/packages/webapp/src/containers/Entries/AutoExchangeProvider.tsx index dcaf86283..c4b5ea1f9 100644 --- a/packages/webapp/src/containers/Entries/AutoExchangeProvider.tsx +++ b/packages/webapp/src/containers/Entries/AutoExchangeProvider.tsx @@ -20,13 +20,16 @@ function AutoExchangeRateProvider({ children }: AutoExchangeRateProviderProps) { // Retrieves the exchange rate. const { data: autoExchangeRate, isLoading: isAutoExchangeRateLoading } = - useLatestExchangeRate(autoExRateCurrency, { - enabled: Boolean(autoExRateCurrency), - refetchOnWindowFocus: false, - staleTime: 0, - cacheTime: 0, - retry: 0, - }); + useLatestExchangeRate( + { fromCurrency: autoExRateCurrency }, + { + enabled: Boolean(autoExRateCurrency), + refetchOnWindowFocus: false, + staleTime: 0, + cacheTime: 0, + retry: 0, + }, + ); const value = { autoExRateCurrency, diff --git a/packages/webapp/src/hooks/query/exchangeRates.tsx b/packages/webapp/src/hooks/query/exchangeRates.tsx index a0f58f7ac..36700276b 100644 --- a/packages/webapp/src/hooks/query/exchangeRates.tsx +++ b/packages/webapp/src/hooks/query/exchangeRates.tsx @@ -3,15 +3,23 @@ import { useQuery } from 'react-query'; import QUERY_TYPES from './types'; import useApiRequest from '../useRequest'; +interface LatestExchangeRateQuery { + fromCurrency?: string; + toCurrency?: string; +} + /** * Retrieves latest exchange rate. * @param {number} customerId - Customer id. */ -export function useLatestExchangeRate(toCurrency: string, props) { +export function useLatestExchangeRate( + { toCurrency, fromCurrency }: LatestExchangeRateQuery, + props, +) { const apiRequest = useApiRequest(); return useQuery( - [QUERY_TYPES.EXCHANGE_RATE, toCurrency], + [QUERY_TYPES.EXCHANGE_RATE, toCurrency, fromCurrency], () => apiRequest .http({ @@ -19,6 +27,7 @@ export function useLatestExchangeRate(toCurrency: string, props) { method: 'get', params: { to_currency: toCurrency, + from_currency: fromCurrency, }, }) .then((res) => res.data), From 59c5c8979de223fe41526eee95d28a3ce3067c5a Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Mon, 29 Jan 2024 18:39:28 +0200 Subject: [PATCH 08/39] feat(webapp): add default exchange rate value to 1 in cause the ex. rate service returned error or failed. --- .../withExRateItemEntriesPriceRecalc.tsx | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/packages/webapp/src/containers/Entries/withExRateItemEntriesPriceRecalc.tsx b/packages/webapp/src/containers/Entries/withExRateItemEntriesPriceRecalc.tsx index 490ef20b5..a1fd7af39 100644 --- a/packages/webapp/src/containers/Entries/withExRateItemEntriesPriceRecalc.tsx +++ b/packages/webapp/src/containers/Entries/withExRateItemEntriesPriceRecalc.tsx @@ -98,24 +98,30 @@ interface UseSyncExRateToFormProps { */ export const useSyncExRateToForm = ({ onSynced }: UseSyncExRateToFormProps) => { const { setFieldValue, values } = useFormikContext(); - const { autoExRateCurrency, autoExchangeRate } = useAutoExRateContext(); + const { autoExRateCurrency, autoExchangeRate, isAutoExchangeRateLoading } = + useAutoExRateContext(); const updateEntriesOnExChange = useUpdateEntriesOnExchangeRateChange(); // Sync the fetched real-time exchanage rate to the form. useEffect(() => { - if (autoExchangeRate?.exchange_rate && autoExRateCurrency) { - setFieldValue('exchange_rate', autoExchangeRate?.exchange_rate + ''); + if (!isAutoExchangeRateLoading && autoExRateCurrency) { + // Sets a default ex. rate to 1 in case the exchange rate service wasn't configured. + // or returned an error from the server-side. + const exchangeRate = autoExchangeRate?.exchange_rate || 1; + + setFieldValue('exchange_rate', exchangeRate + ''); setFieldValue( 'entries', - updateEntriesOnExChange( - values.exchange_rate, - autoExchangeRate?.exchange_rate, - ), + updateEntriesOnExChange(values.exchange_rate, exchangeRate), ); onSynced?.(); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [autoExchangeRate?.exchange_rate, autoExRateCurrency]); + }, [ + autoExchangeRate?.exchange_rate, + autoExRateCurrency, + isAutoExchangeRateLoading, + ]); return null; }; From f93c8b46dcf222268057424869160943bc8cfb1a Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Mon, 29 Jan 2024 18:42:54 +0200 Subject: [PATCH 09/39] chore: change env name in .env.example --- .env.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env.example b/.env.example index 7af1c6061..f32107eb4 100644 --- a/.env.example +++ b/.env.example @@ -63,4 +63,4 @@ GOTENBERG_DOCS_URL=http://server:3000/public/ EXCHANGE_RATE_SERVICE=open-exchange-rate # Open Exchange Rate -OPEN_EXCHANGE_RATE_APP_I= \ No newline at end of file +OPEN_EXCHANGE_RATE_APP_ID= \ No newline at end of file From d01eacf8d962f25899139bfd3b3ab0381f7e9841 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Mon, 29 Jan 2024 19:18:59 +0200 Subject: [PATCH 10/39] feat(server): add `formattedAmount` to purchase invoice to POJO --- .../src/services/Purchases/Bills/PurchaseInvoiceTransformer.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/server/src/services/Purchases/Bills/PurchaseInvoiceTransformer.ts b/packages/server/src/services/Purchases/Bills/PurchaseInvoiceTransformer.ts index 4ec163d8b..4cb92adf9 100644 --- a/packages/server/src/services/Purchases/Bills/PurchaseInvoiceTransformer.ts +++ b/packages/server/src/services/Purchases/Bills/PurchaseInvoiceTransformer.ts @@ -13,6 +13,7 @@ export class PurchaseInvoiceTransformer extends Transformer { return [ 'formattedBillDate', 'formattedDueDate', + 'formattedAmount', 'formattedPaymentAmount', 'formattedBalance', 'formattedDueAmount', From a52f3a933fe6073e5fee16d41971e03a0feeebf9 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Mon, 29 Jan 2024 19:19:46 +0200 Subject: [PATCH 11/39] fix(webapp): inconsistency in currency of universal search items --- .../CreditNotes/VendorCreditIUniversalSearchBind.tsx | 4 ++-- .../Purchases/PaymentMades/PaymentMadeUniversalSearch.tsx | 1 + .../Sales/CreditNotes/CreditNoteUniversalSearch.tsx | 2 +- .../src/containers/Sales/Invoices/InvoiceUniversalSearch.tsx | 2 +- .../src/containers/Sales/Receipts/ReceiptUniversalSearch.tsx | 2 +- .../webapp/src/containers/Vendors/VendorsUniversalSearch.tsx | 2 +- 6 files changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/webapp/src/containers/Purchases/CreditNotes/VendorCreditIUniversalSearchBind.tsx b/packages/webapp/src/containers/Purchases/CreditNotes/VendorCreditIUniversalSearchBind.tsx index fa82987e3..a360ab617 100644 --- a/packages/webapp/src/containers/Purchases/CreditNotes/VendorCreditIUniversalSearchBind.tsx +++ b/packages/webapp/src/containers/Purchases/CreditNotes/VendorCreditIUniversalSearchBind.tsx @@ -22,7 +22,7 @@ function VendorCreditUniversalSearchSelectComponent({ openDrawer, }) { if (resourceType === RESOURCES_TYPES.VENDOR_CREDIT) { - openDrawer(DRAWERS.VENDOR_CREDIT_DETAIL_DRAWER, { + openDrawer(DRAWERS.VENDOR_CREDIT_DETAILS, { vendorCreditId: resourceId, }); onAction && onAction(); @@ -83,7 +83,7 @@ export function VendorCreditUniversalSearchItem( } label={ <> -
${item.reference.amount}
+
{item.reference.formatted_amount}
} diff --git a/packages/webapp/src/containers/Purchases/PaymentMades/PaymentMadeUniversalSearch.tsx b/packages/webapp/src/containers/Purchases/PaymentMades/PaymentMadeUniversalSearch.tsx index f1cdc60ce..890401038 100644 --- a/packages/webapp/src/containers/Purchases/PaymentMades/PaymentMadeUniversalSearch.tsx +++ b/packages/webapp/src/containers/Purchases/PaymentMades/PaymentMadeUniversalSearch.tsx @@ -7,6 +7,7 @@ import { Icon } from '@/components'; import { RESOURCES_TYPES } from '@/constants/resourcesTypes'; import { highlightText } from '@/utils'; import { AbilitySubject, PaymentMadeAction } from '@/constants/abilityOption'; +import { DRAWERS } from '@/constants/drawers'; import withDrawerActions from '@/containers/Drawer/withDrawerActions'; /** diff --git a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteUniversalSearch.tsx b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteUniversalSearch.tsx index 2c495f802..8407a4f39 100644 --- a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteUniversalSearch.tsx +++ b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteUniversalSearch.tsx @@ -82,7 +82,7 @@ export function CreditNoteUniversalSearchItem( } label={ <> -
${item.reference.amount}
+
{item.reference.formatted_amount}
} diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceUniversalSearch.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceUniversalSearch.tsx index 1656b9bc7..a86141612 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceUniversalSearch.tsx +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceUniversalSearch.tsx @@ -89,7 +89,7 @@ export function InvoiceUniversalSearchItem( } label={ <> -
${item.reference.total_formatted}
+
{item.reference.total_formatted}
} diff --git a/packages/webapp/src/containers/Sales/Receipts/ReceiptUniversalSearch.tsx b/packages/webapp/src/containers/Sales/Receipts/ReceiptUniversalSearch.tsx index 2eb9405ac..abaea4004 100644 --- a/packages/webapp/src/containers/Sales/Receipts/ReceiptUniversalSearch.tsx +++ b/packages/webapp/src/containers/Sales/Receipts/ReceiptUniversalSearch.tsx @@ -75,7 +75,7 @@ export function ReceiptUniversalSearchItem( } label={ <> -
${item.reference.formatted_amount}
+
{item.reference.formatted_amount}
} diff --git a/packages/webapp/src/containers/Vendors/VendorsUniversalSearch.tsx b/packages/webapp/src/containers/Vendors/VendorsUniversalSearch.tsx index 84ccdfcb7..8134cc25c 100644 --- a/packages/webapp/src/containers/Vendors/VendorsUniversalSearch.tsx +++ b/packages/webapp/src/containers/Vendors/VendorsUniversalSearch.tsx @@ -34,7 +34,7 @@ const VendorUniversalSearchSelectAction = withDrawerActions( const vendorToSearch = (contact) => ({ id: contact.id, text: contact.display_name, - label: contact.balance > 0 ? contact.formatted_balance + '' : '', + label: contact.formatted_balance, reference: contact, }); From ba387e81f761f87b7e1e38ff572e4d78cf7dcea3 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Mon, 29 Jan 2024 23:25:37 +0200 Subject: [PATCH 12/39] hotfix: editing sales and expense transactions don't reflect GL entries --- .../server/src/api/controllers/Purchases/VendorCredit.ts | 9 ++++----- .../server/src/services/CreditNotes/EditCreditNote.ts | 2 +- .../server/src/services/Expenses/CRUD/EditExpense.ts | 2 +- .../src/services/Expenses/ExpenseGLEntriesStorage.ts | 2 +- .../src/services/Expenses/ExpenseGLEntriesSubscriber.ts | 6 +++--- .../services/Purchases/VendorCredits/EditVendorCredit.ts | 6 +++++- .../src/services/Sales/Receipts/SaleReceiptGLEntries.ts | 2 +- packages/webapp/src/hooks/query/bills.tsx | 3 +++ packages/webapp/src/hooks/query/creditNote.tsx | 3 +++ packages/webapp/src/hooks/query/invoices.tsx | 3 +++ packages/webapp/src/hooks/query/paymentReceives.tsx | 3 +++ packages/webapp/src/hooks/query/receipts.tsx | 3 +++ packages/webapp/src/hooks/query/vendorCredit.tsx | 3 +++ 13 files changed, 34 insertions(+), 13 deletions(-) diff --git a/packages/server/src/api/controllers/Purchases/VendorCredit.ts b/packages/server/src/api/controllers/Purchases/VendorCredit.ts index 3d2832308..79ae6741b 100644 --- a/packages/server/src/api/controllers/Purchases/VendorCredit.ts +++ b/packages/server/src/api/controllers/Purchases/VendorCredit.ts @@ -320,20 +320,19 @@ export default class VendorCreditController extends BaseController { res: Response, next: NextFunction ) => { - const { id: billId } = req.params; + const { id: vendorCreditId } = req.params; const { tenantId, user } = req; const vendorCreditEditDTO: IVendorCreditEditDTO = this.matchedBodyData(req); try { await this.editVendorCreditService.editVendorCredit( tenantId, - billId, - vendorCreditEditDTO, - user + vendorCreditId, + vendorCreditEditDTO ); return res.status(200).send({ - id: billId, + id: vendorCreditId, message: 'The vendor credit has been edited successfully.', }); } catch (error) { diff --git a/packages/server/src/services/CreditNotes/EditCreditNote.ts b/packages/server/src/services/CreditNotes/EditCreditNote.ts index 074115c04..0e045227d 100644 --- a/packages/server/src/services/CreditNotes/EditCreditNote.ts +++ b/packages/server/src/services/CreditNotes/EditCreditNote.ts @@ -80,7 +80,7 @@ export default class EditCreditNote extends BaseCreditNotes { } as ICreditNoteEditingPayload); // Saves the credit note graph to the storage. - const creditNote = await CreditNote.query(trx).upsertGraph({ + const creditNote = await CreditNote.query(trx).upsertGraphAndFetch({ id: creditNoteId, ...creditNoteModel, }); diff --git a/packages/server/src/services/Expenses/CRUD/EditExpense.ts b/packages/server/src/services/Expenses/CRUD/EditExpense.ts index 93b0acc62..e3aeb06ce 100644 --- a/packages/server/src/services/Expenses/CRUD/EditExpense.ts +++ b/packages/server/src/services/Expenses/CRUD/EditExpense.ts @@ -136,7 +136,7 @@ export class EditExpense { } as IExpenseEventEditingPayload); // Upsert the expense object with expense entries. - const expense: IExpense = await Expense.query(trx).upsertGraph({ + const expense: IExpense = await Expense.query(trx).upsertGraphAndFetch({ id: expenseId, ...expenseObj, }); diff --git a/packages/server/src/services/Expenses/ExpenseGLEntriesStorage.ts b/packages/server/src/services/Expenses/ExpenseGLEntriesStorage.ts index 0821c3bd4..76b450c39 100644 --- a/packages/server/src/services/Expenses/ExpenseGLEntriesStorage.ts +++ b/packages/server/src/services/Expenses/ExpenseGLEntriesStorage.ts @@ -1,7 +1,7 @@ import { Knex } from 'knex'; +import { Service, Inject } from 'typedi'; import LedgerStorageService from '@/services/Accounting/LedgerStorageService'; import HasTenancyService from '@/services/Tenancy/TenancyService'; -import { Service, Inject } from 'typedi'; import { ExpenseGLEntries } from './ExpenseGLEntries'; @Service() diff --git a/packages/server/src/services/Expenses/ExpenseGLEntriesSubscriber.ts b/packages/server/src/services/Expenses/ExpenseGLEntriesSubscriber.ts index 1a177876e..4c69b7f04 100644 --- a/packages/server/src/services/Expenses/ExpenseGLEntriesSubscriber.ts +++ b/packages/server/src/services/Expenses/ExpenseGLEntriesSubscriber.ts @@ -70,10 +70,10 @@ export class ExpensesWriteGLSubscriber { authorizedUser, trx, }: IExpenseEventEditPayload) => { - // In case expense published, write journal entries. - if (expense.publishedAt) return; + // Cannot continue if the expense is not published. + if (!expense.publishedAt) return; - await this.expenseGLEntries.writeExpenseGLEntries( + await this.expenseGLEntries.rewriteExpenseGLEntries( tenantId, expense.id, trx diff --git a/packages/server/src/services/Purchases/VendorCredits/EditVendorCredit.ts b/packages/server/src/services/Purchases/VendorCredits/EditVendorCredit.ts index d76dd3690..a0ff0f421 100644 --- a/packages/server/src/services/Purchases/VendorCredits/EditVendorCredit.ts +++ b/packages/server/src/services/Purchases/VendorCredits/EditVendorCredit.ts @@ -9,6 +9,7 @@ import UnitOfWork from '@/services/UnitOfWork'; import { EventPublisher } from '@/lib/EventPublisher/EventPublisher'; import ItemsEntriesService from '@/services/Items/ItemsEntriesService'; import events from '@/subscribers/events'; +import HasTenancyService from '@/services/Tenancy/TenancyService'; @Service() export default class EditVendorCredit extends BaseVendorCredit { @@ -21,6 +22,9 @@ export default class EditVendorCredit extends BaseVendorCredit { @Inject() private itemsEntriesService: ItemsEntriesService; + @Inject() + private tenancy: HasTenancyService; + /** * Deletes the given vendor credit. * @param {number} tenantId - Tenant id. @@ -31,7 +35,7 @@ export default class EditVendorCredit extends BaseVendorCredit { vendorCreditId: number, vendorCreditDTO: IVendorCreditEditDTO ) => { - const { VendorCredit } = this.tenancy.models(tenantId); + const { VendorCredit, Contact } = this.tenancy.models(tenantId); // Retrieve the vendor credit or throw not found service error. const oldVendorCredit = await this.getVendorCreditOrThrowError( diff --git a/packages/server/src/services/Sales/Receipts/SaleReceiptGLEntries.ts b/packages/server/src/services/Sales/Receipts/SaleReceiptGLEntries.ts index df440958f..d354141e9 100644 --- a/packages/server/src/services/Sales/Receipts/SaleReceiptGLEntries.ts +++ b/packages/server/src/services/Sales/Receipts/SaleReceiptGLEntries.ts @@ -32,7 +32,7 @@ export class SaleReceiptGLEntries { ): Promise => { const { SaleReceipt } = this.tenancy.models(tenantId); - const saleReceipt = await SaleReceipt.query() + const saleReceipt = await SaleReceipt.query(trx) .findById(saleReceiptId) .withGraphFetched('entries.item'); diff --git a/packages/webapp/src/hooks/query/bills.tsx b/packages/webapp/src/hooks/query/bills.tsx index 828621d89..1ec3ef0e1 100644 --- a/packages/webapp/src/hooks/query/bills.tsx +++ b/packages/webapp/src/hooks/query/bills.tsx @@ -32,6 +32,9 @@ const commonInvalidateQueries = (queryClient) => { // Invalidate financial reports. queryClient.invalidateQueries(t.FINANCIAL_REPORT); + // Invalidate the transactions by reference. + queryClient.invalidateQueries(t.TRANSACTIONS_BY_REFERENCE); + // Invalidate items associated bills transactions. queryClient.invalidateQueries(t.ITEMS_ASSOCIATED_WITH_BILLS); diff --git a/packages/webapp/src/hooks/query/creditNote.tsx b/packages/webapp/src/hooks/query/creditNote.tsx index 99cfd535d..0fb31f873 100644 --- a/packages/webapp/src/hooks/query/creditNote.tsx +++ b/packages/webapp/src/hooks/query/creditNote.tsx @@ -44,6 +44,9 @@ const commonInvalidateQueries = (queryClient) => { // Invalidate financial reports. queryClient.invalidateQueries(t.FINANCIAL_REPORT); + // Invalidate transactions by reference. + queryClient.invalidateQueries(t.TRANSACTIONS_BY_REFERENCE); + // Invalidate mutate base currency abilities. queryClient.invalidateQueries(t.ORGANIZATION_MUTATE_BASE_CURRENCY_ABILITIES); }; diff --git a/packages/webapp/src/hooks/query/invoices.tsx b/packages/webapp/src/hooks/query/invoices.tsx index 2adbf87e0..886b04e7d 100644 --- a/packages/webapp/src/hooks/query/invoices.tsx +++ b/packages/webapp/src/hooks/query/invoices.tsx @@ -24,6 +24,9 @@ const commonInvalidateQueries = (queryClient) => { // Invalidate financial reports. queryClient.invalidateQueries(t.FINANCIAL_REPORT); + + // Invalidate transactions by reference. + queryClient.invalidateQueries(t.TRANSACTIONS_BY_REFERENCE); // Invalidate accounts. queryClient.invalidateQueries(t.ACCOUNTS); diff --git a/packages/webapp/src/hooks/query/paymentReceives.tsx b/packages/webapp/src/hooks/query/paymentReceives.tsx index 376993e19..5ed0118ee 100644 --- a/packages/webapp/src/hooks/query/paymentReceives.tsx +++ b/packages/webapp/src/hooks/query/paymentReceives.tsx @@ -24,6 +24,9 @@ const commonInvalidateQueries = (client) => { // Invalidate financial reports. client.invalidateQueries(t.FINANCIAL_REPORT); + // Invalidate transactions by reference. + client.invalidateQueries(t.TRANSACTIONS_BY_REFERENCE); + // Invalidate customers. client.invalidateQueries(t.CUSTOMERS); client.invalidateQueries(t.CUSTOMER); diff --git a/packages/webapp/src/hooks/query/receipts.tsx b/packages/webapp/src/hooks/query/receipts.tsx index 7a6ae2ce9..fed4ad5af 100644 --- a/packages/webapp/src/hooks/query/receipts.tsx +++ b/packages/webapp/src/hooks/query/receipts.tsx @@ -21,6 +21,9 @@ const commonInvalidateQueries = (queryClient) => { // Invalidate financial reports. queryClient.invalidateQueries(t.FINANCIAL_REPORT); + // Invalidate the transactions by reference. + queryClient.invalidateQueries(t.TRANSACTIONS_BY_REFERENCE); + // Invalidate the cashflow transactions. queryClient.invalidateQueries(t.CASH_FLOW_TRANSACTIONS); queryClient.invalidateQueries(t.CASHFLOW_ACCOUNT_TRANSACTIONS_INFINITY); diff --git a/packages/webapp/src/hooks/query/vendorCredit.tsx b/packages/webapp/src/hooks/query/vendorCredit.tsx index 48e424099..0d2131543 100644 --- a/packages/webapp/src/hooks/query/vendorCredit.tsx +++ b/packages/webapp/src/hooks/query/vendorCredit.tsx @@ -43,6 +43,9 @@ const commonInvalidateQueries = (queryClient) => { // Invalidate financial reports. queryClient.invalidateQueries(t.FINANCIAL_REPORT); + // Invalidate the transactions by reference. + queryClient.invalidateQueries(t.TRANSACTIONS_BY_REFERENCE); + // Invalidate mutate base currency abilities. queryClient.invalidateQueries(t.ORGANIZATION_MUTATE_BASE_CURRENCY_ABILITIES); }; From ba3ea93a2d13f2b977a5b3899fff74271f227a1b Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Tue, 30 Jan 2024 00:28:23 +0200 Subject: [PATCH 13/39] chore: dump CHANGELOG.md --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c2938ad4..ae75e5f45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ All notable changes to Bigcapital server-side will be in this file. +## [0.14.0] - 30-01-2024 + +* feat: purchases by items exporting by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/327 +* fix: expense amounts should not be rounded by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/339 +* feat: get latest exchange rate from third party services by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/340 +* fix(webapp): inconsistency in currency of universal search items by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/335 +* hotfix: editing sales and expense transactions don't reflect GL entries by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/342 + ## [0.13.3] - 22-01-2024 * hotfix(server): Unhandled thrown errors of services by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/329 From 7b5287ee8011e607fac5b9efcf153661a8ca6736 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Mon, 5 Feb 2024 18:50:34 +0200 Subject: [PATCH 14/39] fix(server): Revert the paid amount to bill transaction after editing bill payment amount --- .../BillPayments/BillPaymentValidators.ts | 14 -------------- .../Purchases/BillPayments/DeleteBillPayment.ts | 10 ++-------- .../Purchases/BillPayments/EditBillPayment.ts | 12 ++++++------ .../Purchases/BillPayments/GetBillPayment.ts | 13 ++----------- .../Purchases/BillPayments/GetPaymentBills.ts | 7 +++---- .../Sales/PaymentReceives/EditPaymentReceive.ts | 3 ++- 6 files changed, 15 insertions(+), 44 deletions(-) diff --git a/packages/server/src/services/Purchases/BillPayments/BillPaymentValidators.ts b/packages/server/src/services/Purchases/BillPayments/BillPaymentValidators.ts index 3aa2902ca..8bbfadbff 100644 --- a/packages/server/src/services/Purchases/BillPayments/BillPaymentValidators.ts +++ b/packages/server/src/services/Purchases/BillPayments/BillPaymentValidators.ts @@ -10,7 +10,6 @@ import { import TenancyService from '@/services/Tenancy/TenancyService'; import { ServiceError } from '@/exceptions'; import { ACCOUNT_TYPE } from '@/data/AccountTypes'; -import { BillPayment } from '@/models'; import { ERRORS } from './constants'; @Service() @@ -18,19 +17,6 @@ export class BillPaymentValidators { @Inject() private tenancy: TenancyService; - /** - * Validates the payment existance. - * @param {BillPayment | undefined | null} payment - * @throws {ServiceError(ERRORS.PAYMENT_MADE_NOT_FOUND)} - */ - public async validateBillPaymentExistance( - payment: BillPayment | undefined | null - ) { - if (!payment) { - throw new ServiceError(ERRORS.PAYMENT_MADE_NOT_FOUND); - } - } - /** * Validates the bill payment existance. * @param {Request} req diff --git a/packages/server/src/services/Purchases/BillPayments/DeleteBillPayment.ts b/packages/server/src/services/Purchases/BillPayments/DeleteBillPayment.ts index 4ab1c9a25..e4a1ab1fa 100644 --- a/packages/server/src/services/Purchases/BillPayments/DeleteBillPayment.ts +++ b/packages/server/src/services/Purchases/BillPayments/DeleteBillPayment.ts @@ -1,6 +1,5 @@ import { Knex } from 'knex'; import UnitOfWork from '@/services/UnitOfWork'; -import { BillPaymentValidators } from './BillPaymentValidators'; import { EventPublisher } from '@/lib/EventPublisher/EventPublisher'; import HasTenancyService from '@/services/Tenancy/TenancyService'; import { Inject, Service } from 'typedi'; @@ -21,9 +20,6 @@ export class DeleteBillPayment { @Inject() private uow: UnitOfWork; - @Inject() - private validators: BillPaymentValidators; - /** * Deletes the bill payment and associated transactions. * @param {number} tenantId - Tenant id. @@ -36,10 +32,8 @@ export class DeleteBillPayment { // Retrieve the bill payment or throw not found service error. const oldBillPayment = await BillPayment.query() .withGraphFetched('entries') - .findById(billPaymentId); - - // Validates the bill payment existance. - this.validators.validateBillPaymentExistance(oldBillPayment); + .findById(billPaymentId) + .throwIfNotFound(); // Deletes the bill transactions with associated transactions under // unit-of-work envirement. diff --git a/packages/server/src/services/Purchases/BillPayments/EditBillPayment.ts b/packages/server/src/services/Purchases/BillPayments/EditBillPayment.ts index 20c72d38b..de18853bb 100644 --- a/packages/server/src/services/Purchases/BillPayments/EditBillPayment.ts +++ b/packages/server/src/services/Purchases/BillPayments/EditBillPayment.ts @@ -57,12 +57,12 @@ export class EditBillPayment { const tenantMeta = await TenantMetadata.query().findOne({ tenantId }); - const oldBillPayment = await BillPayment.query().findById(billPaymentId); + const oldBillPayment = await BillPayment.query() + .findById(billPaymentId) + .withGraphFetched('entries') + .throwIfNotFound(); - // Validates the bill payment existance. - this.validators.validateBillPaymentExistance(oldBillPayment); - - // + // Retrieves the bill payment vendor or throw not found error. const vendor = await Contact.query() .modify('vendor') .findById(billPaymentDTO.vendorId) @@ -126,7 +126,7 @@ export class EditBillPayment { trx, } as IBillPaymentEditingPayload); - // Deletes the bill payment transaction graph from the storage. + // Edits the bill payment transaction graph on the storage. const billPayment = await BillPayment.query(trx).upsertGraphAndFetch({ id: billPaymentId, ...billPaymentObj, diff --git a/packages/server/src/services/Purchases/BillPayments/GetBillPayment.ts b/packages/server/src/services/Purchases/BillPayments/GetBillPayment.ts index 5984e8932..ccb1cab77 100644 --- a/packages/server/src/services/Purchases/BillPayments/GetBillPayment.ts +++ b/packages/server/src/services/Purchases/BillPayments/GetBillPayment.ts @@ -1,12 +1,8 @@ import { IBillPayment } from '@/interfaces'; import HasTenancyService from '@/services/Tenancy/TenancyService'; import { Inject, Service } from 'typedi'; -import { ERRORS } from './constants'; -import { ServiceError } from '@/exceptions'; import { BillPaymentTransformer } from './BillPaymentTransformer'; import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable'; -import { BillsValidators } from '../Bills/BillsValidators'; -import { BillPaymentValidators } from './BillPaymentValidators'; @Service() export class GetBillPayment { @@ -16,9 +12,6 @@ export class GetBillPayment { @Inject() private transformer: TransformerInjectable; - @Inject() - private validators: BillPaymentValidators; - /** * Retrieve bill payment. * @param {number} tenantId @@ -37,10 +30,8 @@ export class GetBillPayment { .withGraphFetched('paymentAccount') .withGraphFetched('transactions') .withGraphFetched('branch') - .findById(billPyamentId); - - // Validates the bill payment existance. - this.validators.validateBillPaymentExistance(billPayment); + .findById(billPyamentId) + .throwIfNotFound(); return this.transformer.transform( tenantId, diff --git a/packages/server/src/services/Purchases/BillPayments/GetPaymentBills.ts b/packages/server/src/services/Purchases/BillPayments/GetPaymentBills.ts index 7b86c8f04..ec839411b 100644 --- a/packages/server/src/services/Purchases/BillPayments/GetPaymentBills.ts +++ b/packages/server/src/services/Purchases/BillPayments/GetPaymentBills.ts @@ -18,10 +18,9 @@ export class GetPaymentBills { public async getPaymentBills(tenantId: number, billPaymentId: number) { const { Bill, BillPayment } = this.tenancy.models(tenantId); - const billPayment = await BillPayment.query().findById(billPaymentId); - - // Validates the bill payment existance. - this.validators.validateBillPaymentExistance(billPayment); + const billPayment = await BillPayment.query() + .findById(billPaymentId) + .throwIfNotFound(); const paymentBillsIds = billPayment.entries.map((entry) => entry.id); diff --git a/packages/server/src/services/Sales/PaymentReceives/EditPaymentReceive.ts b/packages/server/src/services/Sales/PaymentReceives/EditPaymentReceive.ts index b99413748..635f48946 100644 --- a/packages/server/src/services/Sales/PaymentReceives/EditPaymentReceive.ts +++ b/packages/server/src/services/Sales/PaymentReceives/EditPaymentReceive.ts @@ -61,7 +61,8 @@ export class EditPaymentReceive { // Validate the payment receive existance. const oldPaymentReceive = await PaymentReceive.query() .withGraphFetched('entries') - .findById(paymentReceiveId); + .findById(paymentReceiveId) + .throwIfNotFound(); // Validates the payment existance. this.validators.validatePaymentExistance(oldPaymentReceive); From b38020d3971f562ed1aafb3f93b2b1cc95bf5a8d Mon Sep 17 00:00:00 2001 From: "a.nasouf" Date: Mon, 5 Feb 2024 18:58:02 +0200 Subject: [PATCH 15/39] fix: gmail email addresses dots gets removed --- .../src/api/controllers/Contacts/Contacts.ts | 126 +++++++++--------- .../webapp/src/components/Details/index.tsx | 2 +- 2 files changed, 64 insertions(+), 64 deletions(-) diff --git a/packages/server/src/api/controllers/Contacts/Contacts.ts b/packages/server/src/api/controllers/Contacts/Contacts.ts index a05ee93fd..2449e7d6f 100644 --- a/packages/server/src/api/controllers/Contacts/Contacts.ts +++ b/packages/server/src/api/controllers/Contacts/Contacts.ts @@ -1,11 +1,11 @@ -import { check, param, query, body, ValidationChain } from 'express-validator'; -import { Router, Request, Response, NextFunction } from 'express'; -import { Inject, Service } from 'typedi'; -import { ServiceError } from '@/exceptions'; -import BaseController from '@/api/controllers/BaseController'; -import ContactsService from '@/services/Contacts/ContactsService'; -import DynamicListingService from '@/services/DynamicListing/DynamicListService'; -import { DATATYPES_LENGTH } from '@/data/DataTypes'; +import { check, param, query, body, ValidationChain } from "express-validator"; +import { Router, Request, Response, NextFunction } from "express"; +import { Inject, Service } from "typedi"; +import { ServiceError } from "@/exceptions"; +import BaseController from "@/api/controllers/BaseController"; +import ContactsService from "@/services/Contacts/ContactsService"; +import DynamicListingService from "@/services/DynamicListing/DynamicListService"; +import { DATATYPES_LENGTH } from "@/data/DataTypes"; @Service() export default class ContactsController extends BaseController { @@ -22,28 +22,28 @@ export default class ContactsController extends BaseController { const router = Router(); router.get( - '/auto-complete', + "/auto-complete", [...this.autocompleteQuerySchema], this.validationResult, this.asyncMiddleware(this.autocompleteContacts.bind(this)), this.dynamicListService.handlerErrorsToResponse ); router.get( - '/:id', - [param('id').exists().isNumeric().toInt()], + "/:id", + [param("id").exists().isNumeric().toInt()], this.validationResult, this.asyncMiddleware(this.getContact.bind(this)) ); router.post( - '/:id/inactivate', - [param('id').exists().isNumeric().toInt()], + "/:id/inactivate", + [param("id").exists().isNumeric().toInt()], this.validationResult, this.asyncMiddleware(this.inactivateContact.bind(this)), this.handlerServiceErrors ); router.post( - '/:id/activate', - [param('id').exists().isNumeric().toInt()], + "/:id/activate", + [param("id").exists().isNumeric().toInt()], this.validationResult, this.asyncMiddleware(this.activateContact.bind(this)), this.handlerServiceErrors @@ -56,11 +56,11 @@ export default class ContactsController extends BaseController { */ get autocompleteQuerySchema() { return [ - query('column_sort_by').optional().trim().escape(), - query('sort_order').optional().isIn(['desc', 'asc']), + query("column_sort_by").optional().trim().escape(), + query("sort_order").optional().isIn(["desc", "asc"]), - query('stringified_filter_roles').optional().isJSON(), - query('limit').optional().isNumeric().toInt(), + query("stringified_filter_roles").optional().isJSON(), + query("limit").optional().isNumeric().toInt(), ]; } @@ -97,8 +97,8 @@ export default class ContactsController extends BaseController { const { tenantId } = req; const filter = { filterRoles: [], - sortOrder: 'asc', - columnSortBy: 'display_name', + sortOrder: "asc", + columnSortBy: "display_name", limit: 10, ...this.matchedQueryData(req), }; @@ -118,170 +118,170 @@ export default class ContactsController extends BaseController { */ get contactDTOSchema(): ValidationChain[] { return [ - check('salutation') + check("salutation") .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check('first_name') + check("first_name") .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check('last_name') + check("last_name") .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check('company_name') + check("company_name") .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check('display_name') + check("display_name") .exists() .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check('email') + check("email") .optional({ nullable: true }) .isString() - .normalizeEmail() + .normalizeEmail({ gmail_remove_dots: false }) .isEmail() .isLength({ max: DATATYPES_LENGTH.STRING }), - check('website') + check("website") .optional({ nullable: true }) .isString() .trim() .isURL() .isLength({ max: DATATYPES_LENGTH.STRING }), - check('work_phone') + check("work_phone") .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check('personal_phone') + check("personal_phone") .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check('billing_address_1') + check("billing_address_1") .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check('billing_address_2') + check("billing_address_2") .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check('billing_address_city') + check("billing_address_city") .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check('billing_address_country') + check("billing_address_country") .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check('billing_address_email') + check("billing_address_email") .optional({ nullable: true }) .isString() .isEmail() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check('billing_address_postcode') + check("billing_address_postcode") .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check('billing_address_phone') + check("billing_address_phone") .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check('billing_address_state') + check("billing_address_state") .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check('shipping_address_1') + check("shipping_address_1") .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check('shipping_address_2') + check("shipping_address_2") .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check('shipping_address_city') + check("shipping_address_city") .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check('shipping_address_country') + check("shipping_address_country") .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check('shipping_address_email') + check("shipping_address_email") .optional({ nullable: true }) .isString() .isEmail() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check('shipping_address_postcode') + check("shipping_address_postcode") .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check('shipping_address_phone') + check("shipping_address_phone") .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check('shipping_address_state') + check("shipping_address_state") .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check('note') + check("note") .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.TEXT }), - check('active').optional().isBoolean().toBoolean(), + check("active").optional().isBoolean().toBoolean(), ]; } @@ -291,19 +291,19 @@ export default class ContactsController extends BaseController { */ get contactNewDTOSchema(): ValidationChain[] { return [ - check('opening_balance') + check("opening_balance") .optional({ nullable: true }) .isInt({ min: 0, max: DATATYPES_LENGTH.DECIMAL_13_3 }) .toInt(), - check('opening_balance_exchange_rate') + check("opening_balance_exchange_rate") .default(1) .isFloat({ gt: 0 }) .toFloat(), - body('opening_balance_at') - .if(body('opening_balance').exists()) + body("opening_balance_at") + .if(body("opening_balance").exists()) .exists() .isISO8601(), - check('opening_balance_branch_id') + check("opening_balance_branch_id") .optional({ nullable: true }) .isNumeric() .toInt(), @@ -322,7 +322,7 @@ export default class ContactsController extends BaseController { * @returns {ValidationChain[]} */ get specificContactSchema(): ValidationChain[] { - return [param('id').exists().isNumeric().toInt()]; + return [param("id").exists().isNumeric().toInt()]; } /** @@ -340,7 +340,7 @@ export default class ContactsController extends BaseController { return res.status(200).send({ id: contactId, - message: 'The given contact activated successfully.', + message: "The given contact activated successfully.", }); } catch (error) { next(error); @@ -362,7 +362,7 @@ export default class ContactsController extends BaseController { return res.status(200).send({ id: contactId, - message: 'The given contact inactivated successfully.', + message: "The given contact inactivated successfully.", }); } catch (error) { next(error); @@ -383,19 +383,19 @@ export default class ContactsController extends BaseController { next: NextFunction ) { if (error instanceof ServiceError) { - if (error.errorType === 'contact_not_found') { + if (error.errorType === "contact_not_found") { return res.boom.badRequest(null, { - errors: [{ type: 'CONTACT.NOT.FOUND', code: 100 }], + errors: [{ type: "CONTACT.NOT.FOUND", code: 100 }], }); } - if (error.errorType === 'CONTACT_ALREADY_ACTIVE') { + if (error.errorType === "CONTACT_ALREADY_ACTIVE") { return res.boom.badRequest(null, { - errors: [{ type: 'CONTACT_ALREADY_ACTIVE', code: 700 }], + errors: [{ type: "CONTACT_ALREADY_ACTIVE", code: 700 }], }); } - if (error.errorType === 'CONTACT_ALREADY_INACTIVE') { + if (error.errorType === "CONTACT_ALREADY_INACTIVE") { return res.boom.badRequest(null, { - errors: [{ type: 'CONTACT_ALREADY_INACTIVE', code: 800 }], + errors: [{ type: "CONTACT_ALREADY_INACTIVE", code: 800 }], }); } } diff --git a/packages/webapp/src/components/Details/index.tsx b/packages/webapp/src/components/Details/index.tsx index e6803b069..9879fc7c0 100644 --- a/packages/webapp/src/components/Details/index.tsx +++ b/packages/webapp/src/components/Details/index.tsx @@ -66,7 +66,7 @@ export function DetailItem({ label, children, name, align, className }) { > {label} -
{children}
+
{children}
); } From a6db4fb6dffad1c75edf053c7f7526049e555f50 Mon Sep 17 00:00:00 2001 From: "a.nasouf" Date: Mon, 5 Feb 2024 19:52:48 +0200 Subject: [PATCH 16/39] fix: some keywords are not localized --- packages/webapp/src/containers/Authentication/Login.tsx | 2 +- packages/webapp/src/containers/Authentication/Register.tsx | 2 +- .../webapp/src/containers/Authentication/ResetPassword.tsx | 6 +++--- .../src/containers/Authentication/SendResetPassword.tsx | 6 +++--- .../src/containers/Authentication/SendResetPasswordForm.tsx | 5 ++--- packages/webapp/src/lang/ar/index.json | 5 +++++ packages/webapp/src/lang/en/index.json | 5 +++++ 7 files changed, 20 insertions(+), 11 deletions(-) diff --git a/packages/webapp/src/containers/Authentication/Login.tsx b/packages/webapp/src/containers/Authentication/Login.tsx index 9efbac5e9..0ac30e1bc 100644 --- a/packages/webapp/src/containers/Authentication/Login.tsx +++ b/packages/webapp/src/containers/Authentication/Login.tsx @@ -70,7 +70,7 @@ function LoginFooterLinks() { {!signupDisabled && ( - Don't have an account? Sign up + )} diff --git a/packages/webapp/src/containers/Authentication/Register.tsx b/packages/webapp/src/containers/Authentication/Register.tsx index 5a42bbf67..32225c850 100644 --- a/packages/webapp/src/containers/Authentication/Register.tsx +++ b/packages/webapp/src/containers/Authentication/Register.tsx @@ -87,7 +87,7 @@ function RegisterFooterLinks() { return ( - Return to Sign In + diff --git a/packages/webapp/src/containers/Authentication/ResetPassword.tsx b/packages/webapp/src/containers/Authentication/ResetPassword.tsx index 136d28174..afb43308b 100644 --- a/packages/webapp/src/containers/Authentication/ResetPassword.tsx +++ b/packages/webapp/src/containers/Authentication/ResetPassword.tsx @@ -5,7 +5,7 @@ import { Formik } from 'formik'; import { Intent, Position } from '@blueprintjs/core'; import { Link, useParams, useHistory } from 'react-router-dom'; -import { AppToaster } from '@/components'; +import { AppToaster, FormattedMessage as T } from '@/components'; import { useAuthResetPassword } from '@/hooks/query'; import AuthInsider from '@/containers/Authentication/AuthInsider'; @@ -86,11 +86,11 @@ function ResetPasswordFooterLinks() { {!signupDisabled && ( - Don't have an account? Sign up + )} - Return to Sign In + ); diff --git a/packages/webapp/src/containers/Authentication/SendResetPassword.tsx b/packages/webapp/src/containers/Authentication/SendResetPassword.tsx index c90f872c1..b8f24831c 100644 --- a/packages/webapp/src/containers/Authentication/SendResetPassword.tsx +++ b/packages/webapp/src/containers/Authentication/SendResetPassword.tsx @@ -5,7 +5,7 @@ import { Formik } from 'formik'; import { Link, useHistory } from 'react-router-dom'; import { Intent } from '@blueprintjs/core'; -import { AppToaster } from '@/components'; +import { AppToaster, FormattedMessage as T } from '@/components'; import { useAuthSendResetPassword } from '@/hooks/query'; import SendResetPasswordForm from './SendResetPasswordForm'; @@ -82,11 +82,11 @@ function SendResetPasswordFooterLinks() { {!signupDisabled && ( - Don't have an account? Sign up + )} - Return to Sign In + ); diff --git a/packages/webapp/src/containers/Authentication/SendResetPasswordForm.tsx b/packages/webapp/src/containers/Authentication/SendResetPasswordForm.tsx index 3f2718d59..a2f6e114b 100644 --- a/packages/webapp/src/containers/Authentication/SendResetPasswordForm.tsx +++ b/packages/webapp/src/containers/Authentication/SendResetPasswordForm.tsx @@ -14,8 +14,7 @@ export default function SendResetPasswordForm({ isSubmitting }) { return (
- Enter the email address associated with your account and we'll send you - a link to reset your password. + }> @@ -29,7 +28,7 @@ export default function SendResetPasswordForm({ isSubmitting }) { large={true} loading={isSubmitting} > - Reset Password + ); diff --git a/packages/webapp/src/lang/ar/index.json b/packages/webapp/src/lang/ar/index.json index 976b049d6..6e3996d91 100644 --- a/packages/webapp/src/lang/ar/index.json +++ b/packages/webapp/src/lang/ar/index.json @@ -20,6 +20,11 @@ "log_in": "تسجيل الدخول", "forget_my_password": "نسيت كلمة المرور الخاصة بي", "keep_me_logged_in": "تذكرني", + "dont_have_an_account": "ليس لديك حساب؟", + "sign_up": "تسجيل", + "return_to": "عودة إلى", + "sign_in": "صفحة الدخول", + "enter_the_email_address_associated_with_your_account": "قم بادخال بريدك الإلكتروني المرتبط بالحساب وسوف نرسل لك رابط لاعادة تعيين كلمة المرور.", "create_an_account": "إنشاء حساب", "need_bigcapital_account": "تحتاج إلى حساب Bigcapital؟", "show": "عرض", diff --git a/packages/webapp/src/lang/en/index.json b/packages/webapp/src/lang/en/index.json index 5522f57d6..12a36e5bd 100644 --- a/packages/webapp/src/lang/en/index.json +++ b/packages/webapp/src/lang/en/index.json @@ -19,6 +19,11 @@ "log_in": "Log in", "forget_my_password": "Forget my password", "keep_me_logged_in": "Keep me logged in", + "dont_have_an_account": "Don't have an account?", + "sign_up": "Sign up", + "return_to": "Return to", + "sign_in": "Sign In", + "enter_the_email_address_associated_with_your_account": "Enter the email address associated with your account and we'll send you a link to reset your password.", "create_an_account": "Create an account", "need_bigcapital_account": "Need a Bigcapital account ?", "show": "Show", From f7f77b12c9d70b9b89f56d18ec9a368b6f1b568d Mon Sep 17 00:00:00 2001 From: "a.nasouf" Date: Mon, 5 Feb 2024 20:05:10 +0200 Subject: [PATCH 17/39] fix: file formatting --- .../src/api/controllers/Contacts/Contacts.ts | 138 +++++++++--------- 1 file changed, 69 insertions(+), 69 deletions(-) diff --git a/packages/server/src/api/controllers/Contacts/Contacts.ts b/packages/server/src/api/controllers/Contacts/Contacts.ts index 2449e7d6f..751654a8e 100644 --- a/packages/server/src/api/controllers/Contacts/Contacts.ts +++ b/packages/server/src/api/controllers/Contacts/Contacts.ts @@ -1,11 +1,11 @@ -import { check, param, query, body, ValidationChain } from "express-validator"; -import { Router, Request, Response, NextFunction } from "express"; -import { Inject, Service } from "typedi"; -import { ServiceError } from "@/exceptions"; -import BaseController from "@/api/controllers/BaseController"; -import ContactsService from "@/services/Contacts/ContactsService"; -import DynamicListingService from "@/services/DynamicListing/DynamicListService"; -import { DATATYPES_LENGTH } from "@/data/DataTypes"; +import { check, param, query, body, ValidationChain } from 'express-validator'; +import { Router, Request, Response, NextFunction } from 'express'; +import { Inject, Service } from 'typedi'; +import { ServiceError } from '@/exceptions'; +import BaseController from '@/api/controllers/BaseController'; +import ContactsService from '@/services/Contacts/ContactsService'; +import DynamicListingService from '@/services/DynamicListing/DynamicListService'; +import { DATATYPES_LENGTH } from '@/data/DataTypes'; @Service() export default class ContactsController extends BaseController { @@ -22,31 +22,31 @@ export default class ContactsController extends BaseController { const router = Router(); router.get( - "/auto-complete", + '/auto-complete', [...this.autocompleteQuerySchema], this.validationResult, this.asyncMiddleware(this.autocompleteContacts.bind(this)), - this.dynamicListService.handlerErrorsToResponse + this.dynamicListService.handlerErrorsToResponse, ); router.get( - "/:id", - [param("id").exists().isNumeric().toInt()], + '/:id', + [param('id').exists().isNumeric().toInt()], this.validationResult, - this.asyncMiddleware(this.getContact.bind(this)) + this.asyncMiddleware(this.getContact.bind(this)), ); router.post( - "/:id/inactivate", - [param("id").exists().isNumeric().toInt()], + '/:id/inactivate', + [param('id').exists().isNumeric().toInt()], this.validationResult, this.asyncMiddleware(this.inactivateContact.bind(this)), - this.handlerServiceErrors + this.handlerServiceErrors, ); router.post( - "/:id/activate", - [param("id").exists().isNumeric().toInt()], + '/:id/activate', + [param('id').exists().isNumeric().toInt()], this.validationResult, this.asyncMiddleware(this.activateContact.bind(this)), - this.handlerServiceErrors + this.handlerServiceErrors, ); return router; } @@ -56,11 +56,11 @@ export default class ContactsController extends BaseController { */ get autocompleteQuerySchema() { return [ - query("column_sort_by").optional().trim().escape(), - query("sort_order").optional().isIn(["desc", "asc"]), + query('column_sort_by').optional().trim().escape(), + query('sort_order').optional().isIn(['desc', 'asc']), - query("stringified_filter_roles").optional().isJSON(), - query("limit").optional().isNumeric().toInt(), + query('stringified_filter_roles').optional().isJSON(), + query('limit').optional().isNumeric().toInt(), ]; } @@ -77,7 +77,7 @@ export default class ContactsController extends BaseController { try { const contact = await this.contactsService.getContact( tenantId, - contactId + contactId, ); return res.status(200).send({ customer: this.transfromToResponse(contact), @@ -97,15 +97,15 @@ export default class ContactsController extends BaseController { const { tenantId } = req; const filter = { filterRoles: [], - sortOrder: "asc", - columnSortBy: "display_name", + sortOrder: 'asc', + columnSortBy: 'display_name', limit: 10, ...this.matchedQueryData(req), }; try { const contacts = await this.contactsService.autocompleteContacts( tenantId, - filter + filter, ); return res.status(200).send({ contacts }); } catch (error) { @@ -118,170 +118,170 @@ export default class ContactsController extends BaseController { */ get contactDTOSchema(): ValidationChain[] { return [ - check("salutation") + check('salutation') .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check("first_name") + check('first_name') .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check("last_name") + check('last_name') .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check("company_name") + check('company_name') .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check("display_name") + check('display_name') .exists() .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check("email") + check('email') .optional({ nullable: true }) .isString() .normalizeEmail({ gmail_remove_dots: false }) .isEmail() .isLength({ max: DATATYPES_LENGTH.STRING }), - check("website") + check('website') .optional({ nullable: true }) .isString() .trim() .isURL() .isLength({ max: DATATYPES_LENGTH.STRING }), - check("work_phone") + check('work_phone') .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check("personal_phone") + check('personal_phone') .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check("billing_address_1") + check('billing_address_1') .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check("billing_address_2") + check('billing_address_2') .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check("billing_address_city") + check('billing_address_city') .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check("billing_address_country") + check('billing_address_country') .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check("billing_address_email") + check('billing_address_email') .optional({ nullable: true }) .isString() .isEmail() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check("billing_address_postcode") + check('billing_address_postcode') .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check("billing_address_phone") + check('billing_address_phone') .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check("billing_address_state") + check('billing_address_state') .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check("shipping_address_1") + check('shipping_address_1') .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check("shipping_address_2") + check('shipping_address_2') .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check("shipping_address_city") + check('shipping_address_city') .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check("shipping_address_country") + check('shipping_address_country') .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check("shipping_address_email") + check('shipping_address_email') .optional({ nullable: true }) .isString() .isEmail() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check("shipping_address_postcode") + check('shipping_address_postcode') .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check("shipping_address_phone") + check('shipping_address_phone') .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check("shipping_address_state") + check('shipping_address_state') .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check("note") + check('note') .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.TEXT }), - check("active").optional().isBoolean().toBoolean(), + check('active').optional().isBoolean().toBoolean(), ]; } @@ -291,19 +291,19 @@ export default class ContactsController extends BaseController { */ get contactNewDTOSchema(): ValidationChain[] { return [ - check("opening_balance") + check('opening_balance') .optional({ nullable: true }) .isInt({ min: 0, max: DATATYPES_LENGTH.DECIMAL_13_3 }) .toInt(), - check("opening_balance_exchange_rate") + check('opening_balance_exchange_rate') .default(1) .isFloat({ gt: 0 }) .toFloat(), - body("opening_balance_at") - .if(body("opening_balance").exists()) + body('opening_balance_at') + .if(body('opening_balance').exists()) .exists() .isISO8601(), - check("opening_balance_branch_id") + check('opening_balance_branch_id') .optional({ nullable: true }) .isNumeric() .toInt(), @@ -322,7 +322,7 @@ export default class ContactsController extends BaseController { * @returns {ValidationChain[]} */ get specificContactSchema(): ValidationChain[] { - return [param("id").exists().isNumeric().toInt()]; + return [param('id').exists().isNumeric().toInt()]; } /** @@ -340,7 +340,7 @@ export default class ContactsController extends BaseController { return res.status(200).send({ id: contactId, - message: "The given contact activated successfully.", + message: 'The given contact activated successfully.', }); } catch (error) { next(error); @@ -362,7 +362,7 @@ export default class ContactsController extends BaseController { return res.status(200).send({ id: contactId, - message: "The given contact inactivated successfully.", + message: 'The given contact inactivated successfully.', }); } catch (error) { next(error); @@ -380,22 +380,22 @@ export default class ContactsController extends BaseController { error: Error, req: Request, res: Response, - next: NextFunction + next: NextFunction, ) { if (error instanceof ServiceError) { - if (error.errorType === "contact_not_found") { + if (error.errorType === 'contact_not_found') { return res.boom.badRequest(null, { - errors: [{ type: "CONTACT.NOT.FOUND", code: 100 }], + errors: [{ type: 'CONTACT.NOT.FOUND', code: 100 }], }); } - if (error.errorType === "CONTACT_ALREADY_ACTIVE") { + if (error.errorType === 'CONTACT_ALREADY_ACTIVE') { return res.boom.badRequest(null, { - errors: [{ type: "CONTACT_ALREADY_ACTIVE", code: 700 }], + errors: [{ type: 'CONTACT_ALREADY_ACTIVE', code: 700 }], }); } - if (error.errorType === "CONTACT_ALREADY_INACTIVE") { + if (error.errorType === 'CONTACT_ALREADY_INACTIVE') { return res.boom.badRequest(null, { - errors: [{ type: "CONTACT_ALREADY_INACTIVE", code: 800 }], + errors: [{ type: 'CONTACT_ALREADY_INACTIVE', code: 800 }], }); } } From 12740223a8664cf8464628e62ab3863366988885 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Mon, 5 Feb 2024 22:38:56 +0200 Subject: [PATCH 18/39] fix: Invalid bill payment amount on editing bill payment --- .../server/src/api/controllers/Purchases/Bills.ts | 10 ++++++++++ .../services/Purchases/Bills/BillsValidators.ts | 14 ++++++++++++++ .../src/services/Purchases/Bills/EditBill.ts | 6 ++++++ .../src/services/Purchases/Bills/constants.ts | 1 + packages/webapp/src/constants/errors.tsx | 6 ++++-- .../containers/Purchases/Bills/BillForm/utils.tsx | 9 +++++++++ .../PaymentForm/PaymentMadeEntriesTable.tsx | 3 ++- .../Sales/Invoices/InvoiceForm/utils.tsx | 10 ++++++++++ .../PaymentReceiveItemsTable.tsx | 3 ++- packages/webapp/src/lang/en/index.json | 2 ++ 10 files changed, 60 insertions(+), 4 deletions(-) diff --git a/packages/server/src/api/controllers/Purchases/Bills.ts b/packages/server/src/api/controllers/Purchases/Bills.ts index 9de79ecf7..4f65eab26 100644 --- a/packages/server/src/api/controllers/Purchases/Bills.ts +++ b/packages/server/src/api/controllers/Purchases/Bills.ts @@ -560,6 +560,16 @@ export default class BillsController extends BaseController { errors: [{ type: 'ITEM_ENTRY_TAX_RATE_ID_NOT_FOUND', code: 1900 }], }); } + if (error.errorType === 'BILL_AMOUNT_SMALLER_THAN_PAID_AMOUNT') { + return res.boom.badRequest(null, { + errors: [ + { + type: 'BILL_AMOUNT_SMALLER_THAN_PAID_AMOUNT', + code: 2000, + }, + ], + }); + } } next(error); } diff --git a/packages/server/src/services/Purchases/Bills/BillsValidators.ts b/packages/server/src/services/Purchases/Bills/BillsValidators.ts index cba38dfbb..9f209e40d 100644 --- a/packages/server/src/services/Purchases/Bills/BillsValidators.ts +++ b/packages/server/src/services/Purchases/Bills/BillsValidators.ts @@ -21,6 +21,20 @@ export class BillsValidators { } } + /** + * Validates the bill amount is bigger than paid amount. + * @param {number} billAmount + * @param {number} paidAmount + */ + public validateBillAmountBiggerPaidAmount( + billAmount: number, + paidAmount: number, + ) { + if (billAmount < paidAmount) { + throw new ServiceError(ERRORS.BILL_AMOUNT_SMALLER_THAN_PAID_AMOUNT); + } + } + /** * Validates the bill number existance. */ diff --git a/packages/server/src/services/Purchases/Bills/EditBill.ts b/packages/server/src/services/Purchases/Bills/EditBill.ts index 26121c120..7b10bb7f1 100644 --- a/packages/server/src/services/Purchases/Bills/EditBill.ts +++ b/packages/server/src/services/Purchases/Bills/EditBill.ts @@ -103,6 +103,7 @@ export class EditBill { tenantId, billDTO.entries ); + // Transforms the bill DTO to model object. const billObj = await this.transformerDTO.billDTOToModel( tenantId, @@ -111,6 +112,11 @@ export class EditBill { authorizedUser, oldBill ); + // Validate bill total amount should be bigger than paid amount. + this.validators.validateBillAmountBiggerPaidAmount( + billObj.amount, + oldBill.paymentAmount + ); // Validate landed cost entries that have allocated cost could not be deleted. await this.entriesService.validateLandedCostEntriesNotDeleted( oldBill.entries, diff --git a/packages/server/src/services/Purchases/Bills/constants.ts b/packages/server/src/services/Purchases/Bills/constants.ts index 12afad4c7..9cc8566c8 100644 --- a/packages/server/src/services/Purchases/Bills/constants.ts +++ b/packages/server/src/services/Purchases/Bills/constants.ts @@ -18,6 +18,7 @@ export const ERRORS = { LANDED_COST_ENTRIES_SHOULD_BE_INVENTORY_ITEMS: 'LANDED_COST_ENTRIES_SHOULD_BE_INVENTORY_ITEMS', BILL_HAS_APPLIED_TO_VENDOR_CREDIT: 'BILL_HAS_APPLIED_TO_VENDOR_CREDIT', + BILL_AMOUNT_SMALLER_THAN_PAID_AMOUNT: 'BILL_AMOUNT_SMALLER_THAN_PAID_AMOUNT', }; export const DEFAULT_VIEW_COLUMNS = []; diff --git a/packages/webapp/src/constants/errors.tsx b/packages/webapp/src/constants/errors.tsx index 2411c4d74..22f6345cb 100644 --- a/packages/webapp/src/constants/errors.tsx +++ b/packages/webapp/src/constants/errors.tsx @@ -9,6 +9,8 @@ export const ERROR = { SALE_INVOICE_NO_NOT_UNIQUE: 'SALE_INVOICE_NO_NOT_UNIQUE', SALE_ESTIMATE_IS_ALREADY_CONVERTED_TO_INVOICE: 'SALE_ESTIMATE_IS_ALREADY_CONVERTED_TO_INVOICE', + INVOICE_AMOUNT_SMALLER_THAN_PAYMENT_AMOUNT: + 'INVOICE_AMOUNT_SMALLER_THAN_PAYMENT_AMOUNT', // Sales Receipts SALE_RECEIPT_NUMBER_NOT_UNIQUE: 'SALE_RECEIPT_NUMBER_NOT_UNIQUE', @@ -17,6 +19,6 @@ export const ERROR = { // Bills BILL_NUMBER_EXISTS: 'BILL.NUMBER.EXISTS', SALE_INVOICE_NO_IS_REQUIRED: 'SALE_INVOICE_NO_IS_REQUIRED', - ENTRIES_ALLOCATED_COST_COULD_NOT_DELETED:"ENTRIES_ALLOCATED_COST_COULD_NOT_DELETED", - + ENTRIES_ALLOCATED_COST_COULD_NOT_DELETED: + 'ENTRIES_ALLOCATED_COST_COULD_NOT_DELETED', }; diff --git a/packages/webapp/src/containers/Purchases/Bills/BillForm/utils.tsx b/packages/webapp/src/containers/Purchases/Bills/BillForm/utils.tsx index 763573b30..f98f02757 100644 --- a/packages/webapp/src/containers/Purchases/Bills/BillForm/utils.tsx +++ b/packages/webapp/src/containers/Purchases/Bills/BillForm/utils.tsx @@ -67,6 +67,7 @@ export const ERRORS = { BILL_NUMBER_EXISTS: 'BILL.NUMBER.EXISTS', ENTRIES_ALLOCATED_COST_COULD_NOT_DELETED: 'ENTRIES_ALLOCATED_COST_COULD_NOT_DELETED', + BILL_AMOUNT_SMALLER_THAN_PAID_AMOUNT: 'BILL_AMOUNT_SMALLER_THAN_PAID_AMOUNT', }; /** * Transformes the bill to initial values of edit form. @@ -200,6 +201,14 @@ export const handleErrors = (errors, { setErrors }) => { }), ); } + if ( + errors.some((e) => e.type === ERRORS.BILL_AMOUNT_SMALLER_THAN_PAID_AMOUNT) + ) { + AppToaster.show({ + intent: Intent.DANGER, + message: intl.get('bill.total_smaller_than_paid_amount'), + }); + } }; export const useSetPrimaryBranchToForm = () => { diff --git a/packages/webapp/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeEntriesTable.tsx b/packages/webapp/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeEntriesTable.tsx index cd0228799..841a3f359 100644 --- a/packages/webapp/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeEntriesTable.tsx +++ b/packages/webapp/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeEntriesTable.tsx @@ -30,6 +30,7 @@ export default function PaymentMadeEntriesTable({ // Formik context. const { values: { vendor_id }, + errors, } = useFormikContext(); // Handle update data. @@ -63,7 +64,7 @@ export default function PaymentMadeEntriesTable({ data={entries} spinnerProps={false} payload={{ - errors: [], + errors: errors?.entries || [], updateData: handleUpdateData, currencyCode, }} diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/utils.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/utils.tsx index 8c32f017c..a2ff9988a 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/utils.tsx +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/utils.tsx @@ -112,6 +112,16 @@ export const transformErrors = (errors, { setErrors }) => { intent: Intent.DANGER, }); } + if ( + errors.some( + ({ type }) => type === ERROR.INVOICE_AMOUNT_SMALLER_THAN_PAYMENT_AMOUNT, + ) + ) { + AppToaster.show({ + message: intl.get('sale_invoice.total_smaller_than_paid_amount'), + intent: Intent.DANGER, + }); + } if ( errors.some((error) => error.type === ERROR.SALE_INVOICE_NO_IS_REQUIRED) ) { diff --git a/packages/webapp/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveItemsTable.tsx b/packages/webapp/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveItemsTable.tsx index 5b8a1be34..910403c7e 100644 --- a/packages/webapp/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveItemsTable.tsx +++ b/packages/webapp/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveItemsTable.tsx @@ -28,6 +28,7 @@ export default function PaymentReceiveItemsTable({ // Formik context. const { values: { customer_id }, + errors, } = useFormikContext(); // No results message. @@ -58,7 +59,7 @@ export default function PaymentReceiveItemsTable({ data={entries} spinnerProps={false} payload={{ - errors: [], + errors: errors?.entries || [], updateData: handleUpdateData, currencyCode, }} diff --git a/packages/webapp/src/lang/en/index.json b/packages/webapp/src/lang/en/index.json index 5522f57d6..bf780d4cf 100644 --- a/packages/webapp/src/lang/en/index.json +++ b/packages/webapp/src/lang/en/index.json @@ -653,7 +653,9 @@ "invoice_number_is_not_unqiue": "Invoice number is not unqiue", "sale_receipt_number_not_unique": "Receipt number is not unique", "sale_invoice_number_is_exists": "Sale invoice number is exists", + "sale_invoice.total_smaller_than_paid_amount": "The invoice total is smaller than the invoice paid amount.", "bill_number_exists": "Bill number exists", + "bill.total_smaller_than_paid_amount": "The bill total is smaller than the bill paid amount.", "ok": "Ok!", "quantity_cannot_be_zero_or_empty": "Quantity cannot be zero or empty.", "customer_email": "Customer email", From 528d447443a4b2ada6f1e365e0878ab6c43e30e5 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Mon, 5 Feb 2024 23:04:02 +0200 Subject: [PATCH 19/39] fix(server): Trial balance total row shouldn't show if accounts have no balances --- .../TrialBalanceSheet/TrialBalanceSheet.ts | 4 ---- .../TrialBalanceSheet/TrialBalanceSheetTable.ts | 4 ++-- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheet.ts b/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheet.ts index 38ed3a944..8554268e3 100644 --- a/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheet.ts +++ b/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheet.ts @@ -252,10 +252,6 @@ export default class TrialBalanceSheet extends FinancialSheet { * @return {ITrialBalanceSheetData} */ public reportData(): ITrialBalanceSheetData { - // Don't return noting if the journal has no transactions. - if (this.repository.totalAccountsLedger.isEmpty()) { - return null; - } // Retrieve accounts nodes. const accounts = this.accountsSection(this.repository.accounts); diff --git a/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetTable.ts b/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetTable.ts index 1cbb2e7e6..5de03eb71 100644 --- a/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetTable.ts +++ b/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetTable.ts @@ -46,7 +46,7 @@ export class TrialBalanceSheetTable extends R.compose( this.query = query; this.i18n = i18n; } - + /** * Retrieve the common columns for all report nodes. * @param {ITableColumnAccessor[]} @@ -123,7 +123,7 @@ export class TrialBalanceSheetTable extends R.compose( */ public tableRows = (): ITableRow[] => { return R.compose( - R.append(this.totalTableRow()), + R.unless(R.isEmpty, R.append(this.totalTableRow())), R.concat(this.accountsTableRows()) )([]); }; From 374f1acf8a48c6035d3777f63c1faffda15f5b41 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Tue, 6 Feb 2024 10:54:41 +0200 Subject: [PATCH 20/39] fix: payment receive subtotal shouldn't be rounded --- .../PaymentReceives/PaymentReceiveTransformer.ts | 13 +++++++++++++ .../PaymentReceiveDetailTableFooter.tsx | 5 ++--- .../Drawers/PaymentReceiveDetailDrawer/utils.tsx | 12 +++++------- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/packages/server/src/services/Sales/PaymentReceives/PaymentReceiveTransformer.ts b/packages/server/src/services/Sales/PaymentReceives/PaymentReceiveTransformer.ts index 531023ce3..5ca84db07 100644 --- a/packages/server/src/services/Sales/PaymentReceives/PaymentReceiveTransformer.ts +++ b/packages/server/src/services/Sales/PaymentReceives/PaymentReceiveTransformer.ts @@ -10,6 +10,7 @@ export class PaymentReceiveTransfromer extends Transformer { */ public includeAttributes = (): string[] => { return [ + 'subtotalFormatted', 'formattedPaymentDate', 'formattedAmount', 'formattedExchangeRate', @@ -26,6 +27,18 @@ export class PaymentReceiveTransfromer extends Transformer { return this.formatDate(payment.paymentDate); }; + /** + * Retrieve the formatted payment subtotal. + * @param {IPaymentReceive} payment + * @returns {string} + */ + protected subtotalFormatted = (payment: IPaymentReceive): string => { + return formatNumber(payment.amount, { + currencyCode: payment.currencyCode, + money: false, + }); + }; + /** * Retrieve formatted payment amount. * @param {ISaleInvoice} invoice diff --git a/packages/webapp/src/containers/Drawers/PaymentReceiveDetailDrawer/PaymentReceiveDetailTableFooter.tsx b/packages/webapp/src/containers/Drawers/PaymentReceiveDetailDrawer/PaymentReceiveDetailTableFooter.tsx index eeee13ffe..868aecc0d 100644 --- a/packages/webapp/src/containers/Drawers/PaymentReceiveDetailDrawer/PaymentReceiveDetailTableFooter.tsx +++ b/packages/webapp/src/containers/Drawers/PaymentReceiveDetailDrawer/PaymentReceiveDetailTableFooter.tsx @@ -3,10 +3,9 @@ import React from 'react'; import styled from 'styled-components'; import { - FormatNumber, + T, TotalLineTextStyle, TotalLineBorderStyle, - T, TotalLine, TotalLines, } from '@/components'; @@ -27,7 +26,7 @@ export default function PaymentReceiveDetailTableFooter() { > } - value={} + value={paymentReceive.subtotal_formatted} /> } diff --git a/packages/webapp/src/containers/Drawers/PaymentReceiveDetailDrawer/utils.tsx b/packages/webapp/src/containers/Drawers/PaymentReceiveDetailDrawer/utils.tsx index 8971f3ec6..984eb5658 100644 --- a/packages/webapp/src/containers/Drawers/PaymentReceiveDetailDrawer/utils.tsx +++ b/packages/webapp/src/containers/Drawers/PaymentReceiveDetailDrawer/utils.tsx @@ -10,7 +10,7 @@ import { MenuItem, Menu, } from '@blueprintjs/core'; -import { Icon, FormatNumberCell } from '@/components'; +import { Icon } from '@/components'; import { getColumnWidth } from '@/utils'; import { usePaymentReceiveDetailContext } from './PaymentReceiveDetailProvider'; @@ -40,9 +40,8 @@ export const usePaymentReceiveEntriesColumns = () => { }, { Header: intl.get('invoice_amount'), - accessor: 'invoice.balance', - Cell: FormatNumberCell, - width: getColumnWidth(entries, 'invoice.balance', { + accessor: 'invoice.total_formatted', + width: getColumnWidth(entries, 'invoice.total_formatted', { minWidth: 60, magicSpacing: 5, }), @@ -51,10 +50,9 @@ export const usePaymentReceiveEntriesColumns = () => { }, { Header: intl.get('amount_due'), - accessor: 'invoice.due_amount', - Cell: FormatNumberCell, + accessor: 'invoice.due_amount_formatted', align: 'right', - width: getColumnWidth(entries, 'invoice.due_amount', { + width: getColumnWidth(entries, 'invoice.due_amount_formatted', { minWidth: 60, magicSpacing: 5, }), From 0f678e61c5322ae5c299e3a18bb3bffbfd83862e Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Tue, 6 Feb 2024 20:31:48 +0200 Subject: [PATCH 21/39] fix: Decimal amounts are rounded when create a new transaction on some transactions types --- .../src/services/CreditNotes/CreditNoteTransformer.ts | 10 ++++++++++ .../VendorCredits/VendorCreditTransformer.ts | 10 ++++++++++ .../Sales/Estimates/SaleEstimateTransformer.ts | 10 ++++++++++ .../services/Sales/Invoices/ItemEntryTransformer.ts | 11 ++++++++++- .../services/Sales/Receipts/SaleReceiptTransformer.ts | 10 ++++++++++ .../CreditNoteDetailTableFooter.tsx | 5 +---- .../EstimateDetailTableFooter.tsx | 3 +-- .../containers/Drawers/EstimateDetailDrawer/utils.tsx | 5 ++--- .../ReceiptDetailDrawer/ReceiptDetailTableFooter.tsx | 2 +- .../JournalEntriesTransactions/components.tsx | 7 ++++--- .../VendorCreditDetailDrawerFooter.tsx | 3 +-- .../Drawers/VendorCreditDetailDrawer/utils.tsx | 5 ++--- 12 files changed, 62 insertions(+), 19 deletions(-) diff --git a/packages/server/src/services/CreditNotes/CreditNoteTransformer.ts b/packages/server/src/services/CreditNotes/CreditNoteTransformer.ts index f532c2eab..6ed80a6f0 100644 --- a/packages/server/src/services/CreditNotes/CreditNoteTransformer.ts +++ b/packages/server/src/services/CreditNotes/CreditNoteTransformer.ts @@ -14,6 +14,7 @@ export class CreditNoteTransformer extends Transformer { 'formattedCreditNoteDate', 'formattedAmount', 'formattedCreditsUsed', + 'formattedSubtotal', 'entries', ]; }; @@ -60,6 +61,15 @@ export class CreditNoteTransformer extends Transformer { }); }; + /** + * Retrieves the formatted subtotal. + * @param {ICreditNote} credit + * @returns {string} + */ + protected formattedSubtotal = (credit): string => { + return formatNumber(credit.amount, { money: false }); + }; + /** * Retrieves the entries of the credit note. * @param {ICreditNote} credit diff --git a/packages/server/src/services/Purchases/VendorCredits/VendorCreditTransformer.ts b/packages/server/src/services/Purchases/VendorCredits/VendorCreditTransformer.ts index 3d74ee770..be1431ac2 100644 --- a/packages/server/src/services/Purchases/VendorCredits/VendorCreditTransformer.ts +++ b/packages/server/src/services/Purchases/VendorCredits/VendorCreditTransformer.ts @@ -11,6 +11,7 @@ export class VendorCreditTransformer extends Transformer { public includeAttributes = (): string[] => { return [ 'formattedAmount', + 'formattedSubtotal', 'formattedVendorCreditDate', 'formattedCreditsRemaining', 'entries', @@ -37,6 +38,15 @@ export class VendorCreditTransformer extends Transformer { }); }; + /** + * Retrieves the vendor credit formatted subtotal. + * @param {IVendorCredit} vendorCredit + * @returns {string} + */ + protected formattedSubtotal = (vendorCredit): string => { + return formatNumber(vendorCredit.amount, { money: false }); + }; + /** * Retrieve formatted credits remaining. * @param {IVendorCredit} credit diff --git a/packages/server/src/services/Sales/Estimates/SaleEstimateTransformer.ts b/packages/server/src/services/Sales/Estimates/SaleEstimateTransformer.ts index 1102f7bd0..8cd99a9db 100644 --- a/packages/server/src/services/Sales/Estimates/SaleEstimateTransformer.ts +++ b/packages/server/src/services/Sales/Estimates/SaleEstimateTransformer.ts @@ -10,6 +10,7 @@ export class SaleEstimateTransfromer extends Transformer { */ public includeAttributes = (): string[] => { return [ + 'formattedSubtotal', 'formattedAmount', 'formattedEstimateDate', 'formattedExpirationDate', @@ -76,6 +77,15 @@ export class SaleEstimateTransfromer extends Transformer { }); }; + /** + * Retrieves the formatted invoice subtotal. + * @param {ISaleEstimate} estimate + * @returns {string} + */ + protected formattedSubtotal = (estimate: ISaleEstimate): string => { + return formatNumber(estimate.amount, { money: false }); + }; + /** * Retrieves the entries of the sale estimate. * @param {ISaleEstimate} estimate diff --git a/packages/server/src/services/Sales/Invoices/ItemEntryTransformer.ts b/packages/server/src/services/Sales/Invoices/ItemEntryTransformer.ts index ad0d88525..dbaea4862 100644 --- a/packages/server/src/services/Sales/Invoices/ItemEntryTransformer.ts +++ b/packages/server/src/services/Sales/Invoices/ItemEntryTransformer.ts @@ -8,7 +8,16 @@ export class ItemEntryTransformer extends Transformer { * @returns {Array} */ public includeAttributes = (): string[] => { - return ['rateFormatted', 'totalFormatted']; + return ['quantityFormatted', 'rateFormatted', 'totalFormatted']; + }; + + /** + * Retrieves the formatted quantitty of item entry. + * @param {IItemEntry} entry + * @returns {string} + */ + protected quantityFormatted = (entry: IItemEntry): string => { + return formatNumber(entry.quantity, { money: false }); }; /** diff --git a/packages/server/src/services/Sales/Receipts/SaleReceiptTransformer.ts b/packages/server/src/services/Sales/Receipts/SaleReceiptTransformer.ts index c8b950711..9e5d3a127 100644 --- a/packages/server/src/services/Sales/Receipts/SaleReceiptTransformer.ts +++ b/packages/server/src/services/Sales/Receipts/SaleReceiptTransformer.ts @@ -12,6 +12,7 @@ export class SaleReceiptTransformer extends Transformer { */ public includeAttributes = (): string[] => { return [ + 'formattedSubtotal', 'formattedAmount', 'formattedReceiptDate', 'formattedClosedAtDate', @@ -37,6 +38,15 @@ export class SaleReceiptTransformer extends Transformer { return this.formatDate(receipt.closedAt); }; + /** + * Retrieves the estimate formatted subtotal. + * @param {ISaleReceipt} receipt + * @returns {string} + */ + protected formattedSubtotal = (receipt: ISaleReceipt): string => { + return formatNumber(receipt.amount, { money: false }); + }; + /** * Retrieve formatted invoice amount. * @param {ISaleReceipt} estimate diff --git a/packages/webapp/src/containers/Drawers/CreditNoteDetailDrawer/CreditNoteDetailTableFooter.tsx b/packages/webapp/src/containers/Drawers/CreditNoteDetailDrawer/CreditNoteDetailTableFooter.tsx index 66ecf1b66..6171686cf 100644 --- a/packages/webapp/src/containers/Drawers/CreditNoteDetailDrawer/CreditNoteDetailTableFooter.tsx +++ b/packages/webapp/src/containers/Drawers/CreditNoteDetailDrawer/CreditNoteDetailTableFooter.tsx @@ -1,12 +1,9 @@ // @ts-nocheck -import React from 'react'; import styled from 'styled-components'; - import { T, TotalLines, TotalLine, - FormatNumber, TotalLineBorderStyle, TotalLineTextStyle, } from '@/components'; @@ -23,7 +20,7 @@ export default function CreditNoteDetailTableFooter() { } - value={} + value={creditNote.formatted_subtotal} /> } diff --git a/packages/webapp/src/containers/Drawers/EstimateDetailDrawer/EstimateDetailTableFooter.tsx b/packages/webapp/src/containers/Drawers/EstimateDetailDrawer/EstimateDetailTableFooter.tsx index c5a79c935..8bc3ee96a 100644 --- a/packages/webapp/src/containers/Drawers/EstimateDetailDrawer/EstimateDetailTableFooter.tsx +++ b/packages/webapp/src/containers/Drawers/EstimateDetailDrawer/EstimateDetailTableFooter.tsx @@ -8,7 +8,6 @@ import { TotalLine, TotalLineBorderStyle, TotalLineTextStyle, - FormatNumber, } from '@/components'; import { useEstimateDetailDrawerContext } from './EstimateDetailDrawerProvider'; @@ -23,7 +22,7 @@ export default function EstimateDetailTableFooter() { } - value={} + value={estimate.formatted_subtotal} borderStyle={TotalLineBorderStyle.SingleDark} /> { }, { Header: intl.get('quantity'), - accessor: 'quantity', + accessor: 'quantity_formatted', Cell: FormatNumberCell, - width: getColumnWidth(entries, 'quantity', { + width: getColumnWidth(entries, 'quantity_formatted', { minWidth: 60, magicSpacing: 5, }), @@ -59,7 +59,6 @@ export const useEstimateReadonlyEntriesColumns = () => { { Header: intl.get('amount'), accessor: 'total_formatted', - Cell: FormatNumberCell, width: getColumnWidth(entries, 'total_formatted', { minWidth: 60, magicSpacing: 5, diff --git a/packages/webapp/src/containers/Drawers/ReceiptDetailDrawer/ReceiptDetailTableFooter.tsx b/packages/webapp/src/containers/Drawers/ReceiptDetailDrawer/ReceiptDetailTableFooter.tsx index 657606f17..b5687a091 100644 --- a/packages/webapp/src/containers/Drawers/ReceiptDetailDrawer/ReceiptDetailTableFooter.tsx +++ b/packages/webapp/src/containers/Drawers/ReceiptDetailDrawer/ReceiptDetailTableFooter.tsx @@ -23,7 +23,7 @@ export default function ReceiptDetailTableFooter() { } - value={receipt.formatted_amount} + value={receipt.formatted_subtotal} /> } diff --git a/packages/webapp/src/containers/Drawers/VendorCreditDetailDrawer/JournalEntriesTransactions/components.tsx b/packages/webapp/src/containers/Drawers/VendorCreditDetailDrawer/JournalEntriesTransactions/components.tsx index 247d21a46..99c8101de 100644 --- a/packages/webapp/src/containers/Drawers/VendorCreditDetailDrawer/JournalEntriesTransactions/components.tsx +++ b/packages/webapp/src/containers/Drawers/VendorCreditDetailDrawer/JournalEntriesTransactions/components.tsx @@ -13,7 +13,6 @@ export const useJournalEntriesTransactionsColumns = () => { () => [ { Header: intl.get('date'), - accessor: 'date', accessor: 'formatted_date', Cell: FormatDateCell, width: 140, @@ -34,15 +33,17 @@ export const useJournalEntriesTransactionsColumns = () => { }, { Header: intl.get('credit'), - accessor: ({ credit }) => credit.formatted_amount, + accessor: 'credit.formatted_amount', width: 100, className: 'credit', + align: 'right', }, { Header: intl.get('debit'), - accessor: ({ debit }) => debit.formatted_amount, + accessor: 'debit.formatted_amount', width: 100, className: 'debit', + align: 'right', }, ], [], diff --git a/packages/webapp/src/containers/Drawers/VendorCreditDetailDrawer/VendorCreditDetailDrawerFooter.tsx b/packages/webapp/src/containers/Drawers/VendorCreditDetailDrawer/VendorCreditDetailDrawerFooter.tsx index e11cbce67..0bfd435e9 100644 --- a/packages/webapp/src/containers/Drawers/VendorCreditDetailDrawer/VendorCreditDetailDrawerFooter.tsx +++ b/packages/webapp/src/containers/Drawers/VendorCreditDetailDrawer/VendorCreditDetailDrawerFooter.tsx @@ -8,7 +8,6 @@ import { TotalLine, TotalLineBorderStyle, TotalLineTextStyle, - FormatNumber, } from '@/components'; import { useVendorCreditDetailDrawerContext } from './VendorCreditDetailDrawerProvider'; @@ -23,7 +22,7 @@ export default function VendorCreditDetailDrawerFooter() { } - value={vendorCredit.formatted_amount} + value={vendorCredit.formatted_subtotal} borderStyle={TotalLineBorderStyle.SingleDark} /> { }, { Header: intl.get('quantity'), - accessor: 'quantity', - Cell: FormatNumberCell, - width: getColumnWidth(entries, 'quantity', { + accessor: 'quantity_formatted', + width: getColumnWidth(entries, 'quantity_formatted', { minWidth: 60, magicSpacing: 5, }), From 0c61f85707c83c4ebd6e6cd4866bc147034685d6 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Tue, 6 Feb 2024 20:38:25 +0200 Subject: [PATCH 22/39] chore: remove format number from estimate quantity --- .../src/containers/Drawers/EstimateDetailDrawer/utils.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/webapp/src/containers/Drawers/EstimateDetailDrawer/utils.tsx b/packages/webapp/src/containers/Drawers/EstimateDetailDrawer/utils.tsx index 84acab544..eb9def4e6 100644 --- a/packages/webapp/src/containers/Drawers/EstimateDetailDrawer/utils.tsx +++ b/packages/webapp/src/containers/Drawers/EstimateDetailDrawer/utils.tsx @@ -2,7 +2,7 @@ import React from 'react'; import intl from 'react-intl-universal'; import { getColumnWidth } from '@/utils'; -import { FormatNumberCell, TextOverviewTooltipCell } from '@/components'; +import { TextOverviewTooltipCell } from '@/components'; import { useEstimateDetailDrawerContext } from './EstimateDetailDrawerProvider'; /** @@ -36,7 +36,6 @@ export const useEstimateReadonlyEntriesColumns = () => { { Header: intl.get('quantity'), accessor: 'quantity_formatted', - Cell: FormatNumberCell, width: getColumnWidth(entries, 'quantity_formatted', { minWidth: 60, magicSpacing: 5, From 17dbe9713b97267a350a9cd52076ed5ba91af857 Mon Sep 17 00:00:00 2001 From: "a.nasouf" Date: Sat, 10 Feb 2024 19:59:12 +0200 Subject: [PATCH 23/39] fix: remove normalizeEmail function --- packages/server/src/api/controllers/Contacts/Contacts.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/server/src/api/controllers/Contacts/Contacts.ts b/packages/server/src/api/controllers/Contacts/Contacts.ts index 751654a8e..24b99e09f 100644 --- a/packages/server/src/api/controllers/Contacts/Contacts.ts +++ b/packages/server/src/api/controllers/Contacts/Contacts.ts @@ -153,7 +153,6 @@ export default class ContactsController extends BaseController { check('email') .optional({ nullable: true }) .isString() - .normalizeEmail({ gmail_remove_dots: false }) .isEmail() .isLength({ max: DATATYPES_LENGTH.STRING }), check('website') From 9395ef094a4edcb1b265208bbfbeb56e2ade7b30 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Sat, 10 Feb 2024 22:20:54 +0200 Subject: [PATCH 24/39] feat(server): wip printing financial reports --- .../scss/modules/financial-sheet.scss | 41 ++++++++++++ .../views/modules/financial-sheet.pug | 21 +++++++ .../FinancialStatements/APAgingSummary.ts | 10 +++ .../FinancialStatements/ARAgingSummary.ts | 12 +++- .../FinancialStatements/BalanceSheet.ts | 10 +++ .../FinancialStatements/CashFlow/CashFlow.ts | 10 +++ .../FinancialStatements/JournalSheet.ts | 9 +++ .../FinancialStatements/ProfitLossSheet.ts | 9 +++ .../FinancialStatements/SalesByItems.ts | 11 +++- .../FinancialStatements/TrialBalanceSheet.ts | 13 +++- .../VendorBalanceSummary/index.ts | 12 ++++ .../AgingSummary/APAgingSummaryApplication.ts | 14 +++++ .../APAgingSummaryPdfInjectable.ts | 34 ++++++++++ .../AgingSummary/ARAgingSummaryApplication.ts | 14 +++++ .../ARAgingSummaryPdfInjectable.ts | 34 ++++++++++ .../BalanceSheet/BalanceSheetApplication.ts | 10 +++ .../BalanceSheetExportInjectable.ts | 17 +++++ .../BalanceSheet/BalanceSheetPdfInjectable.ts | 34 ++++++++++ .../BalanceSheet/BalanceSheetTable.ts | 4 +- .../CashFlow/CashflowSheetApplication.ts | 17 +++++ .../CashFlow/CashflowTablePdfInjectable.ts | 33 ++++++++++ .../CustomerBalanceSummaryApplication.ts | 14 +++++ .../CustomerBalanceSummaryPdf.ts | 34 ++++++++++ .../JournalSheet/JournalSheetApplication.ts | 14 +++++ .../JournalSheet/JournalSheetPdfInjectable.ts | 34 ++++++++++ .../ProfitLossSheetApplication.ts | 14 +++++ .../ProfitLossSheetExportInjectable.ts | 7 +++ .../ProfitLossTablePdfInjectable.ts | 34 ++++++++++ .../SalesByItems/SalesByItemsApplication.ts | 17 +++++ .../SalesByItems/SalesByItemsPdfInjectable.ts | 34 ++++++++++ .../FinancialStatements/TableSheetPdf.ts | 62 +++++++++++++++++++ .../TrialBalanceExportInjectable.ts | 19 +++++- .../TrialBalanceSheetApplication.ts | 10 +++ .../TrialBalanceSheetPdfInjectsable.ts | 34 ++++++++++ .../VendorBalanceSummaryApplication.ts | 14 +++++ .../VendorBalanceSummaryPdf.ts | 34 ++++++++++ 36 files changed, 738 insertions(+), 6 deletions(-) create mode 100644 packages/server/resources/scss/modules/financial-sheet.scss create mode 100644 packages/server/resources/views/modules/financial-sheet.pug create mode 100644 packages/server/src/services/FinancialStatements/AgingSummary/APAgingSummaryPdfInjectable.ts create mode 100644 packages/server/src/services/FinancialStatements/AgingSummary/ARAgingSummaryPdfInjectable.ts create mode 100644 packages/server/src/services/FinancialStatements/BalanceSheet/BalanceSheetPdfInjectable.ts create mode 100644 packages/server/src/services/FinancialStatements/CashFlow/CashflowTablePdfInjectable.ts create mode 100644 packages/server/src/services/FinancialStatements/CustomerBalanceSummary/CustomerBalanceSummaryPdf.ts create mode 100644 packages/server/src/services/FinancialStatements/JournalSheet/JournalSheetPdfInjectable.ts create mode 100644 packages/server/src/services/FinancialStatements/ProfitLossSheet/ProfitLossTablePdfInjectable.ts create mode 100644 packages/server/src/services/FinancialStatements/SalesByItems/SalesByItemsPdfInjectable.ts create mode 100644 packages/server/src/services/FinancialStatements/TableSheetPdf.ts create mode 100644 packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetPdfInjectsable.ts create mode 100644 packages/server/src/services/FinancialStatements/VendorBalanceSummary/VendorBalanceSummaryPdf.ts diff --git a/packages/server/resources/scss/modules/financial-sheet.scss b/packages/server/resources/scss/modules/financial-sheet.scss new file mode 100644 index 000000000..5924dc2a3 --- /dev/null +++ b/packages/server/resources/scss/modules/financial-sheet.scss @@ -0,0 +1,41 @@ + +.sheet{} +.sheet__company-name{ + margin: 0; + font-size: 1.6rem; +} +.sheet__sheet-type { + margin: 0 +} +.sheet__sheet-date { + margin-top: 0.5rem; +} + +.sheet__header { + text-align: center; + margin-bottom: 2rem; +} + +.sheet__table { + border-top: 1px solid #000; + table-layout: fixed; + border-spacing: 0; + text-align: left; +} + +.sheet__table thead th { + color: #000; + border-bottom: 1px solid #000000; + padding: 0.5rem; +} + +.sheet__table tbody td { + border-bottom: 0; + padding-top: 0.32rem; + padding-bottom: 0.32rem; + padding-left: 0.5rem; + padding-right: 0.5rem; + color: #252A31; + border-bottom: 1px solid rgb(37, 42, 49); + +} \ No newline at end of file diff --git a/packages/server/resources/views/modules/financial-sheet.pug b/packages/server/resources/views/modules/financial-sheet.pug new file mode 100644 index 000000000..019c74626 --- /dev/null +++ b/packages/server/resources/views/modules/financial-sheet.pug @@ -0,0 +1,21 @@ +block head + style + //- include ../../css/modules/financial-sheet.css + +block content + .sheet + .sheet__header + .sheet__company-name=organizationName + .sheet__sheet-type=sheetName + .sheet__sheet-date=sheetDate + + table.sheet__table + thead + tr + each column in table.columns + th= column.label + tbody + each row in table.rows + tr + each cell in row.cells + td= cell.value \ No newline at end of file diff --git a/packages/server/src/api/controllers/FinancialStatements/APAgingSummary.ts b/packages/server/src/api/controllers/FinancialStatements/APAgingSummary.ts index 5d626896c..d87873a2b 100644 --- a/packages/server/src/api/controllers/FinancialStatements/APAgingSummary.ts +++ b/packages/server/src/api/controllers/FinancialStatements/APAgingSummary.ts @@ -71,6 +71,7 @@ export default class APAgingSummaryReportController extends BaseFinancialReportC ACCEPT_TYPE.APPLICATION_JSON_TABLE, ACCEPT_TYPE.APPLICATION_CSV, ACCEPT_TYPE.APPLICATION_XLSX, + ACCEPT_TYPE.APPLICATION_PDF ]); // Retrieves the json table format. if (ACCEPT_TYPE.APPLICATION_JSON_TABLE === acceptType) { @@ -98,6 +99,15 @@ export default class APAgingSummaryReportController extends BaseFinancialReportC 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ); return res.send(buffer); + // Retrieves the pdf format. + } else if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) { + const pdfContent = await this.APAgingSummaryApp.pdf(tenantId, filter); + + res.set({ + 'Content-Type': 'application/pdf', + 'Content-Length': pdfContent.length, + }); + return res.send(pdfContent); // Retrieves the json format. } else { const sheet = await this.APAgingSummaryApp.sheet(tenantId, filter); diff --git a/packages/server/src/api/controllers/FinancialStatements/ARAgingSummary.ts b/packages/server/src/api/controllers/FinancialStatements/ARAgingSummary.ts index 10e42e900..86b4b920a 100644 --- a/packages/server/src/api/controllers/FinancialStatements/ARAgingSummary.ts +++ b/packages/server/src/api/controllers/FinancialStatements/ARAgingSummary.ts @@ -11,7 +11,7 @@ import { ACCEPT_TYPE } from '@/interfaces/Http'; @Service() export default class ARAgingSummaryReportController extends BaseFinancialReportController { @Inject() - ARAgingSummaryApp: ARAgingSummaryApplication; + private ARAgingSummaryApp: ARAgingSummaryApplication; /** * Router constructor. @@ -69,6 +69,7 @@ export default class ARAgingSummaryReportController extends BaseFinancialReportC ACCEPT_TYPE.APPLICATION_JSON_TABLE, ACCEPT_TYPE.APPLICATION_CSV, ACCEPT_TYPE.APPLICATION_XLSX, + ACCEPT_TYPE.APPLICATION_PDF ]); // Retrieves the xlsx format. if (ACCEPT_TYPE.APPLICATION_XLSX === acceptType) { @@ -96,6 +97,15 @@ export default class ARAgingSummaryReportController extends BaseFinancialReportC res.setHeader('Content-Type', 'text/csv'); return res.send(buffer); + // Retrieves the pdf format. + } else if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) { + const pdfContent = await this.ARAgingSummaryApp.pdf(tenantId, filter); + + res.set({ + 'Content-Type': 'application/pdf', + 'Content-Length': pdfContent.length, + }); + return res.send(pdfContent); // Retrieves the json format. } else { const sheet = await this.ARAgingSummaryApp.sheet(tenantId, filter); diff --git a/packages/server/src/api/controllers/FinancialStatements/BalanceSheet.ts b/packages/server/src/api/controllers/FinancialStatements/BalanceSheet.ts index 0af53d723..fda717a38 100644 --- a/packages/server/src/api/controllers/FinancialStatements/BalanceSheet.ts +++ b/packages/server/src/api/controllers/FinancialStatements/BalanceSheet.ts @@ -101,6 +101,7 @@ export default class BalanceSheetStatementController extends BaseFinancialReport ACCEPT_TYPE.APPLICATION_JSON_TABLE, ACCEPT_TYPE.APPLICATION_XLSX, ACCEPT_TYPE.APPLICATION_CSV, + ACCEPT_TYPE.APPLICATION_PDF, ]); // Retrieves the json table format. if (ACCEPT_TYPE.APPLICATION_JSON_TABLE == acceptType) { @@ -128,6 +129,15 @@ export default class BalanceSheetStatementController extends BaseFinancialReport 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ); return res.send(buffer); + // Retrieves the pdf format. + } else if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) { + const pdfContent = await this.balanceSheetApp.pdf(tenantId, filter); + + res.set({ + 'Content-Type': 'application/pdf', + 'Content-Length': pdfContent.length, + }); + res.send(pdfContent); } else { const sheet = await this.balanceSheetApp.sheet(tenantId, filter); diff --git a/packages/server/src/api/controllers/FinancialStatements/CashFlow/CashFlow.ts b/packages/server/src/api/controllers/FinancialStatements/CashFlow/CashFlow.ts index bab04246d..bc24b5379 100644 --- a/packages/server/src/api/controllers/FinancialStatements/CashFlow/CashFlow.ts +++ b/packages/server/src/api/controllers/FinancialStatements/CashFlow/CashFlow.ts @@ -79,6 +79,7 @@ export default class CashFlowController extends BaseFinancialReportController { ACCEPT_TYPE.APPLICATION_JSON_TABLE, ACCEPT_TYPE.APPLICATION_CSV, ACCEPT_TYPE.APPLICATION_XLSX, + ACCEPT_TYPE.APPLICATION_PDF ]); // Retrieves the json table format. if (ACCEPT_TYPE.APPLICATION_JSON_TABLE === acceptType) { @@ -106,6 +107,15 @@ export default class CashFlowController extends BaseFinancialReportController { 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ); return res.send(buffer); + // Retrieves the pdf format. + } else if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) { + const pdfContent = await this.cashflowSheetApp.pdf(tenantId, filter); + + res.set({ + 'Content-Type': 'application/pdf', + 'Content-Length': pdfContent.length, + }); + return res.send(pdfContent); // Retrieves the json format. } else { const cashflow = await this.cashflowSheetApp.sheet(tenantId, filter); diff --git a/packages/server/src/api/controllers/FinancialStatements/JournalSheet.ts b/packages/server/src/api/controllers/FinancialStatements/JournalSheet.ts index 871bc9af8..0355766db 100644 --- a/packages/server/src/api/controllers/FinancialStatements/JournalSheet.ts +++ b/packages/server/src/api/controllers/FinancialStatements/JournalSheet.ts @@ -72,6 +72,7 @@ export default class JournalSheetController extends BaseFinancialReportControlle ACCEPT_TYPE.APPLICATION_JSON_TABLE, ACCEPT_TYPE.APPLICATION_XLSX, ACCEPT_TYPE.APPLICATION_CSV, + ACCEPT_TYPE.APPLICATION_PDF, ]); // Retrieves the json table format. @@ -97,6 +98,14 @@ export default class JournalSheetController extends BaseFinancialReportControlle ); return res.send(buffer); // Retrieves the json format. + } else if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) { + const pdfContent = await this.journalSheetApp.pdf(tenantId, filter); + + res.set({ + 'Content-Type': 'application/pdf', + 'Content-Length': pdfContent.length, + }); + res.send(pdfContent); } else { const sheet = await this.journalSheetApp.sheet(tenantId, filter); diff --git a/packages/server/src/api/controllers/FinancialStatements/ProfitLossSheet.ts b/packages/server/src/api/controllers/FinancialStatements/ProfitLossSheet.ts index 8c2404335..995df07b4 100644 --- a/packages/server/src/api/controllers/FinancialStatements/ProfitLossSheet.ts +++ b/packages/server/src/api/controllers/FinancialStatements/ProfitLossSheet.ts @@ -96,6 +96,7 @@ export default class ProfitLossSheetController extends BaseFinancialReportContro ACCEPT_TYPE.APPLICATION_JSON_TABLE, ACCEPT_TYPE.APPLICATION_CSV, ACCEPT_TYPE.APPLICATION_XLSX, + ACCEPT_TYPE.APPLICATION_PDF, ]); try { // Retrieves the csv format. @@ -125,6 +126,14 @@ export default class ProfitLossSheetController extends BaseFinancialReportContro ); return res.send(sheet); // Retrieves the json format. + } else if (acceptType === ACCEPT_TYPE.APPLICATION_PDF) { + const pdfContent = await this.profitLossSheetApp.pdf(tenantId, filter); + + res.set({ + 'Content-Type': 'application/pdf', + 'Content-Length': pdfContent.length, + }); + return res.send(pdfContent); } else { const sheet = await this.profitLossSheetApp.sheet(tenantId, filter); diff --git a/packages/server/src/api/controllers/FinancialStatements/SalesByItems.ts b/packages/server/src/api/controllers/FinancialStatements/SalesByItems.ts index bac67231c..28ab611c0 100644 --- a/packages/server/src/api/controllers/FinancialStatements/SalesByItems.ts +++ b/packages/server/src/api/controllers/FinancialStatements/SalesByItems.ts @@ -11,7 +11,7 @@ import { SalesByItemsApplication } from '@/services/FinancialStatements/SalesByI @Service() export default class SalesByItemsReportController extends BaseFinancialReportController { @Inject() - salesByItemsApp: SalesByItemsApplication; + private salesByItemsApp: SalesByItemsApplication; /** * Router constructor. @@ -71,6 +71,7 @@ export default class SalesByItemsReportController extends BaseFinancialReportCon ACCEPT_TYPE.APPLICATION_JSON_TABLE, ACCEPT_TYPE.APPLICATION_CSV, ACCEPT_TYPE.APPLICATION_XLSX, + ACCEPT_TYPE.APPLICATION_PDF, ]); // Retrieves the csv format. if (ACCEPT_TYPE.APPLICATION_CSV === acceptType) { @@ -96,6 +97,14 @@ export default class SalesByItemsReportController extends BaseFinancialReportCon ); return res.send(buffer); // Retrieves the json format. + } else if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) { + const pdfContent = await this.salesByItemsApp.pdf(tenantId, filter); + + res.set({ + 'Content-Type': 'application/pdf', + 'Content-Length': pdfContent.length, + }); + return res.send(pdfContent); } else { const sheet = await this.salesByItemsApp.sheet(tenantId, filter); return res.status(200).send(sheet); diff --git a/packages/server/src/api/controllers/FinancialStatements/TrialBalanceSheet.ts b/packages/server/src/api/controllers/FinancialStatements/TrialBalanceSheet.ts index ce23c1071..b92e355a2 100644 --- a/packages/server/src/api/controllers/FinancialStatements/TrialBalanceSheet.ts +++ b/packages/server/src/api/controllers/FinancialStatements/TrialBalanceSheet.ts @@ -3,7 +3,6 @@ import { Request, Response, Router, NextFunction } from 'express'; import { query, ValidationChain } from 'express-validator'; import { castArray } from 'lodash'; import asyncMiddleware from '@/api/middleware/asyncMiddleware'; -import TrialBalanceSheetService from '@/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetInjectable'; import BaseFinancialReportController from './BaseFinancialReportController'; import { AbilitySubject, ReportsAction } from '@/interfaces'; import CheckPolicies from '@/api/middleware/CheckPolicies'; @@ -81,6 +80,7 @@ export default class TrialBalanceSheetController extends BaseFinancialReportCont ACCEPT_TYPE.APPLICATION_JSON_TABLE, ACCEPT_TYPE.APPLICATION_CSV, ACCEPT_TYPE.APPLICATION_XLSX, + ACCEPT_TYPE.APPLICATION_PDF, ]); // Retrieves in json table format. if (acceptType === ACCEPT_TYPE.APPLICATION_JSON_TABLE) { @@ -109,6 +109,17 @@ export default class TrialBalanceSheetController extends BaseFinancialReportCont res.setHeader('Content-Type', 'text/csv'); return res.send(buffer); + // Retrieves in pdf format. + } else if (acceptType === ACCEPT_TYPE.APPLICATION_PDF) { + const pdfContent = await this.trialBalanceSheetApp.pdf( + tenantId, + filter + ); + res.set({ + 'Content-Type': 'application/pdf', + 'Content-Length': pdfContent.length, + }); + res.send(pdfContent); // Retrieves in json format. } else { const { data, query, meta } = await this.trialBalanceSheetApp.sheet( diff --git a/packages/server/src/api/controllers/FinancialStatements/VendorBalanceSummary/index.ts b/packages/server/src/api/controllers/FinancialStatements/VendorBalanceSummary/index.ts index ade69cb62..f1e26a0ed 100644 --- a/packages/server/src/api/controllers/FinancialStatements/VendorBalanceSummary/index.ts +++ b/packages/server/src/api/controllers/FinancialStatements/VendorBalanceSummary/index.ts @@ -72,6 +72,7 @@ export default class VendorBalanceSummaryReportController extends BaseFinancialR ACCEPT_TYPE.APPLICATION_JSON_TABLE, ACCEPT_TYPE.APPLICATION_CSV, ACCEPT_TYPE.APPLICATION_XLSX, + ACCEPT_TYPE.APPLICATION_PDF, ]); // Retrieves the csv format. @@ -100,6 +101,17 @@ export default class VendorBalanceSummaryReportController extends BaseFinancialR filter ); return res.status(200).send(table); + // Retrieves the pdf format. + } else if (acceptType === ACCEPT_TYPE.APPLICATION_PDF) { + const pdfContent = await this.vendorBalanceSummaryApp.pdf( + tenantId, + filter + ); + res.set({ + 'Content-Type': 'application/pdf', + 'Content-Length': pdfContent.length, + }); + return res.send(pdfContent); // Retrieves the json format. } else { const sheet = await this.vendorBalanceSummaryApp.sheet( diff --git a/packages/server/src/services/FinancialStatements/AgingSummary/APAgingSummaryApplication.ts b/packages/server/src/services/FinancialStatements/AgingSummary/APAgingSummaryApplication.ts index 73c08b2d8..52e6db251 100644 --- a/packages/server/src/services/FinancialStatements/AgingSummary/APAgingSummaryApplication.ts +++ b/packages/server/src/services/FinancialStatements/AgingSummary/APAgingSummaryApplication.ts @@ -3,6 +3,7 @@ import { APAgingSummaryExportInjectable } from './APAgingSummaryExportInjectable import { APAgingSummaryTableInjectable } from './APAgingSummaryTableInjectable'; import { IAPAgingSummaryQuery } from '@/interfaces'; import { APAgingSummaryService } from './APAgingSummaryService'; +import { APAgingSummaryPdfInjectable } from './APAgingSummaryPdfInjectable'; @Service() export class APAgingSummaryApplication { @@ -15,6 +16,9 @@ export class APAgingSummaryApplication { @Inject() private APAgingSummarySheet: APAgingSummaryService; + @Inject() + private APAgingSumaryPdf: APAgingSummaryPdfInjectable; + /** * Retrieve the A/P aging summary in sheet format. * @param {number} tenantId @@ -50,4 +54,14 @@ export class APAgingSummaryApplication { public xlsx(tenantId: number, query: IAPAgingSummaryQuery) { return this.APAgingSummaryExport.xlsx(tenantId, query); } + + /** + * Retrieves the A/P aging summary in pdf format. + * @param {number} tenantId + * @param {IAPAgingSummaryQuery} query + * @returns {Promise} + */ + public pdf(tenantId: number, query: IAPAgingSummaryQuery) { + return this.APAgingSumaryPdf.pdf(tenantId, query); + } } diff --git a/packages/server/src/services/FinancialStatements/AgingSummary/APAgingSummaryPdfInjectable.ts b/packages/server/src/services/FinancialStatements/AgingSummary/APAgingSummaryPdfInjectable.ts new file mode 100644 index 000000000..e1a74f80a --- /dev/null +++ b/packages/server/src/services/FinancialStatements/AgingSummary/APAgingSummaryPdfInjectable.ts @@ -0,0 +1,34 @@ +import { Inject, Service } from 'typedi'; +import { IAPAgingSummaryQuery } from '@/interfaces'; +import { TableSheetPdf } from '../TableSheetPdf'; +import { APAgingSummaryTableInjectable } from './APAgingSummaryTableInjectable'; + +@Service() +export class APAgingSummaryPdfInjectable { + @Inject() + private APAgingSummaryTable: APAgingSummaryTableInjectable; + + @Inject() + private tableSheetPdf: TableSheetPdf; + + /** + * Converts the given A/P aging summary sheet table to pdf. + * @param {number} tenantId - Tenant ID. + * @param {IAPAgingSummaryQuery} query - Balance sheet query. + * @returns {Promise} + */ + public async pdf( + tenantId: number, + query: IAPAgingSummaryQuery + ): Promise { + const table = await this.APAgingSummaryTable.table(tenantId, query); + const sheetName = 'AR Aging Summary'; + + return this.tableSheetPdf.convertToPdf( + tenantId, + table.table, + sheetName, + table.meta.baseCurrency + ); + } +} diff --git a/packages/server/src/services/FinancialStatements/AgingSummary/ARAgingSummaryApplication.ts b/packages/server/src/services/FinancialStatements/AgingSummary/ARAgingSummaryApplication.ts index d3282ca4b..f24932f13 100644 --- a/packages/server/src/services/FinancialStatements/AgingSummary/ARAgingSummaryApplication.ts +++ b/packages/server/src/services/FinancialStatements/AgingSummary/ARAgingSummaryApplication.ts @@ -3,6 +3,7 @@ import { IARAgingSummaryQuery } from '@/interfaces'; import { ARAgingSummaryTableInjectable } from './ARAgingSummaryTableInjectable'; import { ARAgingSummaryExportInjectable } from './ARAgingSummaryExportInjectable'; import ARAgingSummaryService from './ARAgingSummaryService'; +import { ARAgingSummaryPdfInjectable } from './ARAgingSummaryPdfInjectable'; @Service() export class ARAgingSummaryApplication { @@ -15,6 +16,9 @@ export class ARAgingSummaryApplication { @Inject() private ARAgingSummarySheet: ARAgingSummaryService; + @Inject() + private ARAgingSummaryPdf: ARAgingSummaryPdfInjectable; + /** * Retrieve the A/R aging summary sheet. * @param {number} tenantId @@ -50,4 +54,14 @@ export class ARAgingSummaryApplication { public csv(tenantId: number, query: IARAgingSummaryQuery) { return this.ARAgingSummaryExport.csv(tenantId, query); } + + /** + * Retrieves the A/R aging summary in pdf format. + * @param {number} tenantId + * @param {IARAgingSummaryQuery} query + * @returns {Promise} + */ + public pdf(tenantId: number, query: IARAgingSummaryQuery) { + return this.ARAgingSummaryPdf.pdf(tenantId, query); + } } diff --git a/packages/server/src/services/FinancialStatements/AgingSummary/ARAgingSummaryPdfInjectable.ts b/packages/server/src/services/FinancialStatements/AgingSummary/ARAgingSummaryPdfInjectable.ts new file mode 100644 index 000000000..57ea3fef5 --- /dev/null +++ b/packages/server/src/services/FinancialStatements/AgingSummary/ARAgingSummaryPdfInjectable.ts @@ -0,0 +1,34 @@ +import { Inject, Service } from 'typedi'; +import { IARAgingSummaryQuery } from '@/interfaces'; +import { TableSheetPdf } from '../TableSheetPdf'; +import { ARAgingSummaryTableInjectable } from './ARAgingSummaryTableInjectable'; + +@Service() +export class ARAgingSummaryPdfInjectable { + @Inject() + private ARAgingSummaryTable: ARAgingSummaryTableInjectable; + + @Inject() + private tableSheetPdf: TableSheetPdf; + + /** + * Converts the given balance sheet table to pdf. + * @param {number} tenantId - Tenant ID. + * @param {IBalanceSheetQuery} query - Balance sheet query. + * @returns {Promise} + */ + public async pdf( + tenantId: number, + query: IARAgingSummaryQuery + ): Promise { + const table = await this.ARAgingSummaryTable.table(tenantId, query); + const sheetName = 'AR Aging Summary'; + + return this.tableSheetPdf.convertToPdf( + tenantId, + table.table, + sheetName, + table.meta.baseCurrency + ); + } +} diff --git a/packages/server/src/services/FinancialStatements/BalanceSheet/BalanceSheetApplication.ts b/packages/server/src/services/FinancialStatements/BalanceSheet/BalanceSheetApplication.ts index 01ab77bfe..0c965a5f5 100644 --- a/packages/server/src/services/FinancialStatements/BalanceSheet/BalanceSheetApplication.ts +++ b/packages/server/src/services/FinancialStatements/BalanceSheet/BalanceSheetApplication.ts @@ -54,4 +54,14 @@ export class BalanceSheetApplication { public csv(tenantId: number, query: IBalanceSheetQuery): Promise { return this.balanceSheetExport.csv(tenantId, query); } + + /** + * Retrieves the balance sheet in pdf format. + * @param {number} tenantId + * @param {IBalanceSheetQuery} query + * @returns {Promise} + */ + public pdf(tenantId: number, query: IBalanceSheetQuery) { + return this.balanceSheetExport.pdf(tenantId, query); + } } diff --git a/packages/server/src/services/FinancialStatements/BalanceSheet/BalanceSheetExportInjectable.ts b/packages/server/src/services/FinancialStatements/BalanceSheet/BalanceSheetExportInjectable.ts index 2c43d5f80..9198d8536 100644 --- a/packages/server/src/services/FinancialStatements/BalanceSheet/BalanceSheetExportInjectable.ts +++ b/packages/server/src/services/FinancialStatements/BalanceSheet/BalanceSheetExportInjectable.ts @@ -2,12 +2,16 @@ import { Inject, Service } from 'typedi'; import { BalanceSheetTableInjectable } from './BalanceSheetTableInjectable'; import { TableSheet } from '@/lib/Xlsx/TableSheet'; import { IBalanceSheetQuery } from '@/interfaces'; +import { BalanceSheetPdfInjectable } from './BalanceSheetPdfInjectable'; @Service() export class BalanceSheetExportInjectable { @Inject() private balanceSheetTable: BalanceSheetTableInjectable; + @Inject() + private balanceSheetPdf: BalanceSheetPdfInjectable; + /** * Retrieves the trial balance sheet in XLSX format. * @param {number} tenantId @@ -40,4 +44,17 @@ export class BalanceSheetExportInjectable { return tableCsv; } + + /** + * Retrieves the balance sheet in pdf format. + * @param {number} tenantId + * @param {IBalanceSheetQuery} query + * @returns {Promise} + */ + public async pdf( + tenantId: number, + query: IBalanceSheetQuery + ): Promise { + return this.balanceSheetPdf.pdf(tenantId, query); + } } diff --git a/packages/server/src/services/FinancialStatements/BalanceSheet/BalanceSheetPdfInjectable.ts b/packages/server/src/services/FinancialStatements/BalanceSheet/BalanceSheetPdfInjectable.ts new file mode 100644 index 000000000..44fd991b3 --- /dev/null +++ b/packages/server/src/services/FinancialStatements/BalanceSheet/BalanceSheetPdfInjectable.ts @@ -0,0 +1,34 @@ +import { Inject, Service } from 'typedi'; +import { IBalanceSheetQuery } from '@/interfaces'; +import { BalanceSheetTableInjectable } from './BalanceSheetTableInjectable'; +import { TableSheetPdf } from '../TableSheetPdf'; + +@Service() +export class BalanceSheetPdfInjectable { + @Inject() + private balanceSheetTable: BalanceSheetTableInjectable; + + @Inject() + private tableSheetPdf: TableSheetPdf; + + /** + * Converts the given balance sheet table to pdf. + * @param {number} tenantId - Tenant ID. + * @param {IBalanceSheetQuery} query - Balance sheet query. + * @returns {Promise} + */ + public async pdf( + tenantId: number, + query: IBalanceSheetQuery + ): Promise { + const table = await this.balanceSheetTable.table(tenantId, query); + const sheetName = 'Balance Sheet'; + + return this.tableSheetPdf.convertToPdf( + tenantId, + table.table, + sheetName, + table.meta.baseCurrency + ); + } +} diff --git a/packages/server/src/services/FinancialStatements/BalanceSheet/BalanceSheetTable.ts b/packages/server/src/services/FinancialStatements/BalanceSheet/BalanceSheetTable.ts index 349b1a8f4..088362766 100644 --- a/packages/server/src/services/FinancialStatements/BalanceSheet/BalanceSheetTable.ts +++ b/packages/server/src/services/FinancialStatements/BalanceSheet/BalanceSheetTable.ts @@ -43,13 +43,13 @@ export default class BalanceSheetTable extends R.compose( /** * @param {} */ - reportData: IBalanceSheetStatementData; + private reportData: IBalanceSheetStatementData; /** * Balance sheet query. * @parma {} */ - query: BalanceSheetQuery; + private query: BalanceSheetQuery; /** * Constructor method. diff --git a/packages/server/src/services/FinancialStatements/CashFlow/CashflowSheetApplication.ts b/packages/server/src/services/FinancialStatements/CashFlow/CashflowSheetApplication.ts index 0fd8b7357..72a587f52 100644 --- a/packages/server/src/services/FinancialStatements/CashFlow/CashflowSheetApplication.ts +++ b/packages/server/src/services/FinancialStatements/CashFlow/CashflowSheetApplication.ts @@ -3,6 +3,7 @@ import { CashflowExportInjectable } from './CashflowExportInjectable'; import { ICashFlowStatementQuery } from '@/interfaces'; import CashFlowStatementService from './CashFlowService'; import { CashflowTableInjectable } from './CashflowTableInjectable'; +import { CashflowTablePdfInjectable } from './CashflowTablePdfInjectable'; @Service() export class CashflowSheetApplication { @@ -15,6 +16,9 @@ export class CashflowSheetApplication { @Inject() private cashflowTable: CashflowTableInjectable; + @Inject() + private cashflowPdf: CashflowTablePdfInjectable; + /** * Retrieves the cashflow sheet * @param {number} tenantId @@ -55,4 +59,17 @@ export class CashflowSheetApplication { ): Promise { return this.cashflowExport.csv(tenantId, query); } + + /** + * Retrieves the cashflow sheet in pdf format. + * @param {number} tenantId + * @param {ICashFlowStatementQuery} query + * @returns {Promise} + */ + public async pdf( + tenantId: number, + query: ICashFlowStatementQuery + ): Promise { + return this.cashflowPdf.pdf(tenantId, query); + } } diff --git a/packages/server/src/services/FinancialStatements/CashFlow/CashflowTablePdfInjectable.ts b/packages/server/src/services/FinancialStatements/CashFlow/CashflowTablePdfInjectable.ts new file mode 100644 index 000000000..eaab60db4 --- /dev/null +++ b/packages/server/src/services/FinancialStatements/CashFlow/CashflowTablePdfInjectable.ts @@ -0,0 +1,33 @@ +import { Inject } from 'typedi'; +import { CashflowTableInjectable } from './CashflowTableInjectable'; +import { TableSheetPdf } from '../TableSheetPdf'; +import { ICashFlowStatementQuery } from '@/interfaces'; + +export class CashflowTablePdfInjectable { + @Inject() + private cashflowTable: CashflowTableInjectable; + + @Inject() + private tableSheetPdf: TableSheetPdf; + + /** + * Converts the given cashflow sheet table to pdf. + * @param {number} tenantId - Tenant ID. + * @param {IBalanceSheetQuery} query - Balance sheet query. + * @returns {Promise} + */ + public async pdf( + tenantId: number, + query: ICashFlowStatementQuery + ): Promise { + const table = await this.cashflowTable.table(tenantId, query); + const sheetName = 'Cashflow Sheet'; + + return this.tableSheetPdf.convertToPdf( + tenantId, + table.table, + sheetName, + table.meta.baseCurrency + ); + } +} diff --git a/packages/server/src/services/FinancialStatements/CustomerBalanceSummary/CustomerBalanceSummaryApplication.ts b/packages/server/src/services/FinancialStatements/CustomerBalanceSummary/CustomerBalanceSummaryApplication.ts index 964cd91a9..a1693e7d8 100644 --- a/packages/server/src/services/FinancialStatements/CustomerBalanceSummary/CustomerBalanceSummaryApplication.ts +++ b/packages/server/src/services/FinancialStatements/CustomerBalanceSummary/CustomerBalanceSummaryApplication.ts @@ -3,6 +3,7 @@ import { CustomerBalanceSummaryExportInjectable } from './CustomerBalanceSummary import { CustomerBalanceSummaryTableInjectable } from './CustomerBalanceSummaryTableInjectable'; import { ICustomerBalanceSummaryQuery } from '@/interfaces'; import { CustomerBalanceSummaryService } from './CustomerBalanceSummaryService'; +import { CustomerBalanceSummaryPdf } from './CustomerBalanceSummaryPdf'; @Service() export class CustomerBalanceSummaryApplication { @@ -15,6 +16,9 @@ export class CustomerBalanceSummaryApplication { @Inject() private customerBalanceSummarySheet: CustomerBalanceSummaryService; + @Inject() + private customerBalanceSummaryPdf: CustomerBalanceSummaryPdf; + /** * Retrieves the customer balance sheet in json format. * @param {number} tenantId @@ -57,4 +61,14 @@ export class CustomerBalanceSummaryApplication { public csv(tenantId: number, query: ICustomerBalanceSummaryQuery) { return this.customerBalanceSummaryExport.csv(tenantId, query); } + + /** + * Retrieves the customer balance sheet in PDF format. + * @param {number} tenantId + * @param {ICustomerBalanceSummaryQuery} query + * @returns {Promise} + */ + public pdf(tenantId: number, query: ICustomerBalanceSummaryQuery) { + return this.customerBalanceSummaryPdf.pdf(tenantId, query); + } } diff --git a/packages/server/src/services/FinancialStatements/CustomerBalanceSummary/CustomerBalanceSummaryPdf.ts b/packages/server/src/services/FinancialStatements/CustomerBalanceSummary/CustomerBalanceSummaryPdf.ts new file mode 100644 index 000000000..67e74349b --- /dev/null +++ b/packages/server/src/services/FinancialStatements/CustomerBalanceSummary/CustomerBalanceSummaryPdf.ts @@ -0,0 +1,34 @@ +import { Inject, Service } from 'typedi'; +import { IAPAgingSummaryQuery, ICustomerBalanceSummaryQuery } from '@/interfaces'; +import { TableSheetPdf } from '../TableSheetPdf'; +import { CustomerBalanceSummaryTableInjectable } from './CustomerBalanceSummaryTableInjectable'; + +@Service() +export class CustomerBalanceSummaryPdf { + @Inject() + private customerBalanceSummaryTable: CustomerBalanceSummaryTableInjectable; + + @Inject() + private tableSheetPdf: TableSheetPdf; + + /** + * Converts the given A/P aging summary sheet table to pdf. + * @param {number} tenantId - Tenant ID. + * @param {IAPAgingSummaryQuery} query - Balance sheet query. + * @returns {Promise} + */ + public async pdf( + tenantId: number, + query: ICustomerBalanceSummaryQuery + ): Promise { + const table = await this.customerBalanceSummaryTable.table(tenantId, query); + const sheetName = 'Customer Balance Summary'; + + return this.tableSheetPdf.convertToPdf( + tenantId, + table.table, + sheetName, + table.meta.baseCurrency + ); + } +} diff --git a/packages/server/src/services/FinancialStatements/JournalSheet/JournalSheetApplication.ts b/packages/server/src/services/FinancialStatements/JournalSheet/JournalSheetApplication.ts index 4c403ff58..8d3f5d614 100644 --- a/packages/server/src/services/FinancialStatements/JournalSheet/JournalSheetApplication.ts +++ b/packages/server/src/services/FinancialStatements/JournalSheet/JournalSheetApplication.ts @@ -3,6 +3,7 @@ import { JournalSheetService } from './JournalSheetService'; import { JournalSheetTableInjectable } from './JournalSheetTableInjectable'; import { IJournalReportQuery, IJournalTable } from '@/interfaces'; import { JournalSheetExportInjectable } from './JournalSheetExport'; +import { JournalSheetPdfInjectable } from './JournalSheetPdfInjectable'; export class JournalSheetApplication { @Inject() @@ -14,6 +15,9 @@ export class JournalSheetApplication { @Inject() private journalExport: JournalSheetExportInjectable; + @Inject() + private journalPdf: JournalSheetPdfInjectable; + /** * Retrieves the journal sheet. * @param {number} tenantId @@ -56,4 +60,14 @@ export class JournalSheetApplication { public csv(tenantId: number, query: IJournalReportQuery) { return this.journalExport.csv(tenantId, query); } + + /** + * Retrieves the journal sheet in pdf format. + * @param {number} tenantId + * @param {IJournalReportQuery} query + * @returns {Promise} + */ + public pdf(tenantId: number, query: IJournalReportQuery) { + return this.journalPdf.pdf(tenantId, query); + } } diff --git a/packages/server/src/services/FinancialStatements/JournalSheet/JournalSheetPdfInjectable.ts b/packages/server/src/services/FinancialStatements/JournalSheet/JournalSheetPdfInjectable.ts new file mode 100644 index 000000000..e3fcbfb26 --- /dev/null +++ b/packages/server/src/services/FinancialStatements/JournalSheet/JournalSheetPdfInjectable.ts @@ -0,0 +1,34 @@ +import { IJournalReportQuery } from '@/interfaces'; +import { TableSheetPdf } from '../TableSheetPdf'; +import { JournalSheetTableInjectable } from './JournalSheetTableInjectable'; +import { Inject, Service } from 'typedi'; + +@Service() +export class JournalSheetPdfInjectable { + @Inject() + private journalSheetTable: JournalSheetTableInjectable; + + @Inject() + private tableSheetPdf: TableSheetPdf; + + /** + * Converts the given journal sheet table to pdf. + * @param {number} tenantId - Tenant ID. + * @param {IBalanceSheetQuery} query - Balance sheet query. + * @returns {Promise} + */ + public async pdf( + tenantId: number, + query: IJournalReportQuery + ): Promise { + const table = await this.journalSheetTable.table(tenantId, query); + const sheetName = 'Journal Sheet'; + + return this.tableSheetPdf.convertToPdf( + tenantId, + table.table, + sheetName, + table.meta.baseCurrency + ); + } +} diff --git a/packages/server/src/services/FinancialStatements/ProfitLossSheet/ProfitLossSheetApplication.ts b/packages/server/src/services/FinancialStatements/ProfitLossSheet/ProfitLossSheetApplication.ts index eee89e73a..6d15e2cc6 100644 --- a/packages/server/src/services/FinancialStatements/ProfitLossSheet/ProfitLossSheetApplication.ts +++ b/packages/server/src/services/FinancialStatements/ProfitLossSheet/ProfitLossSheetApplication.ts @@ -3,6 +3,7 @@ import { ProfitLossSheetExportInjectable } from './ProfitLossSheetExportInjectab import { ProfitLossSheetTableInjectable } from './ProfitLossSheetTableInjectable'; import { IProfitLossSheetQuery, IProfitLossSheetTable } from '@/interfaces'; import ProfitLossSheetService from './ProfitLossSheetService'; +import { ProfitLossTablePdfInjectable } from './ProfitLossTablePdfInjectable'; @Service() export class ProfitLossSheetApplication { @@ -15,6 +16,9 @@ export class ProfitLossSheetApplication { @Inject() private profitLossSheet: ProfitLossSheetService; + @Inject() + private profitLossPdf: ProfitLossTablePdfInjectable; + /** * Retreives the profit/loss sheet. * @param {number} tenantId @@ -57,4 +61,14 @@ export class ProfitLossSheetApplication { public xlsx(tenantId: number, query: IProfitLossSheetQuery): Promise { return this.profitLossExport.xlsx(tenantId, query); } + + /** + * Retrieves the profit/loss sheet in pdf format. + * @param {number} tenantId + * @param {IProfitLossSheetQuery} query + * @returns {Promise} + */ + public pdf(tenantId: number, query: IProfitLossSheetQuery): Promise { + return this.profitLossPdf.pdf(tenantId, query); + } } diff --git a/packages/server/src/services/FinancialStatements/ProfitLossSheet/ProfitLossSheetExportInjectable.ts b/packages/server/src/services/FinancialStatements/ProfitLossSheet/ProfitLossSheetExportInjectable.ts index ba2371797..4fa9762d3 100644 --- a/packages/server/src/services/FinancialStatements/ProfitLossSheet/ProfitLossSheetExportInjectable.ts +++ b/packages/server/src/services/FinancialStatements/ProfitLossSheet/ProfitLossSheetExportInjectable.ts @@ -40,4 +40,11 @@ export class ProfitLossSheetExportInjectable { return tableCsv; } + + public async pdf( + tenantId: number, + query: IProfitLossSheetQuery + ): Promise { + const table = await this.profitLossSheetTable.table(tenantId, query); + } } diff --git a/packages/server/src/services/FinancialStatements/ProfitLossSheet/ProfitLossTablePdfInjectable.ts b/packages/server/src/services/FinancialStatements/ProfitLossSheet/ProfitLossTablePdfInjectable.ts new file mode 100644 index 000000000..24b720b1a --- /dev/null +++ b/packages/server/src/services/FinancialStatements/ProfitLossSheet/ProfitLossTablePdfInjectable.ts @@ -0,0 +1,34 @@ +import { Inject, Service } from 'typedi'; +import { IProfitLossSheetQuery } from '@/interfaces'; +import { ProfitLossSheetTableInjectable } from './ProfitLossSheetTableInjectable'; +import { TableSheetPdf } from '../TableSheetPdf'; + +@Service() +export class ProfitLossTablePdfInjectable { + @Inject() + private profitLossTable: ProfitLossSheetTableInjectable; + + @Inject() + private tableSheetPdf: TableSheetPdf; + + /** + * Retrieves the profit/loss sheet in pdf format. + * @param {number} tenantId + * @param {number} query + * @returns {Promise} + */ + public async pdf( + tenantId: number, + query: IProfitLossSheetQuery + ): Promise { + const table = await this.profitLossTable.table(tenantId, query); + const sheetName = 'Profit & Loss Sheet'; + + return this.tableSheetPdf.convertToPdf( + tenantId, + table.table, + sheetName, + table.meta.baseCurrency + ); + } +} diff --git a/packages/server/src/services/FinancialStatements/SalesByItems/SalesByItemsApplication.ts b/packages/server/src/services/FinancialStatements/SalesByItems/SalesByItemsApplication.ts index b3b51869f..498e3eb66 100644 --- a/packages/server/src/services/FinancialStatements/SalesByItems/SalesByItemsApplication.ts +++ b/packages/server/src/services/FinancialStatements/SalesByItems/SalesByItemsApplication.ts @@ -8,6 +8,7 @@ import { import { SalesByItemsReportService } from './SalesByItemsService'; import { SalesByItemsTableInjectable } from './SalesByItemsTableInjectable'; import { SalesByItemsExport } from './SalesByItemsExport'; +import { SalesByItemsPdfInjectable } from './SalesByItemsPdfInjectable'; @Service() export class SalesByItemsApplication { @@ -20,6 +21,9 @@ export class SalesByItemsApplication { @Inject() private salesByItemsExport: SalesByItemsExport; + @Inject() + private salesByItemsPdf: SalesByItemsPdfInjectable; + /** * Retrieves the sales by items report in json format. * @param {number} tenantId @@ -71,4 +75,17 @@ export class SalesByItemsApplication { ): Promise { return this.salesByItemsExport.xlsx(tenantId, filter); } + + /** + * Retrieves the sales by items in pdf format. + * @param {number} tenantId + * @param {ISalesByItemsReportQuery} query + * @returns {Promise} + */ + public pdf( + tenantId: number, + query: ISalesByItemsReportQuery + ): Promise { + return this.salesByItemsPdf.pdf(tenantId, query); + } } diff --git a/packages/server/src/services/FinancialStatements/SalesByItems/SalesByItemsPdfInjectable.ts b/packages/server/src/services/FinancialStatements/SalesByItems/SalesByItemsPdfInjectable.ts new file mode 100644 index 000000000..6cf3d98ec --- /dev/null +++ b/packages/server/src/services/FinancialStatements/SalesByItems/SalesByItemsPdfInjectable.ts @@ -0,0 +1,34 @@ +import { Inject, Service } from 'typedi'; +import { ISalesByItemsReportQuery } from '@/interfaces'; +import { SalesByItemsTableInjectable } from './SalesByItemsTableInjectable'; +import { TableSheetPdf } from '../TableSheetPdf'; + +@Service() +export class SalesByItemsPdfInjectable { + @Inject() + private salesByItemsTable: SalesByItemsTableInjectable; + + @Inject() + private tableSheetPdf: TableSheetPdf; + + /** + * Retrieves the sales by items sheet in pdf format. + * @param {number} tenantId + * @param {number} query + * @returns {Promise} + */ + public async pdf( + tenantId: number, + query: ISalesByItemsReportQuery + ): Promise { + const table = await this.salesByItemsTable.table(tenantId, query); + const sheetName = 'Sales By Items'; + + return this.tableSheetPdf.convertToPdf( + tenantId, + table.table, + sheetName, + table.meta.baseCurrency + ); + } +} diff --git a/packages/server/src/services/FinancialStatements/TableSheetPdf.ts b/packages/server/src/services/FinancialStatements/TableSheetPdf.ts new file mode 100644 index 000000000..16fd0f6f9 --- /dev/null +++ b/packages/server/src/services/FinancialStatements/TableSheetPdf.ts @@ -0,0 +1,62 @@ +import { Inject, Service } from 'typedi'; +import * as R from 'ramda'; +import { ITableColumn, ITableData, ITableRow } from '@/interfaces'; +import { ChromiumlyTenancy } from '@/services/ChromiumlyTenancy/ChromiumlyTenancy'; +import { TemplateInjectable } from '@/services/TemplateInjectable/TemplateInjectable'; +import { FinancialTableStructure } from './FinancialTableStructure'; + +@Service() +export class TableSheetPdf { + @Inject() + private templateInjectable: TemplateInjectable; + + @Inject() + private chromiumlyTenancy: ChromiumlyTenancy; + + /** + * Converts the table to PDF. + * @param {number} tenantId - + * @param {IFinancialTable} table - + * @param {string} sheetName - Sheet name. + * @param {string} sheetDate - Sheet date. + */ + public async convertToPdf( + tenantId: number, + table: ITableData, + sheetName: string, + sheetDate: string + ) { + const columns = this.tablePdfColumns(table.columns); + const rows = this.tablePdfRows(table.rows); + const htmlContent = await this.templateInjectable.render( + tenantId, + 'modules/financial-sheet', + { + sheetName, + sheetDate, + table: { rows, columns }, + } + ); + return this.chromiumlyTenancy.convertHtmlContent(tenantId, htmlContent, { + margins: { top: 0, bottom: 0, left: 0, right: 0 }, + }); + } + + /** + * Converts the table columns to pdf columns. + * @param {ITableColumn[]} columns + * @returns {ITableColumn[]} + */ + private tablePdfColumns = (columns: ITableColumn[]): ITableColumn[] => { + return columns; + }; + + /** + * Converts the table rows to pdf rows. + * @param {ITableRow[]} rows - + * @returns {ITableRow[]} + */ + private tablePdfRows = (rows: ITableRow[]): ITableRow[] => { + return R.compose(FinancialTableStructure.flatNestedTree)(rows); + }; +} diff --git a/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceExportInjectable.ts b/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceExportInjectable.ts index a515f1beb..62b847f7c 100644 --- a/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceExportInjectable.ts +++ b/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceExportInjectable.ts @@ -1,13 +1,17 @@ +import { Inject, Service } from 'typedi'; import { TableSheet } from '@/lib/Xlsx/TableSheet'; import { ITrialBalanceSheetQuery } from '@/interfaces'; -import { Inject, Service } from 'typedi'; import { TrialBalanceSheetTableInjectable } from './TrialBalanceSheetTableInjectable'; +import { TrialBalanceSheetPdfInjectable } from './TrialBalanceSheetPdfInjectsable'; @Service() export class TrialBalanceExportInjectable { @Inject() private trialBalanceSheetTable: TrialBalanceSheetTableInjectable; + @Inject() + private trialBalanceSheetPdf: TrialBalanceSheetPdfInjectable; + /** * Retrieves the trial balance sheet in XLSX format. * @param {number} tenantId @@ -40,4 +44,17 @@ export class TrialBalanceExportInjectable { return tableCsv; } + + /** + * Retrieves the trial balance sheet in PDF format. + * @param {number} tenantId + * @param {ITrialBalanceSheetQuery} query + * @returns {Promise} + */ + public async pdf( + tenantId: number, + query: ITrialBalanceSheetQuery + ): Promise { + return this.trialBalanceSheetPdf.pdf(tenantId, query); + } } diff --git a/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetApplication.ts b/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetApplication.ts index a771c8f15..20c485ecc 100644 --- a/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetApplication.ts +++ b/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetApplication.ts @@ -57,4 +57,14 @@ export class TrialBalanceSheetApplication { public async xlsx(tenantId: number, query: ITrialBalanceSheetQuery) { return this.exportable.xlsx(tenantId, query); } + + /** + * Retrieve the trial balance sheet in pdf format. + * @param {number} tenantId + * @param {ITrialBalanceSheetQuery} query + * @returns {Promise} + */ + public async pdf(tenantId: number, query: ITrialBalanceSheetQuery) { + return this.exportable.pdf(tenantId, query); + } } diff --git a/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetPdfInjectsable.ts b/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetPdfInjectsable.ts new file mode 100644 index 000000000..1907dd26d --- /dev/null +++ b/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetPdfInjectsable.ts @@ -0,0 +1,34 @@ +import { Inject, Service } from 'typedi'; +import { ITrialBalanceSheetQuery } from '@/interfaces'; +import { TableSheetPdf } from '../TableSheetPdf'; +import { TrialBalanceSheetTableInjectable } from './TrialBalanceSheetTableInjectable'; + +@Service() +export class TrialBalanceSheetPdfInjectable { + @Inject() + private trialBalanceSheetTable: TrialBalanceSheetTableInjectable; + + @Inject() + private tableSheetPdf: TableSheetPdf; + + /** + * Converts the given balance sheet table to pdf. + * @param {number} tenantId - Tenant ID. + * @param {IBalanceSheetQuery} query - Balance sheet query. + * @returns {Promise} + */ + public async pdf( + tenantId: number, + query: ITrialBalanceSheetQuery + ): Promise { + const table = await this.trialBalanceSheetTable.table(tenantId, query); + const sheetName = 'Trial Balance Sheet'; + + return this.tableSheetPdf.convertToPdf( + tenantId, + table.table, + sheetName, + table.meta.baseCurrency + ); + } +} diff --git a/packages/server/src/services/FinancialStatements/VendorBalanceSummary/VendorBalanceSummaryApplication.ts b/packages/server/src/services/FinancialStatements/VendorBalanceSummary/VendorBalanceSummaryApplication.ts index 5fe4bc74d..c02eac224 100644 --- a/packages/server/src/services/FinancialStatements/VendorBalanceSummary/VendorBalanceSummaryApplication.ts +++ b/packages/server/src/services/FinancialStatements/VendorBalanceSummary/VendorBalanceSummaryApplication.ts @@ -3,6 +3,7 @@ import { IVendorBalanceSummaryQuery } from '@/interfaces'; import { VendorBalanceSummaryTableInjectable } from './VendorBalanceSummaryTableInjectable'; import { VendorBalanceSummaryExportInjectable } from './VendorBalanceSummaryExportInjectable'; import { VendorBalanceSummaryService } from './VendorBalanceSummaryService'; +import { VendorBalanceSummaryPdf } from './VendorBalanceSummaryPdf'; @Service() export class VendorBalanceSummaryApplication { @@ -15,6 +16,9 @@ export class VendorBalanceSummaryApplication { @Inject() private vendorBalanceSummaryExport: VendorBalanceSummaryExportInjectable; + @Inject() + private vendorBalanceSummaryPdf: VendorBalanceSummaryPdf; + /** * Retrieves the vendor balance summary sheet in sheet format. * @param {number} tenantId @@ -59,4 +63,14 @@ export class VendorBalanceSummaryApplication { ): Promise { return this.vendorBalanceSummaryExport.csv(tenantId, query); } + + /** + * Retrieves the vendor balance summary sheet in pdf format. + * @param {number} tenantId + * @param {IVendorBalanceSummaryQuery} query + * @returns {Promise} + */ + public pdf(tenantId: number, query: IVendorBalanceSummaryQuery) { + return this.vendorBalanceSummaryPdf.pdf(tenantId, query); + } } diff --git a/packages/server/src/services/FinancialStatements/VendorBalanceSummary/VendorBalanceSummaryPdf.ts b/packages/server/src/services/FinancialStatements/VendorBalanceSummary/VendorBalanceSummaryPdf.ts new file mode 100644 index 000000000..a404d673e --- /dev/null +++ b/packages/server/src/services/FinancialStatements/VendorBalanceSummary/VendorBalanceSummaryPdf.ts @@ -0,0 +1,34 @@ +import { Inject, Service } from 'typedi'; +import { IVendorBalanceSummaryQuery } from '@/interfaces'; +import { TableSheetPdf } from '../TableSheetPdf'; +import { VendorBalanceSummaryTableInjectable } from './VendorBalanceSummaryTableInjectable'; + +@Service() +export class VendorBalanceSummaryPdf { + @Inject() + private vendorBalanceSummaryTable: VendorBalanceSummaryTableInjectable; + + @Inject() + private tableSheetPdf: TableSheetPdf; + + /** + * Retrieves the sales by items sheet in pdf format. + * @param {number} tenantId + * @param {number} query + * @returns {Promise} + */ + public async pdf( + tenantId: number, + query: IVendorBalanceSummaryQuery + ): Promise { + const table = await this.vendorBalanceSummaryTable.table(tenantId, query); + const sheetName = 'Sales By Items'; + + return this.tableSheetPdf.convertToPdf( + tenantId, + table.table, + sheetName, + table.meta.baseCurrency + ); + } +} From b11c531cf538201d3938a5d92f9ddc5ccdfac387 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Sun, 11 Feb 2024 01:14:31 +0200 Subject: [PATCH 25/39] feat(server): wip priting financial reports --- .../CustomerBalanceSummary/index.ts | 14 ++++++++ .../FinancialStatements/GeneralLedger.ts | 12 +++++++ .../InventoryDetails/index.ts | 10 ++++++ .../InventoryValuationSheet.ts | 10 ++++++ .../FinancialStatements/JournalSheet.ts | 2 +- .../FinancialStatements/PurchasesByItem.ts | 11 +++++- .../SalesTaxLiabilitySummary/index.ts | 11 ++++++ .../TransactionsByCustomers/index.ts | 1 + .../TransactionsByVendors/index.ts | 12 +++++++ .../GeneralLedger/GeneralLedgerApplication.ts | 17 +++++++++ .../GeneralLedger/GeneralLedgerPdf.ts | 34 ++++++++++++++++++ .../InventoryDetailsApplication.ts | 14 ++++++++ .../InventoryDetailsTablePdf.ts | 36 +++++++++++++++++++ .../InventoryValuationSheetApplication.ts | 17 +++++++++ .../InventoryValuationSheetPdf.ts | 35 ++++++++++++++++++ .../PurchasesByItemsApplication.ts | 17 +++++++++ .../PurchasesByItems/PurchasesByItemsPdf.ts | 34 ++++++++++++++++++ .../SalesTaxLiabilitySummaryApplication.ts | 17 +++++++++ .../SalesTaxLiabiltiySummaryPdf.ts | 35 ++++++++++++++++++ .../TransactionsByCustomersApplication.ts | 17 +++++++++ .../TransactionsByCustomersPdf.ts | 36 +++++++++++++++++++ .../TransactionsByVendorApplication.ts | 14 ++++++++ .../TransactionsByVendorPdf.ts | 34 ++++++++++++++++++ 23 files changed, 438 insertions(+), 2 deletions(-) create mode 100644 packages/server/src/services/FinancialStatements/GeneralLedger/GeneralLedgerPdf.ts create mode 100644 packages/server/src/services/FinancialStatements/InventoryDetails/InventoryDetailsTablePdf.ts create mode 100644 packages/server/src/services/FinancialStatements/InventoryValuationSheet/InventoryValuationSheetPdf.ts create mode 100644 packages/server/src/services/FinancialStatements/PurchasesByItems/PurchasesByItemsPdf.ts create mode 100644 packages/server/src/services/FinancialStatements/SalesTaxLiabilitySummary/SalesTaxLiabiltiySummaryPdf.ts create mode 100644 packages/server/src/services/FinancialStatements/TransactionsByCustomer/TransactionsByCustomersPdf.ts create mode 100644 packages/server/src/services/FinancialStatements/TransactionsByVendor/TransactionsByVendorPdf.ts diff --git a/packages/server/src/api/controllers/FinancialStatements/CustomerBalanceSummary/index.ts b/packages/server/src/api/controllers/FinancialStatements/CustomerBalanceSummary/index.ts index 6c10543f5..e69fcdc1c 100644 --- a/packages/server/src/api/controllers/FinancialStatements/CustomerBalanceSummary/index.ts +++ b/packages/server/src/api/controllers/FinancialStatements/CustomerBalanceSummary/index.ts @@ -75,6 +75,7 @@ export default class CustomerBalanceSummaryReportController extends BaseFinancia ACCEPT_TYPE.APPLICATION_JSON_TABLE, ACCEPT_TYPE.APPLICATION_CSV, ACCEPT_TYPE.APPLICATION_XLSX, + ACCEPT_TYPE.APPLICATION_PDF, ]); // Retrieves the xlsx format. @@ -109,6 +110,19 @@ export default class CustomerBalanceSummaryReportController extends BaseFinancia filter ); return res.status(200).send(table); + // Retrieves the pdf format. + } else if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) { + const buffer = await this.customerBalanceSummaryApp.pdf( + tenantId, + filter + ); + + res.set({ + 'Content-Type': 'application/pdf', + 'Content-Length': buffer.length, + }); + return res.send(buffer); + // Retrieves the json format. } else { const sheet = await this.customerBalanceSummaryApp.sheet( tenantId, diff --git a/packages/server/src/api/controllers/FinancialStatements/GeneralLedger.ts b/packages/server/src/api/controllers/FinancialStatements/GeneralLedger.ts index c86da8eae..4c3aec4a9 100644 --- a/packages/server/src/api/controllers/FinancialStatements/GeneralLedger.ts +++ b/packages/server/src/api/controllers/FinancialStatements/GeneralLedger.ts @@ -71,6 +71,7 @@ export default class GeneralLedgerReportController extends BaseFinancialReportCo ACCEPT_TYPE.APPLICATION_JSON_TABLE, ACCEPT_TYPE.APPLICATION_XLSX, ACCEPT_TYPE.APPLICATION_CSV, + ACCEPT_TYPE.APPLICATION_PDF, ]); // Retrieves the table format. if (ACCEPT_TYPE.APPLICATION_JSON_TABLE === acceptType) { @@ -95,6 +96,17 @@ export default class GeneralLedgerReportController extends BaseFinancialReportCo 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ); return res.send(buffer); + // Retrieves the pdf format. + } else if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) { + const pdfContent = await this.generalLedgerApplication.pdf( + tenantId, + filter + ); + res.set({ + 'Content-Type': 'application/pdf', + 'Content-Length': pdfContent.length, + }); + return res.send(pdfContent); // Retrieves the json format. } else { const sheet = await this.generalLedgerApplication.sheet(tenantId, filter); diff --git a/packages/server/src/api/controllers/FinancialStatements/InventoryDetails/index.ts b/packages/server/src/api/controllers/FinancialStatements/InventoryDetails/index.ts index 07f91af4a..3288ee847 100644 --- a/packages/server/src/api/controllers/FinancialStatements/InventoryDetails/index.ts +++ b/packages/server/src/api/controllers/FinancialStatements/InventoryDetails/index.ts @@ -96,6 +96,7 @@ export default class InventoryDetailsController extends BaseController { ACCEPT_TYPE.APPLICATION_JSON_TABLE, ACCEPT_TYPE.APPLICATION_CSV, ACCEPT_TYPE.APPLICATION_XLSX, + ACCEPT_TYPE.APPLICATION_PDF, ]); // Retrieves the csv format. if (acceptType === ACCEPT_TYPE.APPLICATION_CSV) { @@ -127,6 +128,15 @@ export default class InventoryDetailsController extends BaseController { filter ); return res.status(200).send(table); + // Retrieves the pdf format. + } else if (acceptType === ACCEPT_TYPE.APPLICATION_PDF) { + const buffer = await this.inventoryItemDetailsApp.pdf(tenantId, filter); + + res.set({ + 'Content-Type': 'application/pdf', + 'Content-Length': buffer.length, + }); + return res.send(buffer); } else { const sheet = await this.inventoryItemDetailsApp.sheet( tenantId, diff --git a/packages/server/src/api/controllers/FinancialStatements/InventoryValuationSheet.ts b/packages/server/src/api/controllers/FinancialStatements/InventoryValuationSheet.ts index 3a2d3c196..b31a911a2 100644 --- a/packages/server/src/api/controllers/FinancialStatements/InventoryValuationSheet.ts +++ b/packages/server/src/api/controllers/FinancialStatements/InventoryValuationSheet.ts @@ -79,6 +79,7 @@ export default class InventoryValuationReportController extends BaseFinancialRep ACCEPT_TYPE.APPLICATION_JSON_TABLE, ACCEPT_TYPE.APPLICATION_XLSX, ACCEPT_TYPE.APPLICATION_CSV, + ACCEPT_TYPE.APPLICATION_PDF, ]); // Retrieves the json table format. @@ -104,6 +105,15 @@ export default class InventoryValuationReportController extends BaseFinancialRep 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ); return res.send(buffer); + // Retrieves the pdf format. + } else if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) { + const pdfContent = await this.inventoryValuationApp.pdf(tenantId, filter); + + res.set({ + 'Content-Type': 'application/pdf', + 'Content-Length': pdfContent.length, + }); + return res.status(200).send(pdfContent); // Retrieves the json format. } else { const { data, query, meta } = await this.inventoryValuationApp.sheet( diff --git a/packages/server/src/api/controllers/FinancialStatements/JournalSheet.ts b/packages/server/src/api/controllers/FinancialStatements/JournalSheet.ts index 0355766db..561f69329 100644 --- a/packages/server/src/api/controllers/FinancialStatements/JournalSheet.ts +++ b/packages/server/src/api/controllers/FinancialStatements/JournalSheet.ts @@ -82,7 +82,7 @@ export default class JournalSheetController extends BaseFinancialReportControlle // Retrieves the csv format. } else if (ACCEPT_TYPE.APPLICATION_CSV === acceptType) { const buffer = await this.journalSheetApp.csv(tenantId, filter); - + res.setHeader('Content-Disposition', 'attachment; filename=output.csv'); res.setHeader('Content-Type', 'text/csv'); diff --git a/packages/server/src/api/controllers/FinancialStatements/PurchasesByItem.ts b/packages/server/src/api/controllers/FinancialStatements/PurchasesByItem.ts index 2e72587c0..2c92bdc97 100644 --- a/packages/server/src/api/controllers/FinancialStatements/PurchasesByItem.ts +++ b/packages/server/src/api/controllers/FinancialStatements/PurchasesByItem.ts @@ -75,8 +75,8 @@ export default class PurchasesByItemReportController extends BaseFinancialReport ACCEPT_TYPE.APPLICATION_JSON_TABLE, ACCEPT_TYPE.APPLICATION_XLSX, ACCEPT_TYPE.APPLICATION_CSV, + ACCEPT_TYPE.APPLICATION_PDF, ]); - // JSON table response format. if (ACCEPT_TYPE.APPLICATION_JSON_TABLE === acceptType) { const table = await this.purchasesByItemsApp.table(tenantId, filter); @@ -100,6 +100,15 @@ export default class PurchasesByItemReportController extends BaseFinancialReport 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ); return res.send(buffer); + // PDF response format. + } else if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) { + const pdfContent = await this.purchasesByItemsApp.pdf(tenantId, filter); + + res.set({ + 'Content-Type': 'application/pdf', + 'Content-Length': pdfContent.length, + }); + return res.send(pdfContent); // Json response format. } else { const sheet = await this.purchasesByItemsApp.sheet(tenantId, filter); diff --git a/packages/server/src/api/controllers/FinancialStatements/SalesTaxLiabilitySummary/index.ts b/packages/server/src/api/controllers/FinancialStatements/SalesTaxLiabilitySummary/index.ts index 933b5c9c4..42a96aab2 100644 --- a/packages/server/src/api/controllers/FinancialStatements/SalesTaxLiabilitySummary/index.ts +++ b/packages/server/src/api/controllers/FinancialStatements/SalesTaxLiabilitySummary/index.ts @@ -62,6 +62,7 @@ export default class SalesTaxLiabilitySummary extends BaseFinancialReportControl ACCEPT_TYPE.APPLICATION_JSON_TABLE, ACCEPT_TYPE.APPLICATION_CSV, ACCEPT_TYPE.APPLICATION_XLSX, + ACCEPT_TYPE.APPLICATION_PDF, ]); // Retrieves the json table format. @@ -97,6 +98,16 @@ export default class SalesTaxLiabilitySummary extends BaseFinancialReportControl return res.send(buffer); // Retrieves the json format. + } else if (acceptType === ACCEPT_TYPE.APPLICATION_PDF) { + const pdfContent = await this.salesTaxLiabilitySummaryApp.pdf( + tenantId, + filter + ); + res.set({ + 'Content-Type': 'application/pdf', + 'Content-Length': pdfContent.length, + }); + return res.status(200).send(pdfContent); } else { const sheet = await this.salesTaxLiabilitySummaryApp.sheet( tenantId, diff --git a/packages/server/src/api/controllers/FinancialStatements/TransactionsByCustomers/index.ts b/packages/server/src/api/controllers/FinancialStatements/TransactionsByCustomers/index.ts index 4bc3b1f44..42a619ec8 100644 --- a/packages/server/src/api/controllers/FinancialStatements/TransactionsByCustomers/index.ts +++ b/packages/server/src/api/controllers/FinancialStatements/TransactionsByCustomers/index.ts @@ -70,6 +70,7 @@ export default class TransactionsByCustomersReportController extends BaseFinanci ACCEPT_TYPE.APPLICATION_JSON_TABLE, ACCEPT_TYPE.APPLICATION_CSV, ACCEPT_TYPE.APPLICATION_XLSX, + ACCEPT_TYPE.APPLICATION_PDF, ]); try { // Retrieves the json table format. diff --git a/packages/server/src/api/controllers/FinancialStatements/TransactionsByVendors/index.ts b/packages/server/src/api/controllers/FinancialStatements/TransactionsByVendors/index.ts index a0c1bf037..c437892f4 100644 --- a/packages/server/src/api/controllers/FinancialStatements/TransactionsByVendors/index.ts +++ b/packages/server/src/api/controllers/FinancialStatements/TransactionsByVendors/index.ts @@ -71,6 +71,7 @@ export default class TransactionsByVendorsReportController extends BaseFinancial ACCEPT_TYPE.APPLICATION_JSON_TABLE, ACCEPT_TYPE.APPLICATION_CSV, ACCEPT_TYPE.APPLICATION_XLSX, + ACCEPT_TYPE.APPLICATION_PDF, ]); // Retrieves the xlsx format. @@ -101,6 +102,17 @@ export default class TransactionsByVendorsReportController extends BaseFinancial filter ); return res.status(200).send(table); + // Retrieves the pdf format. + } else if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) { + const pdfContent = await this.transactionsByVendorsApp.pdf( + tenantId, + filter + ); + res.set({ + 'Content-Type': 'application/pdf', + 'Content-Length': pdfContent.length, + }); + return res.send(pdfContent); // Retrieves the json format. } else { const sheet = await this.transactionsByVendorsApp.sheet( diff --git a/packages/server/src/services/FinancialStatements/GeneralLedger/GeneralLedgerApplication.ts b/packages/server/src/services/FinancialStatements/GeneralLedger/GeneralLedgerApplication.ts index 924b0da8c..6257e34d8 100644 --- a/packages/server/src/services/FinancialStatements/GeneralLedger/GeneralLedgerApplication.ts +++ b/packages/server/src/services/FinancialStatements/GeneralLedger/GeneralLedgerApplication.ts @@ -6,6 +6,7 @@ import { import { GeneralLedgerTableInjectable } from './GeneralLedgerTableInjectable'; import { GeneralLedgerExportInjectable } from './GeneralLedgerExport'; import { GeneralLedgerService } from './GeneralLedgerService'; +import { GeneralLedgerPdf } from './GeneralLedgerPdf'; export class GeneralLedgerApplication { @Inject() @@ -17,6 +18,9 @@ export class GeneralLedgerApplication { @Inject() private GLSheet: GeneralLedgerService; + @Inject() + private GLPdf: GeneralLedgerPdf; + /** * Retrieves the G/L sheet in json format. * @param {number} tenantId @@ -63,4 +67,17 @@ export class GeneralLedgerApplication { ): Promise { return this.GLExport.csv(tenantId, query); } + + /** + * Retrieves the G/L sheet in pdf format. + * @param {number} tenantId + * @param {IGeneralLedgerSheetQuery} query + * @returns {Promise} + */ + public pdf( + tenantId: number, + query: IGeneralLedgerSheetQuery + ): Promise { + return this.GLPdf.pdf(tenantId, query); + } } diff --git a/packages/server/src/services/FinancialStatements/GeneralLedger/GeneralLedgerPdf.ts b/packages/server/src/services/FinancialStatements/GeneralLedger/GeneralLedgerPdf.ts new file mode 100644 index 000000000..d1d12ae43 --- /dev/null +++ b/packages/server/src/services/FinancialStatements/GeneralLedger/GeneralLedgerPdf.ts @@ -0,0 +1,34 @@ +import { Inject, Service } from "typedi"; +import { TableSheetPdf } from "../TableSheetPdf"; +import { GeneralLedgerTableInjectable } from "./GeneralLedgerTableInjectable"; +import { IGeneralLedgerSheetQuery } from "@/interfaces"; + +@Service() +export class GeneralLedgerPdf { + @Inject() + private generalLedgerTable: GeneralLedgerTableInjectable; + + @Inject() + private tableSheetPdf: TableSheetPdf; + + /** + * Converts the general ledger sheet table to pdf. + * @param {number} tenantId - Tenant ID. + * @param {IGeneralLedgerSheetQuery} query - + * @returns {Promise} + */ + public async pdf( + tenantId: number, + query: IGeneralLedgerSheetQuery + ): Promise { + const table = await this.generalLedgerTable.table(tenantId, query); + const sheetName = 'General Ledger'; + + return this.tableSheetPdf.convertToPdf( + tenantId, + table.table, + sheetName, + table.meta.baseCurrency + ); + } +} \ No newline at end of file diff --git a/packages/server/src/services/FinancialStatements/InventoryDetails/InventoryDetailsApplication.ts b/packages/server/src/services/FinancialStatements/InventoryDetails/InventoryDetailsApplication.ts index a2639094e..50cbd2a2f 100644 --- a/packages/server/src/services/FinancialStatements/InventoryDetails/InventoryDetailsApplication.ts +++ b/packages/server/src/services/FinancialStatements/InventoryDetails/InventoryDetailsApplication.ts @@ -6,6 +6,7 @@ import { Inject, Service } from 'typedi'; import { InventoryDetailsExportInjectable } from './InventoryDetailsExportInjectable'; import { InventoryDetailsTableInjectable } from './InventoryDetailsTableInjectable'; import { InventoryDetailsService } from './InventoryDetailsService'; +import { InventoryDetailsTablePdf } from './InventoryDetailsTablePdf'; @Service() export class InventortyDetailsApplication { @@ -18,6 +19,9 @@ export class InventortyDetailsApplication { @Inject() private inventoryDetails: InventoryDetailsService; + @Inject() + private inventoryDetailsPdf: InventoryDetailsTablePdf; + /** * Retrieves the inventory details report in sheet format. * @param {number} tenantId @@ -63,4 +67,14 @@ export class InventortyDetailsApplication { public csv(tenantId: number, query: IInventoryDetailsQuery): Promise { return this.inventoryDetailsExport.csv(tenantId, query); } + + /** + * Retrieves the inventory details report in PDF format. + * @param {number} tenantId + * @param {IInventoryDetailsQuery} query + * @returns {Promise} + */ + public pdf(tenantId: number, query: IInventoryDetailsQuery) { + return this.inventoryDetailsPdf.pdf(tenantId, query); + } } diff --git a/packages/server/src/services/FinancialStatements/InventoryDetails/InventoryDetailsTablePdf.ts b/packages/server/src/services/FinancialStatements/InventoryDetails/InventoryDetailsTablePdf.ts new file mode 100644 index 000000000..1c2077fc6 --- /dev/null +++ b/packages/server/src/services/FinancialStatements/InventoryDetails/InventoryDetailsTablePdf.ts @@ -0,0 +1,36 @@ +import { Inject, Service } from "typedi"; +import { InventoryDetailsTableInjectable } from "./InventoryDetailsTableInjectable"; +import { TableSheetPdf } from "../TableSheetPdf"; +import { IInventoryDetailsQuery } from "@/interfaces"; + + +@Service() +export class InventoryDetailsTablePdf { + @Inject() + private inventoryDetailsTable: InventoryDetailsTableInjectable; + + @Inject() + private tableSheetPdf: TableSheetPdf; + + /** + * Converts the given inventory details sheet table to pdf. + * @param {number} tenantId - Tenant ID. + * @param {IBalanceSheetQuery} query - Balance sheet query. + * @returns {Promise} + */ + public async pdf( + tenantId: number, + query: IInventoryDetailsQuery + ): Promise { + const table = await this.inventoryDetailsTable.table(tenantId, query); + const sheetName = 'Inventory Items Details'; + + return this.tableSheetPdf.convertToPdf( + tenantId, + table.table, + sheetName, + table.meta.baseCurrency + ); + } + +} \ No newline at end of file diff --git a/packages/server/src/services/FinancialStatements/InventoryValuationSheet/InventoryValuationSheetApplication.ts b/packages/server/src/services/FinancialStatements/InventoryValuationSheet/InventoryValuationSheetApplication.ts index 43406b0d8..7c4a65967 100644 --- a/packages/server/src/services/FinancialStatements/InventoryValuationSheet/InventoryValuationSheetApplication.ts +++ b/packages/server/src/services/FinancialStatements/InventoryValuationSheet/InventoryValuationSheetApplication.ts @@ -7,6 +7,7 @@ import { Inject, Service } from 'typedi'; import { InventoryValuationSheetService } from './InventoryValuationSheetService'; import { InventoryValuationSheetTableInjectable } from './InventoryValuationSheetTableInjectable'; import { InventoryValuationSheetExportable } from './InventoryValuationSheetExportable'; +import { InventoryValuationSheetPdf } from './InventoryValuationSheetPdf'; @Service() export class InventoryValuationSheetApplication { @@ -19,6 +20,9 @@ export class InventoryValuationSheetApplication { @Inject() private inventoryValuationExport: InventoryValuationSheetExportable; + @Inject() + private inventoryValuationPdf: InventoryValuationSheetPdf; + /** * Retrieves the inventory valuation json format. * @param {number} tenantId @@ -73,4 +77,17 @@ export class InventoryValuationSheetApplication { ): Promise { return this.inventoryValuationExport.csv(tenantId, query); } + + /** + * Retrieves the inventory valuation pdf format. + * @param {number} tenantId + * @param {IInventoryValuationReportQuery} query + * @returns {Promise} + */ + public pdf( + tenantId: number, + query: IInventoryValuationReportQuery + ): Promise { + return this.inventoryValuationPdf.pdf(tenantId, query); + } } diff --git a/packages/server/src/services/FinancialStatements/InventoryValuationSheet/InventoryValuationSheetPdf.ts b/packages/server/src/services/FinancialStatements/InventoryValuationSheet/InventoryValuationSheetPdf.ts new file mode 100644 index 000000000..6148816d8 --- /dev/null +++ b/packages/server/src/services/FinancialStatements/InventoryValuationSheet/InventoryValuationSheetPdf.ts @@ -0,0 +1,35 @@ +import { Inject, Service } from "typedi"; +import { InventoryValuationSheetTableInjectable } from "./InventoryValuationSheetTableInjectable"; +import { TableSheetPdf } from "../TableSheetPdf"; +import { IInventoryValuationReportQuery } from "@/interfaces"; + + +@Service() +export class InventoryValuationSheetPdf { + @Inject() + private inventoryValuationTable: InventoryValuationSheetTableInjectable; + + @Inject() + private tableSheetPdf: TableSheetPdf; + + /** + * Converts the given balance sheet table to pdf. + * @param {number} tenantId - Tenant ID. + * @param {IBalanceSheetQuery} query - Balance sheet query. + * @returns {Promise} + */ + public async pdf( + tenantId: number, + query: IInventoryValuationReportQuery + ): Promise { + const table = await this.inventoryValuationTable.table(tenantId, query); + const sheetName = 'Inventory Valuation Sheet'; + + return this.tableSheetPdf.convertToPdf( + tenantId, + table.table, + sheetName, + table.meta.baseCurrency + ); + } +} \ No newline at end of file diff --git a/packages/server/src/services/FinancialStatements/PurchasesByItems/PurchasesByItemsApplication.ts b/packages/server/src/services/FinancialStatements/PurchasesByItems/PurchasesByItemsApplication.ts index c9a94bdbc..eeecaff38 100644 --- a/packages/server/src/services/FinancialStatements/PurchasesByItems/PurchasesByItemsApplication.ts +++ b/packages/server/src/services/FinancialStatements/PurchasesByItems/PurchasesByItemsApplication.ts @@ -7,6 +7,7 @@ import { } from '@/interfaces/PurchasesByItemsSheet'; import { PurchasesByItemsTableInjectable } from './PurchasesByItemsTableInjectable'; import { PurchasesByItemsService } from './PurchasesByItemsService'; +import { PurchasesByItemsPdf } from './PurchasesByItemsPdf'; @Service() export class PurcahsesByItemsApplication { @@ -19,6 +20,9 @@ export class PurcahsesByItemsApplication { @Inject() private purchasesByItemsExport: PurchasesByItemsExport; + @Inject() + private purchasesByItemsPdf: PurchasesByItemsPdf; + /** * Retrieves the purchases by items in json format. * @param {number} tenantId @@ -70,4 +74,17 @@ export class PurcahsesByItemsApplication { ): Promise { return this.purchasesByItemsExport.xlsx(tenantId, query); } + + /** + * Retrieves the purchases by items in pdf format. + * @param {number} tenantId + * @param {IPurchasesByItemsReportQuery} filter + * @returns {Promise} + */ + public pdf( + tenantId: number, + filter: IPurchasesByItemsReportQuery + ): Promise { + return this.purchasesByItemsPdf.pdf(tenantId, filter); + } } diff --git a/packages/server/src/services/FinancialStatements/PurchasesByItems/PurchasesByItemsPdf.ts b/packages/server/src/services/FinancialStatements/PurchasesByItems/PurchasesByItemsPdf.ts new file mode 100644 index 000000000..36645aeb3 --- /dev/null +++ b/packages/server/src/services/FinancialStatements/PurchasesByItems/PurchasesByItemsPdf.ts @@ -0,0 +1,34 @@ +import { Inject, Service } from 'typedi'; +import { TableSheetPdf } from '../TableSheetPdf'; +import { PurchasesByItemsTableInjectable } from './PurchasesByItemsTableInjectable'; +import { IPurchasesByItemsReportQuery } from '@/interfaces/PurchasesByItemsSheet'; + +@Service() +export class PurchasesByItemsPdf { + @Inject() + private purchasesByItemsTable: PurchasesByItemsTableInjectable; + + @Inject() + private tableSheetPdf: TableSheetPdf; + + /** + * Converts the given journal sheet table to pdf. + * @param {number} tenantId - Tenant ID. + * @param {IBalanceSheetQuery} query - Balance sheet query. + * @returns {Promise} + */ + public async pdf( + tenantId: number, + query: IPurchasesByItemsReportQuery + ): Promise { + const table = await this.purchasesByItemsTable.table(tenantId, query); + const sheetName = 'Purchases By Items'; + + return this.tableSheetPdf.convertToPdf( + tenantId, + table.table, + sheetName, + table.meta.baseCurrency + ); + } +} diff --git a/packages/server/src/services/FinancialStatements/SalesTaxLiabilitySummary/SalesTaxLiabilitySummaryApplication.ts b/packages/server/src/services/FinancialStatements/SalesTaxLiabilitySummary/SalesTaxLiabilitySummaryApplication.ts index f0e5a5248..3b9f0d7b3 100644 --- a/packages/server/src/services/FinancialStatements/SalesTaxLiabilitySummary/SalesTaxLiabilitySummaryApplication.ts +++ b/packages/server/src/services/FinancialStatements/SalesTaxLiabilitySummary/SalesTaxLiabilitySummaryApplication.ts @@ -3,6 +3,7 @@ import { SalesTaxLiabilitySummaryQuery } from '@/interfaces/SalesTaxLiabilitySum import { SalesTaxLiabilitySummaryTableInjectable } from './SalesTaxLiabilitySummaryTableInjectable'; import { SalesTaxLiabilitySummaryExportInjectable } from './SalesTaxLiabilitySummaryExportInjectable'; import { SalesTaxLiabilitySummaryService } from './SalesTaxLiabilitySummaryService'; +import { SalesTaxLiabiltiySummaryPdf } from './SalesTaxLiabiltiySummaryPdf'; @Service() export class SalesTaxLiabilitySummaryApplication { @@ -15,6 +16,9 @@ export class SalesTaxLiabilitySummaryApplication { @Inject() private salesTaxLiabilityTable: SalesTaxLiabilitySummaryTableInjectable; + @Inject() + private salesTaxLiabiltiyPdf: SalesTaxLiabiltiySummaryPdf; + /** * Retrieves the sales tax liability summary in json format. * @param {number} tenantId @@ -60,4 +64,17 @@ export class SalesTaxLiabilitySummaryApplication { ): Promise { return this.salesTaxLiabilityExport.csv(tenantId, query); } + + /** + * Retrieves the sales tax liability summary in PDF format. + * @param {number} tenantId + * @param {SalesTaxLiabilitySummaryQuery} query + * @returns {Promise} + */ + public pdf( + tenantId: number, + query: SalesTaxLiabilitySummaryQuery + ): Promise { + return this.salesTaxLiabiltiyPdf.pdf(tenantId, query): + } } diff --git a/packages/server/src/services/FinancialStatements/SalesTaxLiabilitySummary/SalesTaxLiabiltiySummaryPdf.ts b/packages/server/src/services/FinancialStatements/SalesTaxLiabilitySummary/SalesTaxLiabiltiySummaryPdf.ts new file mode 100644 index 000000000..1b9a509a0 --- /dev/null +++ b/packages/server/src/services/FinancialStatements/SalesTaxLiabilitySummary/SalesTaxLiabiltiySummaryPdf.ts @@ -0,0 +1,35 @@ +import { Inject, Service } from 'typedi'; +import { TableSheetPdf } from '../TableSheetPdf'; +import { SalesTaxLiabilitySummaryTableInjectable } from './SalesTaxLiabilitySummaryTableInjectable'; +import { ISalesByItemsReportQuery } from '@/interfaces'; +import { SalesTaxLiabilitySummaryQuery } from '@/interfaces/SalesTaxLiabilitySummary'; + +@Service() +export class SalesTaxLiabiltiySummaryPdf { + @Inject() + private salesTaxLiabiltiySummaryTable: SalesTaxLiabilitySummaryTableInjectable; + + @Inject() + private tableSheetPdf: TableSheetPdf; + + /** + * Converts the given sales tax liability summary table to pdf. + * @param {number} tenantId - Tenant ID. + * @param {ISalesByItemsReportQuery} query - Balance sheet query. + * @returns {Promise} + */ + public async pdf( + tenantId: number, + query: SalesTaxLiabilitySummaryQuery + ): Promise { + const table = await this.salesTaxLiabiltiySummaryTable.table(tenantId, query); + const sheetName = 'Sales Tax Liability Summary'; + + return this.tableSheetPdf.convertToPdf( + tenantId, + table.table, + sheetName, + table.meta.baseCurrency + ); + } +} diff --git a/packages/server/src/services/FinancialStatements/TransactionsByCustomer/TransactionsByCustomersApplication.ts b/packages/server/src/services/FinancialStatements/TransactionsByCustomer/TransactionsByCustomersApplication.ts index b729c219a..c3e4feb7e 100644 --- a/packages/server/src/services/FinancialStatements/TransactionsByCustomer/TransactionsByCustomersApplication.ts +++ b/packages/server/src/services/FinancialStatements/TransactionsByCustomer/TransactionsByCustomersApplication.ts @@ -6,6 +6,7 @@ import { import { TransactionsByCustomersTableInjectable } from './TransactionsByCustomersTableInjectable'; import { TransactionsByCustomersExportInjectable } from './TransactionsByCustomersExportInjectable'; import { TransactionsByCustomersSheet } from './TransactionsByCustomersService'; +import { TransactionsByCustomersPdf } from './TransactionsByCustomersPdf'; @Service() export class TransactionsByCustomerApplication { @@ -18,6 +19,9 @@ export class TransactionsByCustomerApplication { @Inject() private transactionsByCustomersSheet: TransactionsByCustomersSheet; + @Inject() + private transactionsByCustomersPdf: TransactionsByCustomersPdf; + /** * Retrieves the transactions by customers sheet in json format. * @param {number} tenantId @@ -69,4 +73,17 @@ export class TransactionsByCustomerApplication { ): Promise { return this.transactionsByCustomersExport.xlsx(tenantId, query); } + + /** + * Retrieves the transactions by vendors sheet in PDF format. + * @param {number} tenantId + * @param {ITransactionsByCustomersFilter} query + * @returns {Promise} + */ + public pdf( + tenantId: number, + query: ITransactionsByCustomersFilter + ): Promise { + return this.transactionsByCustomersPdf.pdf(tenantId, query); + } } diff --git a/packages/server/src/services/FinancialStatements/TransactionsByCustomer/TransactionsByCustomersPdf.ts b/packages/server/src/services/FinancialStatements/TransactionsByCustomer/TransactionsByCustomersPdf.ts new file mode 100644 index 000000000..2926e63e6 --- /dev/null +++ b/packages/server/src/services/FinancialStatements/TransactionsByCustomer/TransactionsByCustomersPdf.ts @@ -0,0 +1,36 @@ +import { ITransactionsByCustomersFilter } from '@/interfaces'; +import { Inject } from 'typedi'; +import { TableSheetPdf } from '../TableSheetPdf'; +import { TransactionsByCustomersTableInjectable } from './TransactionsByCustomersTableInjectable'; + +export class TransactionsByCustomersPdf { + @Inject() + private transactionsByCustomersTable: TransactionsByCustomersTableInjectable; + + @Inject() + private tableSheetPdf: TableSheetPdf; + + /** + * Retrieves the transactions by customers in PDF format. + * @param {number} tenantId - Tenant ID. + * @param {IBalanceSheetQuery} query - Balance sheet query. + * @returns {Promise} + */ + public async pdf( + tenantId: number, + query: ITransactionsByCustomersFilter + ): Promise { + const table = await this.transactionsByCustomersTable.table( + tenantId, + query + ); + const sheetName = 'Transactions By Customers'; + + return this.tableSheetPdf.convertToPdf( + tenantId, + table.table, + sheetName, + table.meta.baseCurrency + ); + } +} diff --git a/packages/server/src/services/FinancialStatements/TransactionsByVendor/TransactionsByVendorApplication.ts b/packages/server/src/services/FinancialStatements/TransactionsByVendor/TransactionsByVendorApplication.ts index d8d424a30..744993bb0 100644 --- a/packages/server/src/services/FinancialStatements/TransactionsByVendor/TransactionsByVendorApplication.ts +++ b/packages/server/src/services/FinancialStatements/TransactionsByVendor/TransactionsByVendorApplication.ts @@ -7,6 +7,7 @@ import { import { TransactionsByVendorExportInjectable } from './TransactionsByVendorExportInjectable'; import { TransactionsByVendorTableInjectable } from './TransactionsByVendorTableInjectable'; import { TransactionsByVendorsInjectable } from './TransactionsByVendorInjectable'; +import { TransactionsByVendorsPdf } from './TransactionsByVendorPdf'; @Service() export class TransactionsByVendorApplication { @@ -19,6 +20,9 @@ export class TransactionsByVendorApplication { @Inject() private transactionsByVendorSheet: TransactionsByVendorsInjectable; + @Inject() + private transactionsByVendorPdf: TransactionsByVendorsPdf; + /** * Retrieves the transactions by vendor in sheet format. * @param {number} tenantId @@ -72,4 +76,14 @@ export class TransactionsByVendorApplication { ): Promise { return this.transactionsByVendorExport.xlsx(tenantId, query); } + + /** + * Retrieves the transactions by vendor in PDF format. + * @param {number} tenantId + * @param {ITransactionsByVendorsFilter} query + * @returns {Promise} + */ + public pdf(tenantId: number, query: ITransactionsByVendorsFilter) { + return this.transactionsByVendorPdf.pdf(tenantId, query); + } } diff --git a/packages/server/src/services/FinancialStatements/TransactionsByVendor/TransactionsByVendorPdf.ts b/packages/server/src/services/FinancialStatements/TransactionsByVendor/TransactionsByVendorPdf.ts new file mode 100644 index 000000000..6c8b7f68a --- /dev/null +++ b/packages/server/src/services/FinancialStatements/TransactionsByVendor/TransactionsByVendorPdf.ts @@ -0,0 +1,34 @@ +import { Inject, Service } from 'typedi'; +import { ITransactionsByVendorsFilter } from '@/interfaces'; +import { TableSheetPdf } from '../TableSheetPdf'; +import { TransactionsByVendorTableInjectable } from './TransactionsByVendorTableInjectable'; + +@Service() +export class TransactionsByVendorsPdf { + @Inject() + private transactionsByVendorTable: TransactionsByVendorTableInjectable; + + @Inject() + private tableSheetPdf: TableSheetPdf; + + /** + * Converts the given balance sheet table to pdf. + * @param {number} tenantId - Tenant ID. + * @param {IBalanceSheetQuery} query - Balance sheet query. + * @returns {Promise} + */ + public async pdf( + tenantId: number, + query: ITransactionsByVendorsFilter + ): Promise { + const table = await this.transactionsByVendorTable.table(tenantId, query); + const sheetName = 'Transactions By Vendors'; + + return this.tableSheetPdf.convertToPdf( + tenantId, + table.table, + sheetName, + table.meta.baseCurrency + ); + } +} From 09ad725a6779efd9b87528c251a9c8fcdb16a984 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Sun, 11 Feb 2024 16:12:41 +0200 Subject: [PATCH 26/39] feat(webapp): wip print preview financial reports --- .../src/components/FormattedMessage/index.tsx | 9 +++- packages/webapp/src/constants/dialogs.ts | 5 +++ .../BalanceSheet/BalanceSheet.tsx | 3 ++ .../BalanceSheet/BalanceSheetActionsBar.tsx | 11 +++++ .../BalanceSheet/BalanceSheetDialogs.tsx | 10 +++++ .../BalanceSheetPdfDialog.tsx | 39 ++++++++++++++++ .../BalanceSheetPdfDialogContent.tsx | 42 ++++++++++++++++++ .../dialogs/BalanceSheetPdfDialog/index.ts | 1 + .../CashFlowStatement/CashFlowStatement.tsx | 3 ++ .../CashFlowStatementActionsBar.tsx | 12 +++++ .../CashflowSheetDialogs.tsx | 12 +++++ .../CashflowSheetPdfDialog.tsx | 44 +++++++++++++++++++ .../CashflowSheetPdfDialogContent.tsx | 42 ++++++++++++++++++ .../CashflowSheetPdfDialog/index.ts | 1 + .../ProfitLossSheet/ProfitLossActionsBar.tsx | 11 +++++ .../ProfitLossSheet/ProfitLossSheet.tsx | 3 ++ .../ProfitLossSheetDialogs.tsx | 10 +++++ .../ProfitLossSheetPdfDialog.tsx | 44 +++++++++++++++++++ .../ProfitLossSheetPdfDialogContent.tsx | 42 ++++++++++++++++++ .../ProfitLossSheetPdfDialog/index.ts | 1 + .../TrialBalanceActionsBar.tsx | 12 +++++ .../TrialBalanceSheet/TrialBalanceSheet.tsx | 3 ++ .../TrialBalanceSheetDialogs.tsx | 12 +++++ .../TrialBalanceSheetPdfDialog.tsx | 44 +++++++++++++++++++ .../TrialBalanceSheetPdfDialogContent.tsx | 42 ++++++++++++++++++ .../TrialBalanceSheetPdfDialog/index.ts | 1 + .../src/hooks/query/financialReports.tsx | 30 +++++++++++++ 27 files changed, 487 insertions(+), 2 deletions(-) create mode 100644 packages/webapp/src/containers/FinancialStatements/BalanceSheet/BalanceSheetDialogs.tsx create mode 100644 packages/webapp/src/containers/FinancialStatements/BalanceSheet/dialogs/BalanceSheetPdfDialog/BalanceSheetPdfDialog.tsx create mode 100644 packages/webapp/src/containers/FinancialStatements/BalanceSheet/dialogs/BalanceSheetPdfDialog/BalanceSheetPdfDialogContent.tsx create mode 100644 packages/webapp/src/containers/FinancialStatements/BalanceSheet/dialogs/BalanceSheetPdfDialog/index.ts create mode 100644 packages/webapp/src/containers/FinancialStatements/CashFlowStatement/CashflowSheetDialogs.tsx create mode 100644 packages/webapp/src/containers/FinancialStatements/CashFlowStatement/CashflowSheetPdfDialog/CashflowSheetPdfDialog.tsx create mode 100644 packages/webapp/src/containers/FinancialStatements/CashFlowStatement/CashflowSheetPdfDialog/CashflowSheetPdfDialogContent.tsx create mode 100644 packages/webapp/src/containers/FinancialStatements/CashFlowStatement/CashflowSheetPdfDialog/index.ts create mode 100644 packages/webapp/src/containers/FinancialStatements/ProfitLossSheet/ProfitLossSheetDialogs.tsx create mode 100644 packages/webapp/src/containers/FinancialStatements/ProfitLossSheet/ProfitLossSheetPdfDialog/ProfitLossSheetPdfDialog.tsx create mode 100644 packages/webapp/src/containers/FinancialStatements/ProfitLossSheet/ProfitLossSheetPdfDialog/ProfitLossSheetPdfDialogContent.tsx create mode 100644 packages/webapp/src/containers/FinancialStatements/ProfitLossSheet/ProfitLossSheetPdfDialog/index.ts create mode 100644 packages/webapp/src/containers/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetDialogs.tsx create mode 100644 packages/webapp/src/containers/FinancialStatements/TrialBalanceSheet/dialogs/TrialBalanceSheetPdfDialog/TrialBalanceSheetPdfDialog.tsx create mode 100644 packages/webapp/src/containers/FinancialStatements/TrialBalanceSheet/dialogs/TrialBalanceSheetPdfDialog/TrialBalanceSheetPdfDialogContent.tsx create mode 100644 packages/webapp/src/containers/FinancialStatements/TrialBalanceSheet/dialogs/TrialBalanceSheetPdfDialog/index.ts diff --git a/packages/webapp/src/components/FormattedMessage/index.tsx b/packages/webapp/src/components/FormattedMessage/index.tsx index b23418325..6dd6ff98b 100644 --- a/packages/webapp/src/components/FormattedMessage/index.tsx +++ b/packages/webapp/src/components/FormattedMessage/index.tsx @@ -1,8 +1,13 @@ // @ts-nocheck import intl from 'react-intl-universal'; -export function FormattedMessage({ id, values }) { - return intl.get(id, values); +interface FormattedMessageProps { + id: string; + values?: Record; +} + +export function FormattedMessage({ id, values }: FormattedMessageProps) { + return <>{intl.get(id, values)}; } export function FormattedHTMLMessage({ ...args }) { diff --git a/packages/webapp/src/constants/dialogs.ts b/packages/webapp/src/constants/dialogs.ts index c9bb52a0e..74ec090cf 100644 --- a/packages/webapp/src/constants/dialogs.ts +++ b/packages/webapp/src/constants/dialogs.ts @@ -53,4 +53,9 @@ export enum DialogsName { EstimateMail = 'estimate-mail', ReceiptMail = 'receipt-mail', PaymentMail = 'payment-mail', + BalanceSheetPdfPreview = 'BalanceSheetPdfPreview', + TrialBalanceSheetPdfPreview = 'TrialBalanceSheetPdfPreview', + CashflowSheetPdfPreview = 'CashflowSheetPdfPreview', + ProfitLossSheetPdfPreview = 'ProfitLossSheetPdfPreview', + } diff --git a/packages/webapp/src/containers/FinancialStatements/BalanceSheet/BalanceSheet.tsx b/packages/webapp/src/containers/FinancialStatements/BalanceSheet/BalanceSheet.tsx index 787c7f14e..11928622f 100644 --- a/packages/webapp/src/containers/FinancialStatements/BalanceSheet/BalanceSheet.tsx +++ b/packages/webapp/src/containers/FinancialStatements/BalanceSheet/BalanceSheet.tsx @@ -13,6 +13,7 @@ import { useBalanceSheetQuery } from './utils'; import { compose } from '@/utils'; import withBalanceSheetActions from './withBalanceSheetActions'; +import { BalanceSheetDialogs } from './BalanceSheetDialogs'; /** * Balance sheet. @@ -67,6 +68,8 @@ function BalanceSheet({ + + ); } diff --git a/packages/webapp/src/containers/FinancialStatements/BalanceSheet/BalanceSheetActionsBar.tsx b/packages/webapp/src/containers/FinancialStatements/BalanceSheet/BalanceSheetActionsBar.tsx index 0421ea2ae..af8de6c12 100644 --- a/packages/webapp/src/containers/FinancialStatements/BalanceSheet/BalanceSheetActionsBar.tsx +++ b/packages/webapp/src/containers/FinancialStatements/BalanceSheet/BalanceSheetActionsBar.tsx @@ -17,7 +17,9 @@ import { BalanceSheetExportMenu } from './components'; import { useBalanceSheetContext } from './BalanceSheetProvider'; import withBalanceSheet from './withBalanceSheet'; import withBalanceSheetActions from './withBalanceSheetActions'; +import withDialogActions from '@/containers/Dialog/withDialogActions'; import { compose, saveInvoke } from '@/utils'; +import { DialogsName } from '@/constants/dialogs'; /** * Balance sheet - actions bar. @@ -29,6 +31,9 @@ function BalanceSheetActionsBar({ // #withBalanceSheetActions toggleBalanceSheetFilterDrawer: toggleFilterDrawer, + // #withDialogsActions + openDialog, + // #ownProps numberFormat, onNumberFormatSubmit, @@ -50,6 +55,10 @@ function BalanceSheetActionsBar({ saveInvoke(onNumberFormatSubmit, values); }; + const handlePdfPrintBtnSubmit = () => { + openDialog(DialogsName.BalanceSheetPdfPreview) + } + return ( @@ -111,6 +120,7 @@ function BalanceSheetActionsBar({